rodauth 1.23.0 → 2.4.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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +184 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +221 -79
  5. data/doc/account_expiration.rdoc +12 -26
  6. data/doc/active_sessions.rdoc +49 -0
  7. data/doc/audit_logging.rdoc +44 -0
  8. data/doc/base.rdoc +76 -128
  9. data/doc/change_login.rdoc +7 -14
  10. data/doc/change_password.rdoc +9 -13
  11. data/doc/change_password_notify.rdoc +2 -2
  12. data/doc/close_account.rdoc +9 -16
  13. data/doc/confirm_password.rdoc +12 -5
  14. data/doc/create_account.rdoc +11 -22
  15. data/doc/disallow_password_reuse.rdoc +6 -13
  16. data/doc/email_auth.rdoc +15 -14
  17. data/doc/email_base.rdoc +5 -15
  18. data/doc/guides/admin_activation.rdoc +46 -0
  19. data/doc/guides/already_authenticated.rdoc +10 -0
  20. data/doc/guides/alternative_login.rdoc +46 -0
  21. data/doc/guides/create_account_programmatically.rdoc +38 -0
  22. data/doc/guides/delay_password.rdoc +25 -0
  23. data/doc/guides/email_only.rdoc +16 -0
  24. data/doc/guides/i18n.rdoc +26 -0
  25. data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
  26. data/doc/guides/links.rdoc +12 -0
  27. data/doc/guides/login_return.rdoc +37 -0
  28. data/doc/guides/password_column.rdoc +25 -0
  29. data/doc/guides/password_confirmation.rdoc +37 -0
  30. data/doc/guides/password_requirements.rdoc +30 -0
  31. data/doc/guides/paths.rdoc +36 -0
  32. data/doc/guides/query_params.rdoc +9 -0
  33. data/doc/guides/redirects.rdoc +17 -0
  34. data/doc/guides/registration_field.rdoc +68 -0
  35. data/doc/guides/require_mfa.rdoc +30 -0
  36. data/doc/guides/reset_password_autologin.rdoc +21 -0
  37. data/doc/guides/status_column.rdoc +28 -0
  38. data/doc/guides/totp_or_recovery.rdoc +16 -0
  39. data/doc/http_basic_auth.rdoc +10 -1
  40. data/doc/jwt.rdoc +22 -22
  41. data/doc/jwt_cors.rdoc +2 -3
  42. data/doc/jwt_refresh.rdoc +23 -8
  43. data/doc/lockout.rdoc +17 -15
  44. data/doc/login.rdoc +17 -2
  45. data/doc/login_password_requirements_base.rdoc +18 -37
  46. data/doc/logout.rdoc +2 -2
  47. data/doc/otp.rdoc +25 -19
  48. data/doc/password_complexity.rdoc +10 -26
  49. data/doc/password_expiration.rdoc +11 -25
  50. data/doc/password_grace_period.rdoc +16 -2
  51. data/doc/password_pepper.rdoc +44 -0
  52. data/doc/recovery_codes.rdoc +18 -12
  53. data/doc/release_notes/2.0.0.txt +361 -0
  54. data/doc/release_notes/2.1.0.txt +31 -0
  55. data/doc/release_notes/2.2.0.txt +39 -0
  56. data/doc/release_notes/2.3.0.txt +37 -0
  57. data/doc/release_notes/2.4.0.txt +22 -0
  58. data/doc/remember.rdoc +40 -64
  59. data/doc/reset_password.rdoc +12 -9
  60. data/doc/session_expiration.rdoc +1 -0
  61. data/doc/single_session.rdoc +16 -25
  62. data/doc/sms_codes.rdoc +24 -14
  63. data/doc/two_factor_base.rdoc +60 -22
  64. data/doc/verify_account.rdoc +14 -12
  65. data/doc/verify_account_grace_period.rdoc +6 -2
  66. data/doc/verify_login_change.rdoc +9 -8
  67. data/doc/webauthn.rdoc +115 -0
  68. data/doc/webauthn_login.rdoc +15 -0
  69. data/doc/webauthn_verify_account.rdoc +9 -0
  70. data/javascript/webauthn_auth.js +45 -0
  71. data/javascript/webauthn_setup.js +35 -0
  72. data/lib/roda/plugins/rodauth.rb +1 -1
  73. data/lib/rodauth.rb +33 -28
  74. data/lib/rodauth/features/account_expiration.rb +5 -5
  75. data/lib/rodauth/features/active_sessions.rb +158 -0
  76. data/lib/rodauth/features/audit_logging.rb +98 -0
  77. data/lib/rodauth/features/base.rb +152 -49
  78. data/lib/rodauth/features/change_password_notify.rb +1 -1
  79. data/lib/rodauth/features/close_account.rb +8 -6
  80. data/lib/rodauth/features/confirm_password.rb +40 -2
  81. data/lib/rodauth/features/create_account.rb +8 -13
  82. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  83. data/lib/rodauth/features/disallow_password_reuse.rb +5 -3
  84. data/lib/rodauth/features/email_auth.rb +30 -28
  85. data/lib/rodauth/features/email_base.rb +3 -3
  86. data/lib/rodauth/features/http_basic_auth.rb +55 -35
  87. data/lib/rodauth/features/jwt.rb +63 -16
  88. data/lib/rodauth/features/jwt_cors.rb +15 -15
  89. data/lib/rodauth/features/jwt_refresh.rb +42 -13
  90. data/lib/rodauth/features/lockout.rb +11 -13
  91. data/lib/rodauth/features/login.rb +58 -13
  92. data/lib/rodauth/features/login_password_requirements_base.rb +13 -8
  93. data/lib/rodauth/features/otp.rb +76 -82
  94. data/lib/rodauth/features/password_complexity.rb +8 -13
  95. data/lib/rodauth/features/password_expiration.rb +1 -1
  96. data/lib/rodauth/features/password_grace_period.rb +17 -10
  97. data/lib/rodauth/features/password_pepper.rb +45 -0
  98. data/lib/rodauth/features/recovery_codes.rb +47 -51
  99. data/lib/rodauth/features/remember.rb +13 -27
  100. data/lib/rodauth/features/reset_password.rb +25 -25
  101. data/lib/rodauth/features/session_expiration.rb +7 -10
  102. data/lib/rodauth/features/single_session.rb +8 -6
  103. data/lib/rodauth/features/sms_codes.rb +58 -68
  104. data/lib/rodauth/features/two_factor_base.rb +134 -30
  105. data/lib/rodauth/features/verify_account.rb +28 -20
  106. data/lib/rodauth/features/verify_account_grace_period.rb +18 -9
  107. data/lib/rodauth/features/verify_login_change.rb +11 -10
  108. data/lib/rodauth/features/webauthn.rb +505 -0
  109. data/lib/rodauth/features/webauthn_login.rb +70 -0
  110. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  111. data/lib/rodauth/migrations.rb +16 -5
  112. data/lib/rodauth/version.rb +2 -2
  113. data/templates/button.str +1 -3
  114. data/templates/change-login.str +1 -2
  115. data/templates/change-password.str +3 -5
  116. data/templates/close-account.str +2 -2
  117. data/templates/confirm-password.str +1 -1
  118. data/templates/create-account.str +1 -1
  119. data/templates/email-auth-request-form.str +1 -2
  120. data/templates/email-auth.str +1 -1
  121. data/templates/global-logout-field.str +6 -0
  122. data/templates/login-confirm-field.str +2 -4
  123. data/templates/login-display.str +3 -2
  124. data/templates/login-field.str +2 -4
  125. data/templates/login-form-footer.str +6 -0
  126. data/templates/login-form.str +7 -0
  127. data/templates/login.str +1 -9
  128. data/templates/logout.str +1 -1
  129. data/templates/multi-phase-login.str +3 -0
  130. data/templates/otp-auth-code-field.str +5 -3
  131. data/templates/otp-auth.str +1 -1
  132. data/templates/otp-disable.str +1 -1
  133. data/templates/otp-setup.str +3 -3
  134. data/templates/password-confirm-field.str +2 -4
  135. data/templates/password-field.str +2 -4
  136. data/templates/recovery-auth.str +3 -6
  137. data/templates/recovery-codes.str +1 -1
  138. data/templates/remember.str +15 -20
  139. data/templates/reset-password-request.str +2 -2
  140. data/templates/reset-password.str +1 -2
  141. data/templates/sms-auth.str +1 -1
  142. data/templates/sms-code-field.str +5 -3
  143. data/templates/sms-confirm.str +1 -2
  144. data/templates/sms-disable.str +1 -2
  145. data/templates/sms-request.str +1 -1
  146. data/templates/sms-setup.str +6 -4
  147. data/templates/two-factor-auth.str +5 -0
  148. data/templates/two-factor-disable.str +6 -0
  149. data/templates/two-factor-manage.str +16 -0
  150. data/templates/unlock-account-request.str +2 -2
  151. data/templates/unlock-account.str +1 -1
  152. data/templates/verify-account-resend.str +1 -1
  153. data/templates/verify-account.str +1 -2
  154. data/templates/verify-login-change.str +1 -1
  155. data/templates/webauthn-auth.str +11 -0
  156. data/templates/webauthn-remove.str +14 -0
  157. data/templates/webauthn-setup.str +12 -0
  158. metadata +96 -13
  159. data/doc/verify_change_login.rdoc +0 -11
  160. data/lib/rodauth/features/verify_change_login.rb +0 -20
@@ -2,18 +2,20 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:login_password_requirements_base, :LoginPasswordRequirementsBase) do
5
- auth_value_method :already_an_account_with_this_login_message, 'already an account with this login'
5
+ translatable_method :already_an_account_with_this_login_message, 'already an account with this login'
6
6
  auth_value_method :login_confirm_param, 'login-confirm'
7
+ auth_value_method :login_email_regexp, /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
7
8
  auth_value_method :login_minimum_length, 3
8
9
  auth_value_method :login_maximum_length, 255
9
- auth_value_method :logins_do_not_match_message, 'logins do not match'
10
+ translatable_method :login_not_valid_email_message, 'not a valid email address'
11
+ translatable_method :logins_do_not_match_message, 'logins do not match'
10
12
  auth_value_method :password_confirm_param, 'password-confirm'
11
13
  auth_value_method :password_minimum_length, 6
12
- auth_value_method :passwords_do_not_match_message, 'passwords do not match'
14
+ translatable_method :passwords_do_not_match_message, 'passwords do not match'
13
15
  auth_value_method :require_email_address_logins?, true
14
16
  auth_value_method :require_login_confirmation?, true
15
17
  auth_value_method :require_password_confirmation?, true
16
- auth_value_method :same_as_existing_password_message, "invalid password, same as current password"
18
+ translatable_method :same_as_existing_password_message, "invalid password, same as current password"
17
19
 
18
20
  auth_value_methods(
19
21
  :login_confirm_label,
@@ -28,6 +30,7 @@ module Rodauth
28
30
 
29
31
  auth_methods(
30
32
  :login_meets_requirements?,
33
+ :login_valid_email?,
31
34
  :password_hash,
32
35
  :password_meets_requirements?,
33
36
  :set_password
@@ -104,13 +107,15 @@ module Rodauth
104
107
 
105
108
  def login_meets_email_requirements?(login)
106
109
  return true unless require_email_address_logins?
107
- if login =~ /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
108
- return true
109
- end
110
- @login_requirement_message = 'not a valid email address'
110
+ return true if login_valid_email?(login)
111
+ @login_requirement_message = login_not_valid_email_message
111
112
  return false
112
113
  end
113
114
 
115
+ def login_valid_email?(login)
116
+ login =~ login_email_regexp
117
+ end
118
+
114
119
  def password_meets_length_requirements?(password)
115
120
  return true if password_minimum_length <= password.length
116
121
  @password_requirement_message = password_too_short_message
@@ -19,62 +19,59 @@ module Rodauth
19
19
  before 'otp_setup'
20
20
  before 'otp_disable'
21
21
 
22
- configuration_module_eval do
23
- def before_otp_authentication_route(&block)
24
- warn "before_otp_authentication_route is deprecated, switch to before_otp_auth_route"
25
- before_otp_auth_route(&block)
26
- end
27
- end
28
-
29
- button 'Authenticate via 2nd Factor', 'otp_auth'
30
- button 'Disable Two Factor Authentication', 'otp_disable'
31
- button 'Setup Two Factor Authentication', 'otp_setup'
22
+ button 'Authenticate Using TOTP', 'otp_auth'
23
+ button 'Disable TOTP Authentication', 'otp_disable'
24
+ button 'Setup TOTP Authentication', 'otp_setup'
32
25
 
33
- error_flash "Error disabling up two factor authentication", 'otp_disable'
34
- error_flash "Error logging in via two factor authentication", 'otp_auth'
35
- error_flash "Error setting up two factor authentication", 'otp_setup'
36
- error_flash "You have already setup two factor authentication", :otp_already_setup
26
+ error_flash "Error disabling TOTP authentication", 'otp_disable'
27
+ error_flash "Error logging in via TOTP authentication", 'otp_auth'
28
+ error_flash "Error setting up TOTP authentication", 'otp_setup'
29
+ error_flash "You have already setup TOTP authentication", 'otp_already_setup'
30
+ error_flash "TOTP authentication code use locked out due to numerous failures", 'otp_lockout'
37
31
 
38
- notice_flash "Two factor authentication has been disabled", 'otp_disable'
39
- notice_flash "Two factor authentication is now setup", 'otp_setup'
32
+ notice_flash "TOTP authentication has been disabled", 'otp_disable'
33
+ notice_flash "TOTP authentication is now setup", 'otp_setup'
40
34
 
41
35
  redirect :otp_disable
42
36
  redirect :otp_already_setup
43
37
  redirect :otp_setup
38
+ redirect(:otp_lockout){two_factor_auth_required_redirect}
44
39
 
45
40
  loaded_templates %w'otp-disable otp-auth otp-setup otp-auth-code-field password-field'
46
- view 'otp-disable', 'Disable Two Factor Authentication', 'otp_disable'
41
+ view 'otp-disable', 'Disable TOTP Authentication', 'otp_disable'
47
42
  view 'otp-auth', 'Enter Authentication Code', 'otp_auth'
48
- view 'otp-setup', 'Setup Two Factor Authentication', 'otp_setup'
43
+ view 'otp-setup', 'Setup TOTP Authentication', 'otp_setup'
44
+
45
+ translatable_method :otp_auth_link_text, "Authenticate Using TOTP"
46
+ translatable_method :otp_setup_link_text, "Setup TOTP Authentication"
47
+ translatable_method :otp_disable_link_text, "Disable TOTP Authentication"
49
48
 
50
49
  auth_value_method :otp_auth_failures_limit, 5
51
- auth_value_method :otp_auth_label, 'Authentication Code'
50
+ translatable_method :otp_auth_label, 'Authentication Code'
52
51
  auth_value_method :otp_auth_param, 'otp'
53
52
  auth_value_method :otp_class, ROTP::TOTP
54
53
  auth_value_method :otp_digits, nil
55
- auth_value_method :otp_drift, nil
54
+ auth_value_method :otp_drift, 30
56
55
  auth_value_method :otp_interval, nil
57
- auth_value_method :otp_invalid_auth_code_message, "Invalid authentication code"
58
- auth_value_method :otp_invalid_secret_message, "invalid secret"
56
+ translatable_method :otp_invalid_auth_code_message, "Invalid authentication code"
57
+ translatable_method :otp_invalid_secret_message, "invalid secret"
59
58
  auth_value_method :otp_keys_column, :key
60
59
  auth_value_method :otp_keys_id_column, :id
61
60
  auth_value_method :otp_keys_failures_column, :num_failures
62
61
  auth_value_method :otp_keys_table, :account_otp_keys
63
62
  auth_value_method :otp_keys_last_use_column, :last_use
64
- auth_value_method :otp_provisioning_uri_label, 'Provisioning URL'
65
- auth_value_method :otp_secret_label, 'Secret'
63
+ translatable_method :otp_provisioning_uri_label, 'Provisioning URL'
64
+ translatable_method :otp_secret_label, 'Secret'
66
65
  auth_value_method :otp_setup_param, 'otp_secret'
67
66
  auth_value_method :otp_setup_raw_param, 'otp_raw_secret'
67
+ translatable_method :otp_auth_form_footer, ''
68
68
 
69
69
  auth_cached_method :otp_key
70
70
  auth_cached_method :otp
71
71
  private :otp
72
72
 
73
73
  auth_value_methods(
74
- :otp_auth_form_footer,
75
74
  :otp_issuer,
76
- :otp_lockout_error_flash,
77
- :otp_lockout_redirect,
78
75
  :otp_keys_use_hmac?
79
76
  )
80
77
 
@@ -82,6 +79,7 @@ module Rodauth
82
79
  :otp,
83
80
  :otp_exists?,
84
81
  :otp_key,
82
+ :otp_last_use,
85
83
  :otp_locked_out?,
86
84
  :otp_new_secret,
87
85
  :otp_provisioning_name,
@@ -103,18 +101,13 @@ module Rodauth
103
101
  route(:otp_auth) do |r|
104
102
  require_login
105
103
  require_account_session
106
- require_two_factor_not_authenticated
104
+ require_two_factor_not_authenticated('totp')
107
105
  require_otp_setup
108
106
 
109
107
  if otp_locked_out?
110
108
  set_response_error_status(lockout_error_status)
111
109
  set_redirect_error_flash otp_lockout_error_flash
112
- if redir = otp_lockout_redirect
113
- redirect redir
114
- else
115
- clear_session
116
- redirect require_login_redirect
117
- end
110
+ redirect otp_lockout_redirect
118
111
  end
119
112
 
120
113
  before_otp_auth_route
@@ -126,7 +119,7 @@ module Rodauth
126
119
  r.post do
127
120
  if otp_valid_code?(param(otp_auth_param)) && otp_update_last_use
128
121
  before_otp_authentication
129
- two_factor_authenticate(:totp)
122
+ two_factor_authenticate('totp')
130
123
  end
131
124
 
132
125
  otp_record_authentication_failure
@@ -178,7 +171,9 @@ module Rodauth
178
171
  transaction do
179
172
  before_otp_setup
180
173
  otp_add_key
181
- two_factor_update_session(:totp)
174
+ unless two_factor_authenticated?
175
+ two_factor_update_session('totp')
176
+ end
182
177
  after_otp_setup
183
178
  end
184
179
  set_notice_flash otp_setup_notice_flash
@@ -204,7 +199,9 @@ module Rodauth
204
199
  transaction do
205
200
  before_otp_disable
206
201
  otp_remove
207
- two_factor_remove_session
202
+ if two_factor_login_type_match?('totp')
203
+ two_factor_remove_session('totp')
204
+ end
208
205
  after_otp_disable
209
206
  end
210
207
  set_notice_flash otp_disable_notice_flash
@@ -218,20 +215,6 @@ module Rodauth
218
215
  end
219
216
  end
220
217
 
221
- def two_factor_authentication_setup?
222
- return true if super
223
- return false if @otp_tmp_key
224
- otp_exists?
225
- end
226
-
227
- def two_factor_need_setup_redirect
228
- otp_setup_path
229
- end
230
-
231
- def two_factor_auth_required_redirect
232
- otp_auth_path
233
- end
234
-
235
218
  def two_factor_remove
236
219
  super
237
220
  otp_remove
@@ -242,19 +225,6 @@ module Rodauth
242
225
  otp_remove_auth_failures
243
226
  end
244
227
 
245
- def otp_auth_form_footer
246
- super if defined?(super)
247
- end
248
-
249
- def otp_lockout_redirect
250
- return super if defined?(super)
251
- nil
252
- end
253
-
254
- def otp_lockout_error_flash
255
- "Authentication code use locked out due to numerous failures.#{super if defined?(super)}"
256
- end
257
-
258
228
  def require_otp_setup
259
229
  unless otp_exists?
260
230
  set_redirect_error_status(two_factor_not_setup_error_status)
@@ -272,11 +242,11 @@ module Rodauth
272
242
  ot_pass = ot_pass.gsub(/\s+/, '')
273
243
  if drift = otp_drift
274
244
  if otp.respond_to?(:verify_with_drift)
245
+ # :nocov:
275
246
  otp.verify_with_drift(ot_pass, drift)
276
- else
277
247
  # :nocov:
248
+ else
278
249
  otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
279
- # :nocov:
280
250
  end
281
251
  else
282
252
  otp.verify(ot_pass)
@@ -285,7 +255,7 @@ module Rodauth
285
255
 
286
256
  def otp_remove
287
257
  otp_key_ds.delete
288
- super if defined?(super)
258
+ @otp_key = nil
289
259
  end
290
260
 
291
261
  def otp_add_key
@@ -299,6 +269,10 @@ module Rodauth
299
269
  update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
300
270
  end
301
271
 
272
+ def otp_last_use
273
+ convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
274
+ end
275
+
302
276
  def otp_record_authentication_failure
303
277
  otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
304
278
  end
@@ -316,7 +290,7 @@ module Rodauth
316
290
  end
317
291
 
318
292
  def otp_issuer
319
- request.host
293
+ domain
320
294
  end
321
295
 
322
296
  def otp_provisioning_name
@@ -339,8 +313,37 @@ module Rodauth
339
313
  !!hmac_secret
340
314
  end
341
315
 
316
+ def possible_authentication_methods
317
+ methods = super
318
+ methods << 'totp' if otp_exists? && !@otp_tmp_key
319
+ methods
320
+ end
321
+
342
322
  private
343
323
 
324
+ def _two_factor_auth_links
325
+ links = super
326
+ links << [20, otp_auth_path, otp_auth_link_text] if otp_exists? && !otp_locked_out?
327
+ links
328
+ end
329
+
330
+ def _two_factor_setup_links
331
+ links = super
332
+ links << [20, otp_setup_path, otp_setup_link_text] unless otp_exists?
333
+ links
334
+ end
335
+
336
+ def _two_factor_remove_links
337
+ links = super
338
+ links << [20, otp_disable_path, otp_disable_link_text] if otp_exists?
339
+ links
340
+ end
341
+
342
+ def _two_factor_remove_all_from_session
343
+ two_factor_remove_session('totp')
344
+ super
345
+ end
346
+
344
347
  def clear_cached_otp
345
348
  remove_instance_variable(:@otp) if defined?(@otp)
346
349
  end
@@ -364,29 +367,20 @@ module Rodauth
364
367
  end
365
368
 
366
369
  if ROTP::Base32.respond_to?(:random_base32)
367
- # :nocov:
368
370
  def otp_new_secret
369
371
  ROTP::Base32.random_base32.downcase
370
372
  end
371
- # :nocov:
372
373
  else
374
+ # :nocov:
373
375
  def otp_new_secret
374
376
  ROTP::Base32.random.downcase
375
377
  end
378
+ # :nocov:
376
379
  end
377
380
 
378
- if RUBY_VERSION < '1.9'
379
- # :nocov:
380
- def base32_encode(data, length)
381
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
382
- length.times.map{|i|chars[data[i] % 32].chr}.join
383
- end
384
- # :nocov:
385
- else
386
- def base32_encode(data, length)
387
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
388
- length.times.map{|i|chars[data[i].ord % 32]}.join
389
- end
381
+ def base32_encode(data, length)
382
+ chars = 'abcdefghijklmnopqrstuvwxyz234567'
383
+ length.times.map{|i|chars[data[i].ord % 32]}.join
390
384
  end
391
385
 
392
386
  def _otp_tmp_key(secret)
@@ -11,13 +11,10 @@ module Rodauth
11
11
  auth_value_method :password_max_length_for_groups_check, 11
12
12
  auth_value_method :password_max_repeating_characters, 3
13
13
  auth_value_method :password_invalid_pattern, Regexp.union([/qwerty/i, /azerty/i, /asdf/i, /zxcv/i] + (1..8).map{|i| /#{i}#{i+1}#{(i+2)%10}/})
14
- auth_value_method :password_not_enough_character_groups_message, "does not include uppercase letters, lowercase letters, and numbers"
15
- auth_value_method :password_invalid_pattern_message, "includes common character sequence"
16
- auth_value_method :password_in_dictionary_message, "is a word in a dictionary"
17
-
18
- auth_value_methods(
19
- :password_too_many_repeating_characters_message
20
- )
14
+ translatable_method :password_not_enough_character_groups_message, "does not include uppercase letters, lowercase letters, and numbers"
15
+ translatable_method :password_invalid_pattern_message, "includes common character sequence"
16
+ translatable_method :password_in_dictionary_message, "is a word in a dictionary"
17
+ translatable_method :password_too_many_repeating_characters_message, "contains too many of the same character in a row"
21
18
 
22
19
  def password_meets_requirements?(password)
23
20
  super && \
@@ -29,14 +26,16 @@ module Rodauth
29
26
 
30
27
  def post_configure
31
28
  super
32
- return if singleton_methods.map(&:to_sym).include?(:password_dictionary)
29
+ return if method(:password_dictionary).owner != Rodauth::PasswordComplexity
33
30
 
34
31
  case password_dictionary_file
35
32
  when false
36
- return
33
+ # nothing
37
34
  when nil
38
35
  default_dictionary_file = '/usr/share/dict/words'
36
+ # :nocov:
39
37
  if File.file?(default_dictionary_file)
38
+ # :nocov:
40
39
  words = File.read(default_dictionary_file)
41
40
  end
42
41
  else
@@ -73,10 +72,6 @@ module Rodauth
73
72
  false
74
73
  end
75
74
 
76
- def password_too_many_repeating_characters_message
77
- "contains #{password_max_repeating_characters} or more of the same character in a row"
78
- end
79
-
80
75
  def password_not_in_dictionary?(password)
81
76
  return true unless dict = password_dictionary
82
77
  return true unless password =~ /\A(?:\d*)([A-Za-z!@$+|][A-Za-z!@$+|0134578]+[A-Za-z!@$+|])(?:\d*)\z/
@@ -38,7 +38,7 @@ module Rodauth
38
38
 
39
39
  def set_password(password)
40
40
  update_password_changed_at
41
- session[password_changed_at_session_key] = Time.now.to_i
41
+ set_session_value(password_changed_at_session_key, Time.now.to_i)
42
42
  super
43
43
  end
44
44
 
@@ -5,6 +5,8 @@ module Rodauth
5
5
  auth_value_method :password_grace_period, 300
6
6
  session_key :last_password_entry_session_key, :last_password_entry
7
7
 
8
+ auth_methods :password_recently_entered?
9
+
8
10
  def modifications_require_password?
9
11
  return false unless super
10
12
  !password_recently_entered?
@@ -17,6 +19,16 @@ module Rodauth
17
19
  v
18
20
  end
19
21
 
22
+ def password_recently_entered?
23
+ return false unless last_password_entry = session[last_password_entry_session_key]
24
+ last_password_entry + password_grace_period > Time.now.to_i
25
+ end
26
+
27
+ def update_session
28
+ super
29
+ set_session_value(last_password_entry_session_key, @last_password_entry) if defined?(@last_password_entry)
30
+ end
31
+
20
32
  private
21
33
 
22
34
  def after_create_account
@@ -29,18 +41,13 @@ module Rodauth
29
41
  @last_password_entry = Time.now.to_i
30
42
  end
31
43
 
32
- def update_session
33
- super
34
- session[last_password_entry_session_key] = @last_password_entry if defined?(@last_password_entry)
35
- end
36
-
37
- def password_recently_entered?
38
- return false unless last_password_entry = session[last_password_entry_session_key]
39
- last_password_entry + password_grace_period > Time.now.to_i
44
+ def set_last_password_entry
45
+ set_session_value(last_password_entry_session_key, Time.now.to_i)
40
46
  end
41
47
 
42
- def set_last_password_entry
43
- session[last_password_entry_session_key] = Time.now.to_i
48
+ def require_password_authentication?
49
+ return true if defined?(super) && super
50
+ !password_recently_entered?
44
51
  end
45
52
  end
46
53
  end