3 # this model expects a certain database layout and its based on the name/login pattern.
6 module AuthenticatedUser
8 def self.included(base)
11 # use the table name given
12 set_table_name LoginEngine.config(:user_table)
14 attr_accessor :new_password
16 validates_presence_of :login
17 validates_length_of :login, :within => 3..40
18 validates_uniqueness_of :login
19 validates_uniqueness_of :email
20 validates_format_of :email, :with => /^[^@]+@.+$/
22 validates_presence_of :password, :if => :validate_password?
23 validates_confirmation_of :password, :if => :validate_password?
24 validates_length_of :password, { :minimum => 5, :if => :validate_password? }
25 validates_length_of :password, { :maximum => 40, :if => :validate_password? }
29 attr_accessor :password, :password_confirmation
31 after_save :falsify_new_password
32 after_validation :crypt_password
35 base.extend(ClassMethods)
40 def authenticate(login, pass)
41 u = find(:first, :conditions => ["login = ? AND verified = 1 AND deleted = 0", login])
43 find(:first, :conditions => ["login = ? AND salted_password = ? AND verified = 1", login, AuthenticatedUser.salted_password(u.salt, AuthenticatedUser.hashed(pass))])
46 def authenticate_by_token(id, token)
47 # Allow logins for deleted accounts, but only via this method (and
48 # not the regular authenticate call)
49 u = find(:first, :conditions => ["#{User.primary_key} = ? AND security_token = ?", id, token])
50 return nil if u.nil? or u.token_expired?
51 return nil if false == u.update_expiry
61 # check if a salt has been set...
62 if LoginEngine.config(:salt) == nil
63 raise "You must define a :salt value in the configuration for the LoginEngine module."
66 return Digest::SHA1.hexdigest("#{LoginEngine.config(:salt)}--#{str}--}")[0..39]
69 def self.salted_password(salt, hashed_password)
70 hashed(salt + hashed_password)
75 # hmmm, how does this interact with the developer's own User model initialize?
76 # We would have to *insist* that the User.initialize method called 'super'
78 def initialize(attributes = nil)
84 self.security_token and self.token_expiry and (Time.now > self.token_expiry)
88 write_attribute('token_expiry', [self.token_expiry, Time.at(Time.now.to_i + 600 * 1000)].min)
89 write_attribute('authenticated_by_token', true)
90 write_attribute("verified", 1)
91 update_without_callbacks
94 def generate_security_token(hours = nil)
95 if not hours.nil? or self.security_token.nil? or self.token_expiry.nil? or
96 (Time.now.to_i + token_lifetime / 2) >= self.token_expiry.to_i
97 return new_security_token(hours)
99 return self.security_token
104 hours = LoginEngine.config(:delayed_delete_days) * 24
105 write_attribute('deleted', 1)
106 write_attribute('delete_after', Time.at(Time.now.to_i + hours * 60 * 60))
108 # Generate and return a token here, so that it expires at
109 # the same time that the account deletion takes effect.
110 return generate_security_token(hours)
113 def change_password(pass, confirm = nil)
115 self.password_confirmation = confirm.nil? ? pass : confirm
121 def validate_password?
128 write_attribute("salt", AuthenticatedUser.hashed("salt-#{Time.now}"))
129 write_attribute("salted_password", AuthenticatedUser.salted_password(salt, AuthenticatedUser.hashed(@password)))
133 def falsify_new_password
134 @new_password = false
138 def new_security_token(hours = nil)
139 write_attribute('security_token', AuthenticatedUser.hashed(self.salted_password + Time.now.to_i.to_s + rand.to_s))
140 write_attribute('token_expiry', Time.at(Time.now.to_i + token_lifetime(hours)))
141 update_without_callbacks
142 return self.security_token
145 def token_lifetime(hours = nil)
147 LoginEngine.config(:security_token_life_hours) * 60 * 60