rodauth 2.31.0 → 2.33.0

Sign up to get free protection for your applications and to get access to all the features.
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