Module: Recoverable

Extended by:
ActiveSupport::Concern
Defined in:
lib/generators/propel_authentication/templates/concerns/recoverable.rb

Instance Method Summary collapse

Instance Method Details

#generate_password_reset_tokenObject

Generate password reset token with JWT



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/generators/propel_authentication/templates/concerns/recoverable.rb', line 9

def generate_password_reset_token
  # Use high precision timestamp and random nonce for uniqueness
  now = Time.now
  
  payload = {
    user_id: self.id,
    email_address: self.email_address,
    type: 'password_reset',
    iat: now.to_f,  # Use float for higher precision
    exp: PropelAuthentication.configuration.password_reset_expiration.from_now.to_i,
    password_hash: self.password_digest[0..10],  # Bind token to current password
    nonce: SecureRandom.hex(8)  # Add random nonce for uniqueness
  }
  
  JWT.encode(payload, PropelAuthentication.configuration.jwt_secret, 'HS256')
end

#reset_password_with_token!(token, new_password, new_password_confirmation = new_password) ⇒ Object

Reset password with token validation



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/generators/propel_authentication/templates/concerns/recoverable.rb', line 52

def reset_password_with_token!(token, new_password, new_password_confirmation = new_password)
  return false unless valid_password_reset_token?(token)
  
  if new_password.blank?
    errors.add(:password, "cannot be blank")
    return false
  end
  
  if new_password != new_password_confirmation
    errors.add(:password_confirmation, "doesn't match Password")
    return false  
  end
  
  # Validate password length using Rails validations (populates errors)
  self.password = new_password
  unless valid?
    return false  # Rails validations populate errors automatically
  end
  
  begin
    # Update password and clear failed login attempts
    self.password = new_password
    self. = 0  # Clear lockable state
    
    # Save changes
    self.save!
    true
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved
    false
  end
end

#valid_password_reset_token?(token) ⇒ Boolean

Validate password reset token

Returns:

  • (Boolean)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/generators/propel_authentication/templates/concerns/recoverable.rb', line 27

def valid_password_reset_token?(token)
  return false if token.blank?
  
  begin
    decoded = JWT.decode(token, PropelAuthentication.configuration.jwt_secret, true, { algorithm: 'HS256' })
    payload = decoded.first
    
    # Validate token structure and content
    return false unless payload['type'] == 'password_reset'
    return false unless payload['user_id'] == self.id
    return false unless payload['email_address'] == self.email_address
    
    # Validate token is not expired
    return false if token_expired?(payload)
    
    # Validate password hash binding (prevents reuse after password change)
    return false unless valid_password_hash_binding?(payload)
    
    true
  rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::InvalidSignature
    false
  end
end