rodauth 1.22.0 → 2.3.0

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