rodauth 2.31.0 → 2.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +36 -0
  3. data/README.rdoc +1 -1
  4. data/doc/argon2.rdoc +9 -5
  5. data/doc/base.rdoc +1 -0
  6. data/doc/change_login.rdoc +1 -0
  7. data/doc/change_password.rdoc +1 -0
  8. data/doc/close_account.rdoc +1 -0
  9. data/doc/confirm_password.rdoc +1 -0
  10. data/doc/create_account.rdoc +1 -0
  11. data/doc/email_auth.rdoc +1 -0
  12. data/doc/jwt.rdoc +1 -0
  13. data/doc/lockout.rdoc +4 -2
  14. data/doc/login.rdoc +2 -1
  15. data/doc/logout.rdoc +1 -0
  16. data/doc/otp.rdoc +3 -0
  17. data/doc/release_notes/2.32.0.txt +65 -0
  18. data/doc/release_notes/2.33.0.txt +18 -0
  19. data/doc/remember.rdoc +1 -0
  20. data/doc/reset_password.rdoc +2 -0
  21. data/doc/sms_codes.rdoc +7 -0
  22. data/doc/two_factor_base.rdoc +2 -0
  23. data/doc/verify_account.rdoc +2 -0
  24. data/doc/verify_login_change.rdoc +1 -0
  25. data/doc/webauthn.rdoc +2 -0
  26. data/lib/rodauth/features/active_sessions.rb +10 -4
  27. data/lib/rodauth/features/argon2.rb +26 -6
  28. data/lib/rodauth/features/base.rb +39 -4
  29. data/lib/rodauth/features/change_login.rb +2 -2
  30. data/lib/rodauth/features/change_password.rb +2 -2
  31. data/lib/rodauth/features/close_account.rb +2 -2
  32. data/lib/rodauth/features/confirm_password.rb +2 -2
  33. data/lib/rodauth/features/create_account.rb +3 -3
  34. data/lib/rodauth/features/email_auth.rb +13 -20
  35. data/lib/rodauth/features/email_base.rb +4 -6
  36. data/lib/rodauth/features/jwt.rb +17 -1
  37. data/lib/rodauth/features/jwt_refresh.rb +4 -2
  38. data/lib/rodauth/features/lockout.rb +12 -14
  39. data/lib/rodauth/features/login.rb +13 -4
  40. data/lib/rodauth/features/logout.rb +2 -2
  41. data/lib/rodauth/features/otp.rb +35 -7
  42. data/lib/rodauth/features/remember.rb +15 -11
  43. data/lib/rodauth/features/reset_password.rb +11 -13
  44. data/lib/rodauth/features/single_session.rb +4 -3
  45. data/lib/rodauth/features/sms_codes.rb +42 -13
  46. data/lib/rodauth/features/two_factor_base.rb +8 -6
  47. data/lib/rodauth/features/update_password_hash.rb +2 -1
  48. data/lib/rodauth/features/verify_account.rb +13 -20
  49. data/lib/rodauth/features/verify_login_change.rb +8 -10
  50. data/lib/rodauth/features/webauthn.rb +6 -6
  51. data/lib/rodauth/version.rb +1 -1
  52. data/lib/rodauth.rb +16 -0
  53. metadata +6 -2
@@ -14,6 +14,7 @@ module Rodauth
14
14
  additional_form_tags
15
15
  button 'Change Login'
16
16
  redirect
17
+ response
17
18
 
18
19
  auth_value_methods :change_login_requires_password?
19
20
 
@@ -51,9 +52,8 @@ module Rodauth
51
52
  end
52
53
 
53
54
  after_change_login
54
- set_notice_flash change_login_notice_flash
55
- redirect change_login_redirect
56
55
  end
56
+ change_login_response
57
57
  end
58
58
 
59
59
  set_error_flash change_login_error_flash
@@ -13,6 +13,7 @@ module Rodauth
13
13
  additional_form_tags
14
14
  button 'Change Password'
15
15
  redirect
16
+ response
16
17
 
17
18
  translatable_method :new_password_label, 'New Password'
18
19
  auth_value_method :new_password_param, 'new-password'
@@ -56,8 +57,7 @@ module Rodauth
56
57
  set_password(password)
57
58
  after_change_password
58
59
  end
59
- set_notice_flash change_password_notice_flash
60
- redirect change_password_redirect
60
+ change_password_response
61
61
  end
62
62
 
63
63
  set_error_flash change_password_error_flash
@@ -11,6 +11,7 @@ module Rodauth
11
11
  after
12
12
  before
13
13
  redirect
14
+ response
14
15
 
15
16
  auth_value_method :account_closed_status_value, 3
16
17
 
@@ -50,8 +51,7 @@ module Rodauth
50
51
  end
51
52
  clear_session
52
53
 
53
- set_notice_flash close_account_notice_flash
54
- redirect close_account_redirect
54
+ close_account_response
55
55
  end
56
56
 
57
57
  set_error_flash close_account_error_flash
@@ -11,6 +11,7 @@ module Rodauth
11
11
  button 'Confirm Password'
12
12
  before
13
13
  after
14
+ response
14
15
  redirect(:password_authentication_required){confirm_password_path}
15
16
 
16
17
  session_key :confirm_password_redirect_session_key, :confirm_password_redirect
@@ -37,8 +38,7 @@ module Rodauth
37
38
  confirm_password
38
39
  after_confirm_password
39
40
  end
40
- set_notice_flash confirm_password_notice_flash
41
- redirect confirm_password_redirect
41
+ confirm_password_response
42
42
  else
43
43
  set_response_error_reason_status(:invalid_password, invalid_password_error_status)
44
44
  set_field_error(password_param, invalid_password_message)
@@ -13,6 +13,7 @@ module Rodauth
13
13
  button 'Create Account'
14
14
  additional_form_tags
15
15
  redirect
16
+ response
16
17
 
17
18
  auth_value_method :create_account_autologin?, true
18
19
  translatable_method :create_account_link_text, "Create a New Account"
@@ -79,8 +80,7 @@ module Rodauth
79
80
  if create_account_autologin?
80
81
  autologin_session('create_account')
81
82
  end
82
- set_notice_flash create_account_notice_flash
83
- redirect create_account_redirect
83
+ create_account_response
84
84
  end
85
85
  end
86
86
 
@@ -106,7 +106,7 @@ module Rodauth
106
106
  end
107
107
 
108
108
  if id
109
- account[account_id_column] = id
109
+ account[account_id_column] ||= id
110
110
  end
111
111
 
112
112
  id && !raised
@@ -19,6 +19,7 @@ module Rodauth
19
19
  button 'Send Login Link Via Email', 'email_auth_request'
20
20
  redirect(:email_auth_email_sent){default_post_email_redirect}
21
21
  redirect(:email_auth_email_recently_sent){default_post_email_redirect}
22
+ response :email_auth_email_sent
22
23
  email :email_auth, 'Login Link'
23
24
 
24
25
  auth_value_method :email_auth_deadline_column, :deadline
@@ -57,12 +58,11 @@ module Rodauth
57
58
  r.post do
58
59
  if account_from_login(param(login_param)) && open_account?
59
60
  _email_auth_request
60
- else
61
- set_redirect_error_status(no_matching_login_error_status)
62
- set_error_reason :no_matching_login
63
- set_redirect_error_flash email_auth_request_error_flash
64
61
  end
65
62
 
63
+ set_redirect_error_status(no_matching_login_error_status)
64
+ set_error_reason :no_matching_login
65
+ set_redirect_error_flash email_auth_request_error_flash
66
66
  redirect email_auth_email_sent_redirect
67
67
  end
68
68
  end
@@ -77,14 +77,12 @@ module Rodauth
77
77
  redirect(r.path)
78
78
  end
79
79
 
80
- if key = session[email_auth_session_key]
81
- if account_from_email_auth_key(key)
82
- email_auth_view
83
- else
84
- remove_session_value(email_auth_session_key)
85
- set_redirect_error_flash no_matching_email_auth_key_error_flash
86
- redirect require_login_redirect
87
- end
80
+ if (key = session[email_auth_session_key]) && account_from_email_auth_key(key)
81
+ email_auth_view
82
+ else
83
+ remove_session_value(email_auth_session_key)
84
+ set_redirect_error_flash no_matching_email_auth_key_error_flash
85
+ redirect require_login_redirect
88
86
  end
89
87
  end
90
88
 
@@ -150,7 +148,7 @@ module Rodauth
150
148
 
151
149
  def after_login_entered_during_multi_phase_login
152
150
  # If forcing email auth, just send the email link.
153
- _email_auth_request_and_redirect if force_email_auth?
151
+ _email_auth_request if force_email_auth?
154
152
 
155
153
  super
156
154
  end
@@ -169,7 +167,7 @@ module Rodauth
169
167
 
170
168
  def _multi_phase_login_forms
171
169
  forms = super
172
- forms << [30, email_auth_request_form, :_email_auth_request_and_redirect] if valid_login_entered? && allow_email_auth?
170
+ forms << [30, email_auth_request_form, :_email_auth_request] if valid_login_entered? && allow_email_auth?
173
171
  forms
174
172
  end
175
173
 
@@ -177,11 +175,6 @@ module Rodauth
177
175
  (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
178
176
  end
179
177
 
180
- def _email_auth_request_and_redirect
181
- _email_auth_request
182
- redirect email_auth_email_sent_redirect
183
- end
184
-
185
178
  def _email_auth_request
186
179
  if email_auth_email_recently_sent?
187
180
  set_redirect_error_flash email_auth_email_recently_sent_error_flash
@@ -196,7 +189,7 @@ module Rodauth
196
189
  after_email_auth_request
197
190
  end
198
191
 
199
- set_notice_flash email_auth_email_sent_notice_flash
192
+ email_auth_email_sent_response
200
193
  end
201
194
 
202
195
  attr_reader :email_auth_key_value
@@ -69,12 +69,10 @@ module Rodauth
69
69
 
70
70
  return unless actual = yield(id)
71
71
 
72
- unless timing_safe_eql?(key, convert_email_token_key(actual))
73
- if hmac_secret && allow_raw_email_token?
74
- return unless timing_safe_eql?(key, actual)
75
- else
76
- return
77
- end
72
+ unless (hmac_secret && timing_safe_eql?(key, convert_email_token_key(actual))) ||
73
+ (hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual))) ||
74
+ ((!hmac_secret || allow_raw_email_token?) && timing_safe_eql?(key, actual))
75
+ return
78
76
  end
79
77
  ds = account_ds(id)
80
78
  ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks?
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'jwt'
4
+ require 'jwt/version'
4
5
 
5
6
  module Rodauth
6
7
  Feature.define(:jwt, :Jwt) do
@@ -11,6 +12,7 @@ module Rodauth
11
12
  auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
12
13
  auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
13
14
  auth_value_method :jwt_decode_opts, {}.freeze
15
+ auth_value_method :jwt_old_secret, nil
14
16
  auth_value_method :jwt_session_key, nil
15
17
  auth_value_method :jwt_symbolize_deeply?, false
16
18
 
@@ -111,9 +113,23 @@ module Rodauth
111
113
  jwt_decode_opts
112
114
  end
113
115
 
116
+ if JWT::VERSION::MAJOR > 2 || (JWT::VERSION::MAJOR == 2 && JWT::VERSION::MINOR >= 4)
117
+ def _jwt_decode_secrets
118
+ secrets = [jwt_secret, jwt_old_secret]
119
+ secrets.compact!
120
+ secrets
121
+ end
122
+ # :nocov:
123
+ else
124
+ def _jwt_decode_secrets
125
+ jwt_secret
126
+ end
127
+ # :nocov:
128
+ end
129
+
114
130
  def jwt_payload
115
131
  return @jwt_payload if defined?(@jwt_payload)
116
- @jwt_payload = JWT.decode(jwt_token, jwt_secret, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
132
+ @jwt_payload = JWT.decode(jwt_token, _jwt_decode_secrets, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
117
133
  rescue JWT::DecodeError => e
118
134
  rescue_jwt_payload(e)
119
135
  end
@@ -114,7 +114,7 @@ module Rodauth
114
114
  unless key &&
115
115
  (id.to_s == session_value.to_s) &&
116
116
  (actual = get_active_refresh_token(id, token_id)) &&
117
- timing_safe_eql?(key, convert_token_key(actual)) &&
117
+ (timing_safe_eql?(key, convert_token_key(actual)) || (hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual)))) &&
118
118
  jwt_refresh_token_match?(key)
119
119
  return
120
120
  end
@@ -150,7 +150,9 @@ module Rodauth
150
150
 
151
151
  # If allowing with expired jwt access token, check the expired session contains
152
152
  # hmac matching submitted and active refresh token.
153
- timing_safe_eql?(compute_hmac(session[jwt_refresh_token_data_session_key].to_s + key), session[jwt_refresh_token_hmac_session_key].to_s)
153
+ s = session[jwt_refresh_token_hmac_session_key].to_s
154
+ h = session[jwt_refresh_token_data_session_key].to_s + key
155
+ timing_safe_eql?(compute_hmac(h), s) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(h), s))
154
156
  end
155
157
 
156
158
  def get_active_refresh_token(account_id, token_id)
@@ -23,10 +23,12 @@ module Rodauth
23
23
  notice_flash "Your account has been unlocked", 'unlock_account'
24
24
  notice_flash "An email has been sent to you with a link to unlock your account", 'unlock_account_request'
25
25
  redirect :unlock_account
26
+ response :unlock_account
27
+ response :unlock_account_request
26
28
  redirect(:unlock_account_request){default_post_email_redirect}
27
29
  redirect(:unlock_account_email_recently_sent){default_post_email_redirect}
28
30
  email :unlock_account, 'Unlock Account'
29
-
31
+
30
32
  auth_value_method :unlock_account_autologin?, true
31
33
  auth_value_method :max_invalid_logins, 100
32
34
  auth_value_method :account_login_failures_table, :account_login_failures
@@ -82,14 +84,13 @@ module Rodauth
82
84
  after_unlock_account_request
83
85
  end
84
86
 
85
- set_notice_flash unlock_account_request_notice_flash
87
+ unlock_account_request_response
86
88
  else
87
89
  set_redirect_error_status(no_matching_login_error_status)
88
90
  set_error_reason :no_matching_login
89
91
  set_redirect_error_flash no_matching_login_message.to_s.capitalize
92
+ redirect unlock_account_request_redirect
90
93
  end
91
-
92
- redirect unlock_account_request_redirect
93
94
  end
94
95
  end
95
96
 
@@ -103,14 +104,12 @@ module Rodauth
103
104
  redirect(r.path)
104
105
  end
105
106
 
106
- if key = session[unlock_account_session_key]
107
- if account_from_unlock_key(key)
108
- unlock_account_view
109
- else
110
- remove_session_value(unlock_account_session_key)
111
- set_redirect_error_flash no_matching_unlock_account_key_error_flash
112
- redirect require_login_redirect
113
- end
107
+ if (key = session[unlock_account_session_key]) && account_from_unlock_key(key)
108
+ unlock_account_view
109
+ else
110
+ remove_session_value(unlock_account_session_key)
111
+ set_redirect_error_flash no_matching_unlock_account_key_error_flash
112
+ redirect require_login_redirect
114
113
  end
115
114
  end
116
115
 
@@ -134,8 +133,7 @@ module Rodauth
134
133
  end
135
134
 
136
135
  remove_session_value(unlock_account_session_key)
137
- set_notice_flash unlock_account_notice_flash
138
- redirect unlock_account_redirect
136
+ unlock_account_response
139
137
  else
140
138
  set_response_error_reason_status(:invalid_password, invalid_password_error_status)
141
139
  set_field_error(password_param, invalid_password_message)
@@ -24,7 +24,10 @@ module Rodauth
24
24
 
25
25
  auth_value_methods :login_return_to_requested_location_path
26
26
 
27
- auth_private_methods :login_form_footer_links
27
+ auth_private_methods(
28
+ :login_form_footer_links,
29
+ :login_response
30
+ )
28
31
 
29
32
  internal_request_method
30
33
  internal_request_method :valid_login_and_password?
@@ -77,17 +80,18 @@ module Rodauth
77
80
  end
78
81
 
79
82
  attr_reader :login_form_header
83
+ attr_reader :saved_login_redirect
84
+ private :saved_login_redirect
80
85
 
81
86
  def login(auth_type)
82
- saved_login_redirect = remove_session_value(login_redirect_session_key)
87
+ @saved_login_redirect = remove_session_value(login_redirect_session_key)
83
88
  transaction do
84
89
  before_login
85
90
  login_session(auth_type)
86
91
  yield if block_given?
87
92
  after_login
88
93
  end
89
- set_notice_flash login_notice_flash
90
- redirect(saved_login_redirect || login_redirect)
94
+ require_response(:_login_response)
91
95
  end
92
96
 
93
97
  def login_required
@@ -136,6 +140,11 @@ module Rodauth
136
140
 
137
141
  private
138
142
 
143
+ def _login_response
144
+ set_notice_flash login_notice_flash
145
+ redirect(saved_login_redirect || login_redirect)
146
+ end
147
+
139
148
  def _login_form_footer_links
140
149
  []
141
150
  end
@@ -10,6 +10,7 @@ module Rodauth
10
10
  after
11
11
  button 'Logout'
12
12
  redirect{require_login_redirect}
13
+ response
13
14
 
14
15
  auth_methods :logout
15
16
 
@@ -26,8 +27,7 @@ module Rodauth
26
27
  logout
27
28
  after_logout
28
29
  end
29
- set_notice_flash logout_notice_flash
30
- redirect logout_redirect
30
+ logout_response
31
31
  end
32
32
  end
33
33
 
@@ -35,6 +35,8 @@ module Rodauth
35
35
  redirect :otp_disable
36
36
  redirect :otp_already_setup
37
37
  redirect :otp_setup
38
+ response :otp_disable
39
+ response :otp_setup
38
40
  redirect(:otp_lockout){two_factor_auth_required_redirect}
39
41
 
40
42
  loaded_templates %w'otp-disable otp-auth otp-setup otp-auth-code-field password-field'
@@ -94,7 +96,8 @@ module Rodauth
94
96
 
95
97
  auth_private_methods(
96
98
  :otp_add_key,
97
- :otp_tmp_key
99
+ :otp_tmp_key,
100
+ :otp_valid_code_for_old_secret
98
101
  )
99
102
 
100
103
  internal_request_method :otp_setup_params
@@ -181,8 +184,7 @@ module Rodauth
181
184
  end
182
185
  after_otp_setup
183
186
  end
184
- set_notice_flash otp_setup_notice_flash
185
- redirect otp_setup_redirect
187
+ otp_setup_response
186
188
  end
187
189
 
188
190
  set_error_flash otp_setup_error_flash
@@ -209,8 +211,7 @@ module Rodauth
209
211
  end
210
212
  after_otp_disable
211
213
  end
212
- set_notice_flash otp_disable_notice_flash
213
- redirect otp_disable_redirect
214
+ otp_disable_response
214
215
  end
215
216
 
216
217
  set_response_error_reason_status(:invalid_password, invalid_password_error_status)
@@ -246,8 +247,19 @@ module Rodauth
246
247
  def otp_exists?
247
248
  !otp_key.nil?
248
249
  end
249
-
250
+
250
251
  def otp_valid_code?(ot_pass)
252
+ if _otp_valid_code?(ot_pass, otp)
253
+ true
254
+ elsif hmac_secret_rotation? && _otp_valid_code?(ot_pass, _otp_for_key(otp_hmac_old_secret(otp_key)))
255
+ _otp_valid_code_for_old_secret
256
+ true
257
+ else
258
+ false
259
+ end
260
+ end
261
+
262
+ def _otp_valid_code?(ot_pass, otp)
251
263
  return false unless otp_exists?
252
264
  ot_pass = ot_pass.gsub(/\s+/, '')
253
265
  if drift = otp_drift
@@ -368,9 +380,17 @@ module Rodauth
368
380
  base32_encode(compute_raw_hmac(ROTP::Base32.decode(key)), key.bytesize)
369
381
  end
370
382
 
383
+ def otp_hmac_old_secret(key)
384
+ base32_encode(compute_raw_hmac_with_secret(ROTP::Base32.decode(key), hmac_old_secret), key.bytesize)
385
+ end
386
+
371
387
  def otp_valid_key?(secret)
372
388
  return false unless secret =~ /\A([a-z2-7]{16}|[a-z2-7]{32})\z/
373
389
  if otp_keys_use_hmac?
390
+ # Purposely do not allow creating new OTPs with old secrets,
391
+ # since OTP rotation is difficult. The user will get shown
392
+ # the same page with an updated secret, which they can submit
393
+ # to setup OTP.
374
394
  timing_safe_eql?(otp_hmac_secret(param(otp_setup_raw_param)), secret)
375
395
  else
376
396
  true
@@ -400,6 +420,10 @@ module Rodauth
400
420
  @otp_key = secret
401
421
  end
402
422
 
423
+ # Called for valid OTP codes for old secrets
424
+ def _otp_valid_code_for_old_secret
425
+ end
426
+
403
427
  def _otp_add_key(secret)
404
428
  # Uniqueness errors can't be handled here, as we can't be sure the secret provided
405
429
  # is the same as the current secret.
@@ -411,8 +435,12 @@ module Rodauth
411
435
  otp_key_ds.get(otp_keys_column)
412
436
  end
413
437
 
438
+ def _otp_for_key(key)
439
+ otp_class.new(key, :issuer=>otp_issuer, :digits=>otp_digits, :interval=>otp_interval)
440
+ end
441
+
414
442
  def _otp
415
- otp_class.new(otp_user_key, :issuer=>otp_issuer, :digits=>otp_digits, :interval=>otp_interval)
443
+ _otp_for_key(otp_user_key)
416
444
  end
417
445
 
418
446
  def otp_key_ds
@@ -13,6 +13,7 @@ module Rodauth
13
13
  after
14
14
  after 'load_memory'
15
15
  redirect
16
+ response
16
17
 
17
18
  auth_value_method :raw_remember_token_deadline, nil
18
19
  auth_value_method :remember_cookie_options, {}.freeze
@@ -71,15 +72,14 @@ module Rodauth
71
72
  when remember_remember_param_value
72
73
  remember_login
73
74
  when remember_forget_param_value
74
- forget_login
75
+ forget_login
75
76
  when remember_disable_param_value
76
- disable_remember_login
77
+ disable_remember_login
77
78
  end
78
79
  after_remember
79
80
  end
80
81
 
81
- set_notice_flash remember_notice_flash
82
- redirect remember_redirect
82
+ remember_response
83
83
  else
84
84
  set_response_error_reason_status(:invalid_remember_param, invalid_field_error_status)
85
85
  set_error_flash remember_error_flash
@@ -96,11 +96,11 @@ module Rodauth
96
96
  actual, deadline = active_remember_key_ds(id).get([remember_key_column, remember_deadline_column])
97
97
  return unless actual
98
98
 
99
- if hmac_secret
100
- unless valid = timing_safe_eql?(key, compute_hmac(actual))
101
- unless raw_remember_token_deadline && raw_remember_token_deadline > convert_timestamp(deadline)
102
- return
103
- end
99
+ if hmac_secret && !(valid = timing_safe_eql?(key, compute_hmac(actual)))
100
+ if hmac_secret_rotation? && (valid = timing_safe_eql?(key, compute_old_hmac(actual)))
101
+ _set_remember_cookie(id, actual, deadline)
102
+ elsif !(raw_remember_token_deadline && raw_remember_token_deadline > convert_timestamp(deadline))
103
+ return
104
104
  end
105
105
  end
106
106
 
@@ -177,16 +177,20 @@ module Rodauth
177
177
 
178
178
  private
179
179
 
180
- def set_remember_cookie
180
+ def _set_remember_cookie(account_id, remember_key_value, deadline)
181
181
  opts = Hash[remember_cookie_options]
182
182
  opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
183
- opts[:expires] = convert_timestamp(active_remember_key_ds.get(remember_deadline_column))
183
+ opts[:expires] = convert_timestamp(deadline)
184
184
  opts[:path] = "/" unless opts.key?(:path)
185
185
  opts[:httponly] = true unless opts.key?(:httponly) || opts.key?(:http_only)
186
186
  opts[:secure] = true unless opts.key?(:secure) || !request.ssl?
187
187
  ::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
188
188
  end
189
189
 
190
+ def set_remember_cookie
191
+ _set_remember_cookie(account_id, remember_key_value, active_remember_key_ds.get(remember_deadline_column))
192
+ end
193
+
190
194
  def extend_remember_deadline_while_logged_in?
191
195
  return false unless extend_remember_deadline?
192
196
 
@@ -24,8 +24,10 @@ module Rodauth
24
24
  redirect
25
25
  redirect(:reset_password_email_sent){default_post_email_redirect}
26
26
  redirect(:reset_password_email_recently_sent){default_post_email_redirect}
27
+ response
28
+ response :reset_password_email_sent
27
29
  email :reset_password, 'Reset Password'
28
-
30
+
29
31
  auth_value_method :reset_password_deadline_column, :deadline
30
32
  auth_value_method :reset_password_deadline_interval, {:days=>1}.freeze
31
33
  auth_value_method :reset_password_key_param, 'key'
@@ -88,8 +90,7 @@ module Rodauth
88
90
  after_reset_password_request
89
91
  end
90
92
 
91
- set_notice_flash reset_password_email_sent_notice_flash
92
- redirect reset_password_email_sent_redirect
93
+ reset_password_email_sent_response
93
94
  end
94
95
 
95
96
  set_error_flash reset_password_request_error_flash
@@ -108,14 +109,12 @@ module Rodauth
108
109
  redirect(r.path)
109
110
  end
110
111
 
111
- if key = session[reset_password_session_key]
112
- if account_from_reset_password_key(key)
113
- reset_password_view
114
- else
115
- remove_session_value(reset_password_session_key)
116
- set_redirect_error_flash no_matching_reset_password_key_error_flash
117
- redirect require_login_redirect
118
- end
112
+ if (key = session[reset_password_session_key]) && account_from_reset_password_key(key)
113
+ reset_password_view
114
+ else
115
+ remove_session_value(reset_password_session_key)
116
+ set_redirect_error_flash no_matching_reset_password_key_error_flash
117
+ redirect require_login_redirect
119
118
  end
120
119
  end
121
120
 
@@ -154,8 +153,7 @@ module Rodauth
154
153
  end
155
154
 
156
155
  remove_session_value(reset_password_session_key)
157
- set_notice_flash reset_password_notice_flash
158
- redirect reset_password_redirect
156
+ reset_password_response
159
157
  end
160
158
 
161
159
  set_error_flash reset_password_error_flash
@@ -37,9 +37,10 @@ module Rodauth
37
37
  end
38
38
  true
39
39
  elsif current_key
40
- if hmac_secret
41
- valid = timing_safe_eql?(single_session_key, compute_hmac(current_key))
42
- if !valid && !allow_raw_single_session_key?
40
+ if hmac_secret && !(valid = timing_safe_eql?(single_session_key, hmac = compute_hmac(current_key)))
41
+ if hmac_secret_rotation? && (valid = timing_safe_eql?(single_session_key, compute_old_hmac(current_key)))
42
+ session[single_session_session_key] = hmac
43
+ elsif !allow_raw_single_session_key?
43
44
  return false
44
45
  end
45
46
  end