rodauth 2.20.0 → 2.23.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ = Improvements
2
+
3
+ * The otp feature now uses the :use_path option when rendering QR
4
+ codes, resulting in significantly smaller svg images.
5
+
6
+ * Removing all multifactor authentication methods now removes the fact
7
+ that the session was authenticated via SMS, if the user used SMS as
8
+ an authentication method for the current session.
9
+
10
+ * The invalid domain check in the internal_request feature now works
11
+ correctly when using the rack master branch.
12
+
13
+ * The :httponly cookie option is no longer set automatically in the
14
+ remember feature if the :http_only cookie option was provided by the
15
+ user (rack recognizes both options).
@@ -14,12 +14,12 @@ reset_password_autologin? :: Whether to autologin the user after successfully re
14
14
  reset_password_button :: The text to use for the reset password button.
15
15
  reset_password_deadline_column :: The column name in the +reset_password_table+ storing the deadline after which the token will be ignored.
16
16
  reset_password_deadline_interval :: The amount of time for which to allow users to reset their passwords, 1 day by default. Only used if +set_deadline_values?+ is true.
17
- reset_password_email_last_sent_column :: The email last sent column in the +reset_password_table+. Set to nil to always send a reset password email when requested.
18
- reset_password_email_recently_sent_error_flash :: The flash error to show if not sending reset password email because one has been sent recently.
19
- reset_password_email_recently_sent_redirect :: Where to redirect if not sending reset password email because one has been sent recently.
20
- reset_password_email_sent_notice_flash :: The flash notice to show after a reset password email has been sent.
21
- reset_password_email_sent_redirect :: Where to redirect after sending a reset password email.
22
- reset_password_email_subject :: The subject to use for reset password emails.
17
+ reset_password_email_last_sent_column :: The email last sent column in the +reset_password_table+. Set to nil to always send a reset password request email when requested.
18
+ reset_password_email_recently_sent_error_flash :: The flash error to show if not sending reset password request email because one has been sent recently.
19
+ reset_password_email_recently_sent_redirect :: Where to redirect if not sending reset password request email because one has been sent recently.
20
+ reset_password_email_sent_notice_flash :: The flash notice to show after a reset password request email has been sent.
21
+ reset_password_email_sent_redirect :: Where to redirect after sending a reset password request email.
22
+ reset_password_email_subject :: The subject to use for the reset password request email.
23
23
  reset_password_error_flash :: The flash error to show after resetting a password.
24
24
  reset_password_explanatory_text :: The text to display above the button to request a password reset.
25
25
  reset_password_id_column :: The id column in the +reset_password_table+, should be a foreign key referencing the accounts table.
@@ -30,35 +30,35 @@ reset_password_page_title :: The page title to use on the reset password form.
30
30
  reset_password_redirect :: Where to redirect after resetting a password.
31
31
  reset_password_request_additional_form_tags :: HTML fragment containing additional form tags to use on the reset password request form.
32
32
  reset_password_request_button :: The text to use for the reset password request button.
33
- reset_password_request_error_flash :: The flash error to show if not able to send a reset password email.
33
+ reset_password_request_error_flash :: The flash error to show if not able to send a reset password request email.
34
34
  reset_password_request_link_text :: The text to use for a link to the page to request a password reset.
35
35
  reset_password_request_page_title :: The page title to use on the reset password request form.
36
36
  reset_password_request_route :: The route to the reset password request action. Defaults to +reset-password-request+.
37
37
  reset_password_route :: The route to the reset password action. Defaults to +reset-password+.
38
38
  reset_password_session_key :: The key in the session to hold the reset password key temporarily.
39
- reset_password_skip_resend_email_within :: The number of seconds before sending another reset password email, if +reset_password_email_last_sent_column+ is set.
39
+ reset_password_skip_resend_email_within :: The number of seconds before sending another reset password request email, if +reset_password_email_last_sent_column+ is set.
40
40
  reset_password_table :: The name of the reset password keys table.
41
41
 
42
42
  == Auth Methods
43
43
 
44
44
  account_from_reset_password_key(key) :: Retrieve the account using the given reset password key, or return nil if no account matches.
45
45
  after_reset_password :: Run arbitrary code after successfully resetting a password.
46
- after_reset_password_request :: Run arbitrary code after sending the reset password email.
46
+ after_reset_password_request :: Run arbitrary code after sending the reset password request email.
47
47
  before_reset_password :: Run arbitrary code before resetting a password.
48
- before_reset_password_request :: Run arbitrary code before sending the reset password email.
48
+ before_reset_password_request :: Run arbitrary code before sending the reset password request email.
49
49
  before_reset_password_request_route :: Run arbitrary code before handling a reset password request route.
50
50
  before_reset_password_route :: Run arbitrary code before handling a reset password route.
51
- create_reset_password_email :: A Mail::Message for the reset password email.
51
+ create_reset_password_email :: A Mail::Message for the reset password request email.
52
52
  create_reset_password_key :: Add the reset password key data to the database.
53
- get_reset_password_email_last_sent :: Get the last time a reset password email is sent, or nil if there is no last sent time.
53
+ get_reset_password_email_last_sent :: Get the last time a reset password request email is sent, or nil if there is no last sent time.
54
54
  get_reset_password_key(id) :: Get the password reset key for the given account id from the database.
55
55
  login_failed_reset_password_request_form :: The HTML to use for a form to request a password reset, shown on the login page after the user tries to login with an invalid password.
56
56
  remove_reset_password_key :: Remove the reset password key for the current account, run after successful password reset.
57
- reset_password_email_body :: The body to use for the reset password email.
58
- reset_password_email_link :: The link to the reset password form in the reset password email.
57
+ reset_password_email_body :: The body to use for the reset password request email.
58
+ reset_password_email_link :: The link to the reset password form in the reset password request email.
59
59
  reset_password_key_insert_hash :: The hash to insert into the +reset_password_table+.
60
60
  reset_password_key_value :: The reset password key for the current account.
61
61
  reset_password_request_view :: The HTML to use for the reset password request form.
62
62
  reset_password_view :: The HTML to use for the reset password form.
63
- send_reset_password_email :: Send the reset password email.
64
- set_reset_password_email_last_sent :: Set the last time a reset password email is sent.
63
+ send_reset_password_email :: Send the reset password request email.
64
+ set_reset_password_email_last_sent :: Set the last time a reset password request email is sent.
@@ -0,0 +1,17 @@
1
+ = Documentation for Reset Password Notify Feature
2
+
3
+ The reset password notify feature emails the user after the user has
4
+ reset their password. The user has already been sent a reset password
5
+ email by this point, so they know a password reset was requested, but
6
+ this feature allows for confirming that the password reset process
7
+ was completed. Depends on the reset_password feature.
8
+
9
+ == Auth Value Methods
10
+
11
+ reset_password_notify_email_subject :: The subject to use for the reset password notify email.
12
+ reset_password_notify_email_body :: The body to use for the reset password notify email.
13
+
14
+ == Auth Methods
15
+
16
+ create_reset_password_notify_email :: A Mail::Message for the reset password notify email.
17
+ send_reset_password_notify_email :: Send the reset password notify email.
@@ -13,7 +13,7 @@ module Rodauth
13
13
  auth_value_method :active_sessions_last_use_column, :last_use
14
14
  auth_value_method :active_sessions_session_id_column, :session_id
15
15
  auth_value_method :active_sessions_table, :account_active_session_keys
16
- translatable_method :global_logout_label, 'Logout all Logged In Sessons?'
16
+ translatable_method :global_logout_label, 'Logout all Logged In Sessions?'
17
17
  auth_value_method :global_logout_param, 'global_logout'
18
18
  auth_value_method :inactive_session_error_status, 401
19
19
  auth_value_method :session_inactivity_deadline, 86400
@@ -81,7 +81,9 @@ module Rodauth
81
81
  end
82
82
 
83
83
  def remove_current_session
84
- active_sessions_ds.where(active_sessions_session_id_column=>compute_hmac(session[session_id_session_key])).delete
84
+ if session_id = session[session_id_session_key]
85
+ active_sessions_ds.where(active_sessions_session_id_column=>compute_hmac(session_id)).delete
86
+ end
85
87
  end
86
88
 
87
89
  def remove_all_active_sessions
@@ -1,5 +1,8 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/request'
4
+ require 'rack/utils'
5
+
3
6
  module Rodauth
4
7
  Feature.define(:base, :Base) do
5
8
  after 'login'
@@ -91,6 +94,7 @@ module Rodauth
91
94
  :inputmode_for_field?,
92
95
  :logged_in?,
93
96
  :login_required,
97
+ :null_byte_parameter_value,
94
98
  :open_account?,
95
99
  :password_match?,
96
100
  :random_key,
@@ -338,6 +342,11 @@ module Rodauth
338
342
  require_login
339
343
  end
340
344
 
345
+ def require_account
346
+ require_authentication
347
+ require_account_session
348
+ end
349
+
341
350
  def account_initial_status_value
342
351
  account_open_status_value
343
352
  end
@@ -441,7 +450,16 @@ module Rodauth
441
450
  # parameter with that name.
442
451
  def param_or_nil(key)
443
452
  value = raw_param(key)
444
- value.to_s unless value.nil?
453
+ unless value.nil?
454
+ value = value.to_s
455
+ value = null_byte_parameter_value(key, value) if value.include?("\0")
456
+ end
457
+ value
458
+ end
459
+
460
+ # Return nil by default for values with null bytes
461
+ def null_byte_parameter_value(key, value)
462
+ nil
445
463
  end
446
464
 
447
465
  def raw_param(key)
@@ -496,6 +514,11 @@ module Rodauth
496
514
  request.redirect(path)
497
515
  end
498
516
 
517
+ def return_response(body=nil)
518
+ response.write(body) if body
519
+ request.halt
520
+ end
521
+
499
522
  def route_path(route, opts={})
500
523
  path = "#{prefix}/#{route}"
501
524
  path += "?#{Rack::Utils.build_nested_query(opts)}" unless opts.empty?
@@ -524,11 +547,6 @@ module Rodauth
524
547
  Rack::Utils.secure_compare(provided.ljust(actual.length), actual) && provided.length == actual.length
525
548
  end
526
549
 
527
- def require_account
528
- require_authentication
529
- require_account_session
530
- end
531
-
532
550
  def require_account_session
533
551
  unless account_from_session
534
552
  clear_session
@@ -756,7 +774,7 @@ module Rodauth
756
774
  num = ds.update(values)
757
775
  if num == 1
758
776
  values.each do |k, v|
759
- account[k] = v == Sequel::CURRENT_TIMESTAMP ? Time.now : v
777
+ hash[k] = Sequel::CURRENT_TIMESTAMP == v ? Time.now : v
760
778
  end
761
779
  end
762
780
  num
@@ -3,31 +3,11 @@
3
3
  module Rodauth
4
4
  Feature.define(:change_password_notify, :ChangePasswordNotify) do
5
5
  depends :change_password, :email_base
6
-
7
- translatable_method :password_changed_email_subject, 'Password Changed'
8
-
9
- auth_value_methods(
10
- :password_changed_email_body
11
- )
12
- auth_methods(
13
- :create_password_changed_email,
14
- :send_password_changed_email
15
- )
6
+ loaded_templates %w'password-changed-email'
7
+ email :password_changed, 'Password Changed', :translatable=>true
16
8
 
17
9
  private
18
10
 
19
- def send_password_changed_email
20
- send_email(create_password_changed_email)
21
- end
22
-
23
- def create_password_changed_email
24
- create_email(password_changed_email_subject, password_changed_email_body)
25
- end
26
-
27
- def password_changed_email_body
28
- render('password-changed-email')
29
- end
30
-
31
11
  def after_change_password
32
12
  super
33
13
  send_password_changed_email
@@ -19,10 +19,10 @@ 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
+ email :email_auth, 'Login Link'
22
23
 
23
24
  auth_value_method :email_auth_deadline_column, :deadline
24
25
  auth_value_method :email_auth_deadline_interval, {:days=>1}.freeze
25
- translatable_method :email_auth_email_subject, 'Login Link'
26
26
  auth_value_method :email_auth_id_column, :id
27
27
  auth_value_method :email_auth_key_column, :key
28
28
  auth_value_method :email_auth_key_param, 'key'
@@ -33,9 +33,7 @@ module Rodauth
33
33
  session_key :email_auth_session_key, :email_auth_key
34
34
 
35
35
  auth_methods(
36
- :create_email_auth_email,
37
36
  :create_email_auth_key,
38
- :email_auth_email_body,
39
37
  :email_auth_email_link,
40
38
  :email_auth_key_insert_hash,
41
39
  :email_auth_key_value,
@@ -43,7 +41,6 @@ module Rodauth
43
41
  :get_email_auth_key,
44
42
  :get_email_auth_email_last_sent,
45
43
  :remove_email_auth_key,
46
- :send_email_auth_email,
47
44
  :set_email_auth_email_last_sent
48
45
  )
49
46
 
@@ -137,10 +134,6 @@ module Rodauth
137
134
  @account = _account_from_email_auth_key(key)
138
135
  end
139
136
 
140
- def send_email_auth_email
141
- send_email(create_email_auth_email)
142
- end
143
-
144
137
  def email_auth_email_link
145
138
  token_link(email_auth_route, email_auth_key_param, email_auth_key_value)
146
139
  end
@@ -233,14 +226,6 @@ module Rodauth
233
226
  @email_auth_key_value = random_key
234
227
  end
235
228
 
236
- def create_email_auth_email
237
- create_email(email_auth_email_subject, email_auth_email_body)
238
- end
239
-
240
- def email_auth_email_body
241
- render('email-auth-email')
242
- end
243
-
244
229
  def use_date_arithmetic?
245
230
  super || db.database_type == :mysql
246
231
  end
@@ -27,7 +27,7 @@ module Rodauth
27
27
  def require_http_basic_auth
28
28
  unless http_basic_auth
29
29
  set_http_basic_auth_error_response
30
- request.halt
30
+ return_response
31
31
  end
32
32
  end
33
33
 
@@ -40,7 +40,7 @@ module Rodauth
40
40
 
41
41
  def domain
42
42
  d = super
43
- if d == INVALID_DOMAIN
43
+ if d.nil? || d == INVALID_DOMAIN
44
44
  raise InternalRequestError, "must set domain in configuration, as it cannot be determined from internal request"
45
45
  end
46
46
  d
@@ -156,8 +156,7 @@ module Rodauth
156
156
  end
157
157
  elsif only_json?
158
158
  response.status = json_response_error_status
159
- response.write non_json_request_error_message
160
- request.halt
159
+ return_response non_json_request_error_message
161
160
  end
162
161
 
163
162
  super
@@ -175,8 +174,7 @@ module Rodauth
175
174
  def _return_json_response
176
175
  response.status ||= json_response_error_status if json_response[json_response_error_key]
177
176
  response['Content-Type'] ||= json_response_content_type
178
- response.write(_json_response_body(json_response))
179
- request.halt
177
+ return_response _json_response_body(json_response)
180
178
  end
181
179
 
182
180
  def include_success_messages?
@@ -41,7 +41,7 @@ module Rodauth
41
41
  response['Access-Control-Allow-Headers'] = jwt_cors_allow_headers
42
42
  response['Access-Control-Max-Age'] = jwt_cors_max_age.to_s
43
43
  response.status = 204
44
- request.halt(response.finish)
44
+ return_response
45
45
  end
46
46
 
47
47
  response['Access-Control-Expose-Headers'] = jwt_cors_expose_headers
@@ -25,6 +25,7 @@ module Rodauth
25
25
  redirect :unlock_account
26
26
  redirect(:unlock_account_request){default_post_email_redirect}
27
27
  redirect(:unlock_account_email_recently_sent){default_post_email_redirect}
28
+ email :unlock_account, 'Unlock Account'
28
29
 
29
30
  auth_value_method :unlock_account_autologin?, true
30
31
  auth_value_method :max_invalid_logins, 100
@@ -37,7 +38,6 @@ module Rodauth
37
38
  auth_value_method :account_lockouts_email_last_sent_column, :email_last_sent
38
39
  auth_value_method :account_lockouts_deadline_column, :deadline
39
40
  auth_value_method :account_lockouts_deadline_interval, {:days=>1}.freeze
40
- translatable_method :unlock_account_email_subject, 'Unlock Account'
41
41
  translatable_method :unlock_account_explanatory_text, '<p>This account is currently locked out. You can unlock the account:</p>'
42
42
  translatable_method :unlock_account_request_explanatory_text, '<p>This account is currently locked out. You can request that the account be unlocked:</p>'
43
43
  auth_value_method :unlock_account_key_param, 'key'
@@ -47,15 +47,12 @@ module Rodauth
47
47
 
48
48
  auth_methods(
49
49
  :clear_invalid_login_attempts,
50
- :create_unlock_account_email,
51
50
  :generate_unlock_account_key,
52
51
  :get_unlock_account_key,
53
52
  :get_unlock_account_email_last_sent,
54
53
  :invalid_login_attempted,
55
54
  :locked_out?,
56
- :send_unlock_account_email,
57
55
  :set_unlock_account_email_last_sent,
58
- :unlock_account_email_body,
59
56
  :unlock_account_email_link,
60
57
  :unlock_account,
61
58
  :unlock_account_key
@@ -226,10 +223,6 @@ module Rodauth
226
223
  @account = _account_from_unlock_key(key)
227
224
  end
228
225
 
229
- def send_unlock_account_email
230
- send_email(create_unlock_account_email)
231
- end
232
-
233
226
  def unlock_account_email_link
234
227
  token_link(unlock_account_route, unlock_account_key_param, unlock_account_key_value)
235
228
  end
@@ -284,16 +277,7 @@ module Rodauth
284
277
  def show_lockout_page
285
278
  set_response_error_reason_status(:account_locked_out, lockout_error_status)
286
279
  set_error_flash login_lockout_error_flash
287
- response.write unlock_account_request_view
288
- request.halt
289
- end
290
-
291
- def create_unlock_account_email
292
- create_email(unlock_account_email_subject, unlock_account_email_body)
293
- end
294
-
295
- def unlock_account_email_body
296
- render('unlock-account-email')
280
+ return_response unlock_account_request_view
297
281
  end
298
282
 
299
283
  def unlock_account_email_recently_sent?
@@ -303,7 +303,7 @@ module Rodauth
303
303
  end
304
304
 
305
305
  def otp_qr_code
306
- RQRCode::QRCode.new(otp_provisioning_uri).as_svg(:module_size=>8, :viewbox=>true)
306
+ RQRCode::QRCode.new(otp_provisioning_uri).as_svg(:module_size=>8, :viewbox=>true, :use_path=>true)
307
307
  end
308
308
 
309
309
  def otp_user_key
@@ -144,7 +144,7 @@ module Rodauth
144
144
  opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
145
145
  opts[:expires] = convert_timestamp(active_remember_key_ds.get(remember_deadline_column))
146
146
  opts[:path] = "/" unless opts.key?(:path)
147
- opts[:httponly] = true unless opts.key?(:httponly)
147
+ opts[:httponly] = true unless opts.key?(:httponly) || opts.key?(:http_only)
148
148
  opts[:secure] = true unless opts.key?(:secure) || !request.ssl?
149
149
  ::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
150
150
  end
@@ -24,10 +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
+ email :reset_password, 'Reset Password'
27
28
 
28
29
  auth_value_method :reset_password_deadline_column, :deadline
29
30
  auth_value_method :reset_password_deadline_interval, {:days=>1}.freeze
30
- translatable_method :reset_password_email_subject, 'Reset Password'
31
31
  auth_value_method :reset_password_key_param, 'key'
32
32
  auth_value_method :reset_password_autologin?, false
33
33
  auth_value_method :reset_password_table, :account_password_reset_keys
@@ -41,16 +41,13 @@ module Rodauth
41
41
 
42
42
  auth_methods(
43
43
  :create_reset_password_key,
44
- :create_reset_password_email,
45
44
  :get_reset_password_key,
46
45
  :get_reset_password_email_last_sent,
47
46
  :login_failed_reset_password_request_form,
48
47
  :remove_reset_password_key,
49
- :reset_password_email_body,
50
48
  :reset_password_email_link,
51
49
  :reset_password_key_insert_hash,
52
50
  :reset_password_key_value,
53
- :send_reset_password_email,
54
51
  :set_reset_password_email_last_sent
55
52
  )
56
53
  auth_private_methods(
@@ -187,10 +184,6 @@ module Rodauth
187
184
  @account = _account_from_reset_password_key(key)
188
185
  end
189
186
 
190
- def send_reset_password_email
191
- send_email(create_reset_password_email)
192
- end
193
-
194
187
  def reset_password_email_link
195
188
  token_link(reset_password_route, reset_password_key_param, reset_password_key_value)
196
189
  end
@@ -241,18 +234,10 @@ module Rodauth
241
234
  @reset_password_key_value = random_key
242
235
  end
243
236
 
244
- def create_reset_password_email
245
- create_email(reset_password_email_subject, reset_password_email_body)
246
- end
247
-
248
237
  def login_failed_reset_password_request_form
249
238
  render("reset-password-request")
250
239
  end
251
240
 
252
- def reset_password_email_body
253
- render('reset-password-email')
254
- end
255
-
256
241
  def use_date_arithmetic?
257
242
  super || db.database_type == :mysql
258
243
  end
@@ -0,0 +1,16 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:reset_password_notify, :ResetPasswordNotify) do
5
+ depends :reset_password
6
+ loaded_templates %w'reset-password-notify-email'
7
+ email :reset_password_notify, 'Password Reset Completed', :translatable=>true
8
+
9
+ private
10
+
11
+ def after_reset_password
12
+ super
13
+ send_reset_password_notify_email
14
+ end
15
+ end
16
+ end
@@ -468,7 +468,7 @@ module Rodauth
468
468
  end
469
469
 
470
470
  def _two_factor_remove_all_from_session
471
- two_factor_remove_session('sms_codes')
471
+ two_factor_remove_session('sms_code')
472
472
  super
473
473
  end
474
474
 
@@ -26,8 +26,8 @@ module Rodauth
26
26
  redirect
27
27
  redirect(:verify_account_email_sent){default_post_email_redirect}
28
28
  redirect(:verify_account_email_recently_sent){default_post_email_redirect}
29
+ email :verify_account, 'Verify Account'
29
30
 
30
- translatable_method :verify_account_email_subject, 'Verify Account'
31
31
  auth_value_method :verify_account_key_param, 'key'
32
32
  auth_value_method :verify_account_autologin?, true
33
33
  auth_value_method :verify_account_table, :account_verification_keys
@@ -43,14 +43,11 @@ module Rodauth
43
43
  auth_methods(
44
44
  :allow_resending_verify_account_email?,
45
45
  :create_verify_account_key,
46
- :create_verify_account_email,
47
46
  :get_verify_account_key,
48
47
  :get_verify_account_email_last_sent,
49
48
  :remove_verify_account_key,
50
- :send_verify_account_email,
51
49
  :set_verify_account_email_last_sent,
52
50
  :verify_account,
53
- :verify_account_email_body,
54
51
  :verify_account_email_link,
55
52
  :verify_account_key_insert_hash,
56
53
  :verify_account_key_value
@@ -198,8 +195,7 @@ module Rodauth
198
195
  if account_from_login(login) && allow_resending_verify_account_email?
199
196
  set_response_error_reason_status(:already_an_unverified_account_with_this_login, unopen_account_error_status)
200
197
  set_error_flash attempt_to_create_unverified_account_error_flash
201
- response.write resend_verify_account_view
202
- request.halt
198
+ return_response resend_verify_account_view
203
199
  end
204
200
  super
205
201
  end
@@ -212,10 +208,6 @@ module Rodauth
212
208
  account_unverified_status_value
213
209
  end
214
210
 
215
- def send_verify_account_email
216
- send_email(create_verify_account_email)
217
- end
218
-
219
211
  def verify_account_email_link
220
212
  token_link(verify_account_route, verify_account_key_param, verify_account_key_value)
221
213
  end
@@ -275,8 +267,7 @@ module Rodauth
275
267
  unless open_account?
276
268
  set_response_error_reason_status(:unverified_account, unopen_account_error_status)
277
269
  set_error_flash attempt_to_login_to_unverified_account_error_flash
278
- response.write resend_verify_account_view
279
- request.halt
270
+ return_response resend_verify_account_view
280
271
  end
281
272
  super
282
273
  end
@@ -311,14 +302,6 @@ module Rodauth
311
302
  {verify_account_id_column=>account_id, verify_account_key_column=>verify_account_key_value}
312
303
  end
313
304
 
314
- def create_verify_account_email
315
- create_email(verify_account_email_subject, verify_account_email_body)
316
- end
317
-
318
- def verify_account_email_body
319
- render('verify-account-email')
320
- end
321
-
322
305
  def verify_account_ds(id=account_id)
323
306
  db[verify_account_table].where(verify_account_id_column=>id)
324
307
  end
@@ -30,10 +30,17 @@ module Rodauth
30
30
  false
31
31
  end
32
32
 
33
+ def require_login
34
+ if unverified_grace_period_expired?
35
+ clear_session
36
+ end
37
+ super
38
+ end
39
+
33
40
  def update_session
34
41
  super
35
42
  if account_in_unverified_grace_period?
36
- set_session_value(unverified_account_session_key, true)
43
+ set_session_value(unverified_account_session_key, Time.now.to_i + verify_account_grace_period)
37
44
  end
38
45
  end
39
46
 
@@ -78,6 +85,11 @@ module Rodauth
78
85
  !verify_account_ds.where(Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP).empty?
79
86
  end
80
87
 
88
+ def unverified_grace_period_expired?
89
+ return false unless expires_at = session[unverified_account_session_key]
90
+ expires_at.is_a?(Integer) && Time.now.to_i > expires_at
91
+ end
92
+
81
93
  def use_date_arithmetic?
82
94
  true
83
95
  end
@@ -6,7 +6,7 @@ module Rodauth
6
6
  MAJOR = 2
7
7
 
8
8
  # The minor version of Rodauth, updated for new feature releases of Rodauth.
9
- MINOR = 20
9
+ MINOR = 23
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
data/lib/rodauth.rb CHANGED
@@ -233,6 +233,33 @@ module Rodauth
233
233
  end
234
234
  end
235
235
 
236
+ def email(type, subject, opts = {})
237
+ subject_method = :"#{type}_email_subject"
238
+ body_method = :"#{type}_email_body"
239
+ create_method = :"create_#{type}_email"
240
+ send_method = :"send_#{type}_email"
241
+
242
+ translatable_method subject_method, subject
243
+ auth_methods create_method, send_method
244
+
245
+ body_template = "#{type.to_s.tr('_', '-')}-email"
246
+ if opts[:translatable]
247
+ auth_value_methods body_method
248
+ define_method(body_method){translate(body_method, render(body_template))}
249
+ else
250
+ auth_methods body_method
251
+ define_method(body_method){render(body_template)}
252
+ end
253
+
254
+ define_method(create_method) do
255
+ create_email(send(subject_method), send(body_method))
256
+ end
257
+
258
+ define_method(send_method) do
259
+ send_email(send(create_method))
260
+ end
261
+ end
262
+
236
263
  def additional_form_tags(name=feature_name)
237
264
  auth_value_method(:"#{name}_additional_form_tags", nil)
238
265
  end
@@ -0,0 +1,2 @@
1
+ Someone (hopefully you) has reset the password for the account
2
+ associated to this email address.
@@ -4,6 +4,7 @@
4
4
  #{rodauth.render('password-field') if rodauth.two_factor_modifications_require_password?}
5
5
  <fieldset class="form-group mb-3">
6
6
  #{(usage = rodauth.account_webauthn_usage; last_id = usage.keys.last; usage;).map do |id, last_use|
7
+ last_use = last_use.strftime("%F %T") if last_use.is_a?(Time)
7
8
  input = rodauth.input_field_string(rodauth.webauthn_remove_param, "webauthn-remove-#{h id}", :type=>'radio', :class=>"form-check-input", :skip_error_message=>true, :value=>id, :required=>false)
8
9
  label = "<label class=\"rodauth-webauthn-id form-check-label\" for=\"webauthn-remove-#{h id}\">Last Use: #{last_use}</label>"
9
10
  error = rodauth.formatted_field_error(rodauth.webauthn_remove_param) if id == last_id