rodauth 1.22.0 → 2.3.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +190 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +210 -80
  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 +75 -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 +6 -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/recovery_codes.rdoc +18 -12
  52. data/doc/release_notes/1.23.0.txt +32 -0
  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/remember.rdoc +40 -64
  58. data/doc/reset_password.rdoc +12 -9
  59. data/doc/session_expiration.rdoc +1 -0
  60. data/doc/single_session.rdoc +16 -25
  61. data/doc/sms_codes.rdoc +24 -14
  62. data/doc/two_factor_base.rdoc +60 -22
  63. data/doc/verify_account.rdoc +14 -12
  64. data/doc/verify_account_grace_period.rdoc +6 -2
  65. data/doc/verify_login_change.rdoc +9 -8
  66. data/doc/webauthn.rdoc +115 -0
  67. data/doc/webauthn_login.rdoc +15 -0
  68. data/doc/webauthn_verify_account.rdoc +9 -0
  69. data/javascript/webauthn_auth.js +45 -0
  70. data/javascript/webauthn_setup.js +35 -0
  71. data/lib/roda/plugins/rodauth.rb +1 -1
  72. data/lib/rodauth.rb +36 -28
  73. data/lib/rodauth/features/account_expiration.rb +5 -5
  74. data/lib/rodauth/features/active_sessions.rb +158 -0
  75. data/lib/rodauth/features/audit_logging.rb +98 -0
  76. data/lib/rodauth/features/base.rb +144 -43
  77. data/lib/rodauth/features/change_password_notify.rb +2 -2
  78. data/lib/rodauth/features/close_account.rb +8 -6
  79. data/lib/rodauth/features/confirm_password.rb +40 -2
  80. data/lib/rodauth/features/create_account.rb +8 -13
  81. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  82. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  83. data/lib/rodauth/features/email_auth.rb +31 -30
  84. data/lib/rodauth/features/email_base.rb +9 -4
  85. data/lib/rodauth/features/http_basic_auth.rb +55 -35
  86. data/lib/rodauth/features/jwt.rb +63 -16
  87. data/lib/rodauth/features/jwt_cors.rb +15 -15
  88. data/lib/rodauth/features/jwt_refresh.rb +42 -13
  89. data/lib/rodauth/features/lockout.rb +12 -14
  90. data/lib/rodauth/features/login.rb +64 -15
  91. data/lib/rodauth/features/login_password_requirements_base.rb +13 -8
  92. data/lib/rodauth/features/otp.rb +77 -80
  93. data/lib/rodauth/features/password_complexity.rb +8 -13
  94. data/lib/rodauth/features/password_expiration.rb +2 -2
  95. data/lib/rodauth/features/password_grace_period.rb +17 -10
  96. data/lib/rodauth/features/recovery_codes.rb +49 -53
  97. data/lib/rodauth/features/remember.rb +11 -27
  98. data/lib/rodauth/features/reset_password.rb +26 -26
  99. data/lib/rodauth/features/session_expiration.rb +7 -10
  100. data/lib/rodauth/features/single_session.rb +8 -6
  101. data/lib/rodauth/features/sms_codes.rb +62 -72
  102. data/lib/rodauth/features/two_factor_base.rb +134 -30
  103. data/lib/rodauth/features/verify_account.rb +29 -21
  104. data/lib/rodauth/features/verify_account_grace_period.rb +18 -9
  105. data/lib/rodauth/features/verify_login_change.rb +12 -11
  106. data/lib/rodauth/features/webauthn.rb +505 -0
  107. data/lib/rodauth/features/webauthn_login.rb +70 -0
  108. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  109. data/lib/rodauth/migrations.rb +16 -5
  110. data/lib/rodauth/version.rb +2 -2
  111. data/templates/button.str +1 -3
  112. data/templates/change-login.str +1 -2
  113. data/templates/change-password.str +3 -5
  114. data/templates/close-account.str +2 -2
  115. data/templates/confirm-password.str +1 -1
  116. data/templates/create-account.str +1 -1
  117. data/templates/email-auth-request-form.str +2 -3
  118. data/templates/email-auth.str +1 -1
  119. data/templates/global-logout-field.str +6 -0
  120. data/templates/login-confirm-field.str +2 -4
  121. data/templates/login-display.str +3 -2
  122. data/templates/login-field.str +2 -4
  123. data/templates/login-form-footer.str +6 -0
  124. data/templates/login-form.str +7 -0
  125. data/templates/login.str +1 -9
  126. data/templates/logout.str +1 -1
  127. data/templates/multi-phase-login.str +3 -0
  128. data/templates/otp-auth-code-field.str +5 -3
  129. data/templates/otp-auth.str +1 -1
  130. data/templates/otp-disable.str +1 -1
  131. data/templates/otp-setup.str +3 -3
  132. data/templates/password-confirm-field.str +2 -4
  133. data/templates/password-field.str +2 -4
  134. data/templates/recovery-auth.str +3 -6
  135. data/templates/recovery-codes.str +1 -1
  136. data/templates/remember.str +15 -20
  137. data/templates/reset-password-request.str +3 -3
  138. data/templates/reset-password.str +1 -2
  139. data/templates/sms-auth.str +1 -1
  140. data/templates/sms-code-field.str +5 -3
  141. data/templates/sms-confirm.str +1 -2
  142. data/templates/sms-disable.str +1 -2
  143. data/templates/sms-request.str +1 -1
  144. data/templates/sms-setup.str +6 -4
  145. data/templates/two-factor-auth.str +5 -0
  146. data/templates/two-factor-disable.str +6 -0
  147. data/templates/two-factor-manage.str +16 -0
  148. data/templates/unlock-account-request.str +4 -4
  149. data/templates/unlock-account.str +1 -1
  150. data/templates/verify-account-resend.str +3 -3
  151. data/templates/verify-account.str +1 -2
  152. data/templates/verify-login-change.str +1 -1
  153. data/templates/webauthn-auth.str +11 -0
  154. data/templates/webauthn-remove.str +14 -0
  155. data/templates/webauthn-setup.str +12 -0
  156. metadata +94 -54
  157. data/Rakefile +0 -179
  158. data/doc/verify_change_login.rdoc +0 -11
  159. data/lib/rodauth/features/verify_change_login.rb +0 -20
  160. data/spec/account_expiration_spec.rb +0 -225
  161. data/spec/all.rb +0 -1
  162. data/spec/change_login_spec.rb +0 -156
  163. data/spec/change_password_notify_spec.rb +0 -33
  164. data/spec/change_password_spec.rb +0 -202
  165. data/spec/close_account_spec.rb +0 -162
  166. data/spec/confirm_password_spec.rb +0 -70
  167. data/spec/create_account_spec.rb +0 -127
  168. data/spec/disallow_common_passwords_spec.rb +0 -93
  169. data/spec/disallow_password_reuse_spec.rb +0 -179
  170. data/spec/email_auth_spec.rb +0 -285
  171. data/spec/http_basic_auth_spec.rb +0 -143
  172. data/spec/jwt_cors_spec.rb +0 -57
  173. data/spec/jwt_refresh_spec.rb +0 -256
  174. data/spec/jwt_spec.rb +0 -235
  175. data/spec/lockout_spec.rb +0 -250
  176. data/spec/login_spec.rb +0 -328
  177. data/spec/migrate/001_tables.rb +0 -184
  178. data/spec/migrate/002_account_password_hash_column.rb +0 -11
  179. data/spec/migrate_password/001_tables.rb +0 -73
  180. data/spec/migrate_travis/001_tables.rb +0 -141
  181. data/spec/password_complexity_spec.rb +0 -109
  182. data/spec/password_expiration_spec.rb +0 -244
  183. data/spec/password_grace_period_spec.rb +0 -93
  184. data/spec/remember_spec.rb +0 -451
  185. data/spec/reset_password_spec.rb +0 -229
  186. data/spec/rodauth_spec.rb +0 -343
  187. data/spec/session_expiration_spec.rb +0 -58
  188. data/spec/single_session_spec.rb +0 -127
  189. data/spec/spec_helper.rb +0 -327
  190. data/spec/two_factor_spec.rb +0 -1462
  191. data/spec/update_password_hash_spec.rb +0 -40
  192. data/spec/verify_account_grace_period_spec.rb +0 -171
  193. data/spec/verify_account_spec.rb +0 -240
  194. data/spec/verify_change_login_spec.rb +0 -46
  195. data/spec/verify_login_change_spec.rb +0 -232
  196. data/spec/views/layout-other.str +0 -11
  197. data/spec/views/layout.str +0 -11
  198. data/spec/views/login.str +0 -21
@@ -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,18 +215,6 @@ module Rodauth
218
215
  end
219
216
  end
220
217
 
221
- def two_factor_authentication_setup?
222
- super || otp_exists?
223
- end
224
-
225
- def two_factor_need_setup_redirect
226
- "#{prefix}/#{otp_setup_route}"
227
- end
228
-
229
- def two_factor_auth_required_redirect
230
- "#{prefix}/#{otp_auth_route}"
231
- end
232
-
233
218
  def two_factor_remove
234
219
  super
235
220
  otp_remove
@@ -240,19 +225,6 @@ module Rodauth
240
225
  otp_remove_auth_failures
241
226
  end
242
227
 
243
- def otp_auth_form_footer
244
- super if defined?(super)
245
- end
246
-
247
- def otp_lockout_redirect
248
- return super if defined?(super)
249
- nil
250
- end
251
-
252
- def otp_lockout_error_flash
253
- "Authentication code use locked out due to numerous failures.#{super if defined?(super)}"
254
- end
255
-
256
228
  def require_otp_setup
257
229
  unless otp_exists?
258
230
  set_redirect_error_status(two_factor_not_setup_error_status)
@@ -270,11 +242,11 @@ module Rodauth
270
242
  ot_pass = ot_pass.gsub(/\s+/, '')
271
243
  if drift = otp_drift
272
244
  if otp.respond_to?(:verify_with_drift)
245
+ # :nocov:
273
246
  otp.verify_with_drift(ot_pass, drift)
274
- else
275
247
  # :nocov:
248
+ else
276
249
  otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
277
- # :nocov:
278
250
  end
279
251
  else
280
252
  otp.verify(ot_pass)
@@ -283,7 +255,7 @@ module Rodauth
283
255
 
284
256
  def otp_remove
285
257
  otp_key_ds.delete
286
- super if defined?(super)
258
+ @otp_key = nil
287
259
  end
288
260
 
289
261
  def otp_add_key
@@ -297,6 +269,10 @@ module Rodauth
297
269
  update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
298
270
  end
299
271
 
272
+ def otp_last_use
273
+ convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
274
+ end
275
+
300
276
  def otp_record_authentication_failure
301
277
  otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
302
278
  end
@@ -314,7 +290,7 @@ module Rodauth
314
290
  end
315
291
 
316
292
  def otp_issuer
317
- request.host
293
+ domain
318
294
  end
319
295
 
320
296
  def otp_provisioning_name
@@ -337,8 +313,37 @@ module Rodauth
337
313
  !!hmac_secret
338
314
  end
339
315
 
316
+ def possible_authentication_methods
317
+ methods = super
318
+ methods << 'totp' if otp_exists? && !@otp_tmp_key
319
+ methods
320
+ end
321
+
340
322
  private
341
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
+
342
347
  def clear_cached_otp
343
348
  remove_instance_variable(:@otp) if defined?(@otp)
344
349
  end
@@ -362,32 +367,24 @@ module Rodauth
362
367
  end
363
368
 
364
369
  if ROTP::Base32.respond_to?(:random_base32)
365
- # :nocov:
366
370
  def otp_new_secret
367
371
  ROTP::Base32.random_base32.downcase
368
372
  end
369
- # :nocov:
370
373
  else
374
+ # :nocov:
371
375
  def otp_new_secret
372
376
  ROTP::Base32.random.downcase
373
377
  end
378
+ # :nocov:
374
379
  end
375
380
 
376
- if RUBY_VERSION < '1.9'
377
- # :nocov:
378
- def base32_encode(data, length)
379
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
380
- length.times.map{|i|chars[data[i] % 32].chr}.join
381
- end
382
- # :nocov:
383
- else
384
- def base32_encode(data, length)
385
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
386
- length.times.map{|i|chars[data[i].ord % 32]}.join
387
- end
381
+ def base32_encode(data, length)
382
+ chars = 'abcdefghijklmnopqrstuvwxyz234567'
383
+ length.times.map{|i|chars[data[i].ord % 32]}.join
388
384
  end
389
385
 
390
386
  def _otp_tmp_key(secret)
387
+ @otp_tmp_key = true
391
388
  @otp_user_key = nil
392
389
  @otp_key = secret
393
390
  end
@@ -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/
@@ -8,7 +8,7 @@ module Rodauth
8
8
  error_flash "Your password cannot be changed yet", 'password_not_changeable_yet'
9
9
 
10
10
  redirect :password_not_changeable_yet
11
- redirect(:password_change_needed){"#{prefix}/#{change_password_route}"}
11
+ redirect(:password_change_needed){change_password_path}
12
12
 
13
13
  auth_value_method :allow_password_change_after, -86400
14
14
  auth_value_method :require_password_change_after, 90*86400
@@ -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