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
@@ -28,28 +28,32 @@ module Rodauth
28
28
  button 'Send SMS Code', 'sms_request'
29
29
  button 'Setup SMS Backup Number', 'sms_setup'
30
30
 
31
- error_flash "Error authenticating via SMS code.", 'sms_invalid_code'
31
+ error_flash "Error authenticating via SMS code", 'sms_invalid_code'
32
32
  error_flash "Error disabling SMS authentication", 'sms_disable'
33
33
  error_flash "Error setting up SMS authentication", 'sms_setup'
34
- error_flash "Invalid or out of date SMS confirmation code used, must setup SMS authentication again.", 'sms_invalid_confirmation_code'
34
+ error_flash "Invalid or out of date SMS confirmation code used, must setup SMS authentication again", 'sms_invalid_confirmation_code'
35
35
  error_flash "No current SMS code for this account", 'no_current_sms_code'
36
- error_flash "SMS authentication has been locked out.", 'sms_lockout'
37
- error_flash "SMS authentication has already been setup.", 'sms_already_setup'
38
- error_flash "SMS authentication has not been setup yet.", 'sms_not_setup'
39
- error_flash "SMS authentication needs confirmation.", 'sms_needs_confirmation'
36
+ error_flash "SMS authentication has been locked out", 'sms_lockout'
37
+ error_flash "SMS authentication has already been setup", 'sms_already_setup'
38
+ error_flash "SMS authentication has not been setup yet", 'sms_not_setup'
39
+ error_flash "SMS authentication needs confirmation", 'sms_needs_confirmation'
40
40
 
41
- notice_flash "SMS authentication code has been sent.", 'sms_request'
42
- notice_flash "SMS authentication has been disabled.", 'sms_disable'
43
- notice_flash "SMS authentication has been setup.", 'sms_confirm'
41
+ notice_flash "SMS authentication code has been sent", 'sms_request'
42
+ notice_flash "SMS authentication has been disabled", 'sms_disable'
43
+ notice_flash "SMS authentication has been setup", 'sms_confirm'
44
+
45
+ translatable_method :sms_auth_link_text, "Authenticate Using SMS Code"
46
+ translatable_method :sms_setup_link_text, "Setup Backup SMS Authentication"
47
+ translatable_method :sms_disable_link_text, "Disable SMS Authentication"
44
48
 
45
49
  redirect :sms_already_setup
46
50
  redirect :sms_confirm
47
51
  redirect :sms_disable
48
- redirect(:sms_auth){"#{prefix}/#{sms_auth_route}"}
49
- redirect(:sms_needs_confirmation){"#{prefix}/#{sms_confirm_route}"}
50
- redirect(:sms_needs_setup){"#{prefix}/#{sms_setup_route}"}
51
- redirect(:sms_request){"#{prefix}/#{sms_request_route}"}
52
- redirect(:sms_lockout){_two_factor_auth_required_redirect}
52
+ redirect(:sms_auth){sms_auth_path}
53
+ redirect(:sms_needs_confirmation){sms_confirm_path}
54
+ redirect(:sms_needs_setup){sms_setup_path}
55
+ redirect(:sms_request){sms_request_path}
56
+ redirect(:sms_lockout){two_factor_auth_required_redirect}
53
57
 
54
58
  loaded_templates %w'sms-auth sms-confirm sms-disable sms-request sms-setup sms-code-field password-field'
55
59
  view 'sms-auth', 'Authenticate via SMS Code', 'sms_auth'
@@ -64,18 +68,19 @@ module Rodauth
64
68
  auth_value_method :sms_auth_code_length, 6
65
69
  auth_value_method :sms_code_allowed_seconds, 300
66
70
  auth_value_method :sms_code_column, :code
67
- auth_value_method :sms_code_label, 'SMS Code'
71
+ translatable_method :sms_code_label, 'SMS Code'
68
72
  auth_value_method :sms_code_param, 'sms-code'
69
73
  auth_value_method :sms_codes_table, :account_sms_codes
70
74
  auth_value_method :sms_confirm_code_length, 12
71
75
  auth_value_method :sms_failure_limit, 5
72
76
  auth_value_method :sms_failures_column, :num_failures
73
77
  auth_value_method :sms_id_column, :id
74
- auth_value_method :sms_invalid_code_message, "invalid SMS code"
75
- auth_value_method :sms_invalid_phone_message, "invalid SMS phone number"
78
+ translatable_method :sms_invalid_code_message, "invalid SMS code"
79
+ translatable_method :sms_invalid_phone_message, "invalid SMS phone number"
76
80
  auth_value_method :sms_issued_at_column, :code_issued_at
77
81
  auth_value_method :sms_phone_column, :phone_number
78
- auth_value_method :sms_phone_label, 'Phone Number'
82
+ translatable_method :sms_phone_label, 'Phone Number'
83
+ auth_value_method :sms_phone_input_type, 'tel'
79
84
  auth_value_method :sms_phone_min_length, 7
80
85
  auth_value_method :sms_phone_param, 'sms-phone'
81
86
 
@@ -110,7 +115,7 @@ module Rodauth
110
115
  route(:sms_request) do |r|
111
116
  require_login
112
117
  require_account_session
113
- require_two_factor_not_authenticated
118
+ require_two_factor_not_authenticated('sms_code')
114
119
  require_sms_available
115
120
  before_sms_request_route
116
121
 
@@ -133,7 +138,7 @@ module Rodauth
133
138
  route(:sms_auth) do |r|
134
139
  require_login
135
140
  require_account_session
136
- require_two_factor_not_authenticated
141
+ require_two_factor_not_authenticated('sms_code')
137
142
  require_sms_available
138
143
 
139
144
  unless sms_current_auth?
@@ -157,7 +162,7 @@ module Rodauth
157
162
  if sms_code_match?(param(sms_code_param))
158
163
  before_sms_auth
159
164
  sms_remove_failures
160
- two_factor_authenticate(:sms_code)
165
+ two_factor_authenticate('sms_code')
161
166
  else
162
167
  sms_record_failure
163
168
  after_sms_failure
@@ -238,8 +243,8 @@ module Rodauth
238
243
  before_sms_confirm
239
244
  sms_confirm
240
245
  after_sms_confirm
241
- if sms_codes_primary?
242
- two_factor_authenticate(:sms_code)
246
+ unless two_factor_authenticated?
247
+ two_factor_update_session('sms_code')
243
248
  end
244
249
  end
245
250
 
@@ -268,8 +273,8 @@ module Rodauth
268
273
  transaction do
269
274
  before_sms_disable
270
275
  sms_disable
271
- if sms_codes_primary?
272
- two_factor_remove_session
276
+ if two_factor_login_type_match?('sms_code')
277
+ two_factor_remove_session('sms_code')
273
278
  end
274
279
  after_sms_disable
275
280
  end
@@ -284,18 +289,6 @@ module Rodauth
284
289
  end
285
290
  end
286
291
 
287
- def two_factor_need_setup_redirect
288
- super || (sms_needs_setup_redirect if sms_codes_primary?)
289
- end
290
-
291
- def two_factor_auth_required_redirect
292
- super || (sms_request_redirect if sms_codes_primary? && sms_available?)
293
- end
294
-
295
- def two_factor_auth_fallback_redirect
296
- sms_available? ? sms_request_redirect : super
297
- end
298
-
299
292
  def two_factor_remove
300
293
  super
301
294
  sms_disable
@@ -306,37 +299,6 @@ module Rodauth
306
299
  sms_remove_failures
307
300
  end
308
301
 
309
- def two_factor_authentication_setup?
310
- super || (sms_codes_primary? && sms_setup?)
311
- end
312
-
313
- def otp_auth_form_footer
314
- "#{super if defined?(super)}#{"<p><a href=\"#{sms_request_route}\">Authenticate using SMS code</a></p>" if sms_available?}"
315
- end
316
-
317
- def otp_lockout_redirect
318
- if sms_available?
319
- sms_request_redirect
320
- else
321
- super if defined?(super)
322
- end
323
- end
324
-
325
- def otp_lockout_error_flash
326
- msg = super if defined?(super)
327
- if sms_available?
328
- msg = "#{msg} Can use SMS code to unlock."
329
- end
330
- msg
331
- end
332
-
333
- def otp_remove
334
- super if defined?(super)
335
- unless sms_codes_primary?
336
- sms_disable
337
- end
338
- end
339
-
340
302
  def require_sms_setup
341
303
  unless sms_setup?
342
304
  set_redirect_error_status(two_factor_not_setup_error_status)
@@ -375,7 +337,6 @@ module Rodauth
375
337
  def sms_disable
376
338
  sms_ds.delete
377
339
  @sms = nil
378
- super if defined?(super)
379
340
  end
380
341
 
381
342
  def sms_confirm_failure
@@ -415,11 +376,11 @@ module Rodauth
415
376
  end
416
377
 
417
378
  def sms_auth_message(code)
418
- "SMS authentication code for #{request.host} is #{code}"
379
+ "SMS authentication code for #{domain} is #{code}"
419
380
  end
420
381
 
421
382
  def sms_confirm_message(code)
422
- "SMS confirmation code for #{request.host} is #{code}"
383
+ "SMS confirmation code for #{domain} is #{code}"
423
384
  end
424
385
 
425
386
  def sms_set_code(code)
@@ -468,10 +429,39 @@ module Rodauth
468
429
  sms_code && sms_code_issued_at + sms_code_allowed_seconds > Time.now
469
430
  end
470
431
 
432
+ def possible_authentication_methods
433
+ methods = super
434
+ methods << 'sms_code' if sms_setup?
435
+ methods
436
+ end
437
+
471
438
  private
472
439
 
440
+ def _two_factor_auth_links
441
+ links = super
442
+ links << [30, sms_request_path, sms_auth_link_text] if sms_available?
443
+ links
444
+ end
445
+
446
+ def _two_factor_setup_links
447
+ links = super
448
+ links << [30, sms_setup_path, sms_setup_link_text] if !sms_setup? && (sms_codes_primary? || uses_two_factor_authentication?)
449
+ links
450
+ end
451
+
452
+ def _two_factor_remove_links
453
+ links = super
454
+ links << [30, sms_disable_path, sms_disable_link_text] if sms_setup?
455
+ links
456
+ end
457
+
458
+ def _two_factor_remove_all_from_session
459
+ two_factor_remove_session('sms_codes')
460
+ super
461
+ end
462
+
473
463
  def sms_codes_primary?
474
- !features.include?(:otp)
464
+ (features & [:otp, :webauthn]).empty?
475
465
  end
476
466
 
477
467
  def sms_normalize_phone(phone)
@@ -2,25 +2,50 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:two_factor_base, :TwoFactorBase) do
5
+ loaded_templates %w'two-factor-manage two-factor-auth two-factor-disable'
6
+
7
+ view 'two-factor-manage', 'Manage Multifactor Authentication', 'two_factor_manage'
8
+ view 'two-factor-auth', 'Authenticate Using Additional Factor', 'two_factor_auth'
9
+ view 'two-factor-disable', 'Remove All Multifactor Authentication Methods', 'two_factor_disable'
10
+
11
+ before :two_factor_disable
12
+
5
13
  after :two_factor_authentication
14
+ after :two_factor_disable
15
+
16
+ additional_form_tags :two_factor_disable
17
+
18
+ button "Remove All Multifactor Authentication Methods", :two_factor_disable
6
19
 
7
- redirect :two_factor_auth
8
- redirect :two_factor_already_authenticated
20
+ redirect(:two_factor_auth)
21
+ redirect(:two_factor_already_authenticated)
22
+ redirect(:two_factor_disable)
23
+ redirect(:two_factor_need_setup){two_factor_manage_path}
24
+ redirect(:two_factor_auth_required){two_factor_auth_path}
9
25
 
10
- notice_flash "You have been authenticated via 2nd factor", "two_factor_auth"
26
+ notice_flash "You have been multifactor authenticated", "two_factor_auth"
27
+ notice_flash "All multifactor authentication methods have been disabled", "two_factor_disable"
11
28
 
12
- error_flash "This account has not been setup for two factor authentication", 'two_factor_not_setup'
13
- error_flash "Already authenticated via 2nd factor", 'two_factor_already_authenticated'
14
- error_flash "You need to authenticate via 2nd factor before continuing.", 'two_factor_need_authentication'
29
+ error_flash "This account has not been setup for multifactor authentication", 'two_factor_not_setup'
30
+ error_flash "You have already been multifactor authenticated", 'two_factor_already_authenticated'
31
+ error_flash "You need to authenticate via an additional factor before continuing", 'two_factor_need_authentication'
32
+ error_flash "Unable to remove all multifactor authentication methods", "two_factor_disable"
15
33
 
16
34
  auth_value_method :two_factor_already_authenticated_error_status, 403
17
35
  auth_value_method :two_factor_need_authentication_error_status, 401
18
36
  auth_value_method :two_factor_not_setup_error_status, 403
19
37
 
20
- session_key :two_factor_session_key, :two_factor_auth
21
38
  session_key :two_factor_setup_session_key, :two_factor_auth_setup
22
- auth_value_method :two_factor_need_setup_redirect, nil
23
- auth_value_method :two_factor_auth_required_redirect, nil
39
+ session_key :two_factor_auth_redirect_session_key, :two_factor_auth_redirect
40
+
41
+ translatable_method :two_factor_setup_heading, "<h2>Setup Multifactor Authentication</h2>"
42
+ translatable_method :two_factor_remove_heading, "<h2>Remove Multifactor Authentication</h2>"
43
+ translatable_method :two_factor_disable_link_text, "Remove All Multifactor Authentication Methods"
44
+ auth_value_method :two_factor_auth_return_to_requested_location?, false
45
+
46
+ auth_cached_method :two_factor_auth_links
47
+ auth_cached_method :two_factor_setup_links
48
+ auth_cached_method :two_factor_remove_links
24
49
 
25
50
  auth_value_methods :two_factor_modifications_require_password?
26
51
 
@@ -32,6 +57,62 @@ module Rodauth
32
57
  :two_factor_update_session
33
58
  )
34
59
 
60
+ route(:two_factor_manage, 'multifactor-manage') do |r|
61
+ require_account
62
+ before_two_factor_manage_route
63
+
64
+ r.get do
65
+ all_links = two_factor_setup_links + two_factor_remove_links
66
+ if all_links.length == 1
67
+ redirect all_links[0][1]
68
+ end
69
+ two_factor_manage_view
70
+ end
71
+ end
72
+
73
+ route(:two_factor_auth, 'multifactor-auth') do |r|
74
+ require_login
75
+ require_account_session
76
+ require_two_factor_setup
77
+ require_two_factor_not_authenticated
78
+ before_two_factor_auth_route
79
+
80
+ r.get do
81
+ if two_factor_auth_links.length == 1
82
+ redirect two_factor_auth_links[0][1]
83
+ end
84
+ two_factor_auth_view
85
+ end
86
+ end
87
+
88
+ route(:two_factor_disable, 'multifactor-disable') do |r|
89
+ require_account
90
+ require_two_factor_setup
91
+ before_two_factor_disable_route
92
+
93
+ r.get do
94
+ two_factor_disable_view
95
+ end
96
+
97
+ r.post do
98
+ if two_factor_password_match?(param(password_param))
99
+ transaction do
100
+ before_two_factor_disable
101
+ two_factor_remove
102
+ _two_factor_remove_all_from_session
103
+ after_two_factor_disable
104
+ end
105
+ set_notice_flash two_factor_disable_notice_flash
106
+ redirect two_factor_disable_redirect
107
+ end
108
+
109
+ set_response_error_status(invalid_password_error_status)
110
+ set_field_error(password_param, invalid_password_message)
111
+ set_error_flash two_factor_disable_error_flash
112
+ two_factor_disable_view
113
+ end
114
+ end
115
+
35
116
  def two_factor_modifications_require_password?
36
117
  modifications_require_password?
37
118
  end
@@ -44,7 +125,7 @@ module Rodauth
44
125
  return true if two_factor_authenticated?
45
126
 
46
127
  # True if authenticated via single factor and 2nd factor not setup
47
- !two_factor_authentication_setup?
128
+ !uses_two_factor_authentication?
48
129
  end
49
130
 
50
131
  def require_authentication
@@ -53,7 +134,7 @@ module Rodauth
53
134
  # Avoid database query if already authenticated via 2nd factor
54
135
  return if two_factor_authenticated?
55
136
 
56
- require_two_factor_authenticated if two_factor_authentication_setup?
137
+ require_two_factor_authenticated if uses_two_factor_authentication?
57
138
  end
58
139
 
59
140
  def require_two_factor_setup
@@ -67,8 +148,8 @@ module Rodauth
67
148
  redirect two_factor_need_setup_redirect
68
149
  end
69
150
 
70
- def require_two_factor_not_authenticated
71
- if two_factor_authenticated?
151
+ def require_two_factor_not_authenticated(auth_type = nil)
152
+ if two_factor_authenticated? || (auth_type && two_factor_login_type_match?(auth_type))
72
153
  set_redirect_error_status(two_factor_already_authenticated_error_status)
73
154
  set_redirect_error_flash two_factor_already_authenticated_error_flash
74
155
  redirect two_factor_already_authenticated_redirect
@@ -77,9 +158,12 @@ module Rodauth
77
158
 
78
159
  def require_two_factor_authenticated
79
160
  unless two_factor_authenticated?
161
+ if two_factor_auth_return_to_requested_location?
162
+ set_session_value(two_factor_auth_redirect_session_key, request.fullpath)
163
+ end
80
164
  set_redirect_error_status(two_factor_need_authentication_error_status)
81
165
  set_redirect_error_flash two_factor_need_authentication_error_flash
82
- redirect _two_factor_auth_required_redirect
166
+ redirect two_factor_auth_required_redirect
83
167
  end
84
168
  end
85
169
 
@@ -87,10 +171,6 @@ module Rodauth
87
171
  nil
88
172
  end
89
173
 
90
- def two_factor_auth_fallback_redirect
91
- nil
92
- end
93
-
94
174
  def two_factor_password_match?(password)
95
175
  if two_factor_modifications_require_password?
96
176
  password_match?(password)
@@ -100,25 +180,45 @@ module Rodauth
100
180
  end
101
181
 
102
182
  def two_factor_authenticated?
103
- !!session[two_factor_session_key]
183
+ authenticated_by && authenticated_by.length >= 2
104
184
  end
105
185
 
106
186
  def two_factor_authentication_setup?
107
- false
187
+ possible_authentication_methods.length >= 2
108
188
  end
109
189
 
110
190
  def uses_two_factor_authentication?
111
191
  return false unless logged_in?
112
- session[two_factor_setup_session_key] = two_factor_authentication_setup? unless session.has_key?(two_factor_setup_session_key)
192
+ set_session_value(two_factor_setup_session_key, two_factor_authentication_setup?) unless session.has_key?(two_factor_setup_session_key)
113
193
  session[two_factor_setup_session_key]
114
194
  end
115
195
 
196
+ def two_factor_login_type_match?(type)
197
+ authenticated_by && authenticated_by.include?(type)
198
+ end
199
+
116
200
  def two_factor_remove
117
201
  nil
118
202
  end
119
203
 
120
204
  private
121
205
 
206
+ def _two_factor_auth_links
207
+ (super if defined?(super)) || []
208
+ end
209
+
210
+ def _two_factor_setup_links
211
+ []
212
+ end
213
+
214
+ def _two_factor_remove_links
215
+ []
216
+ end
217
+
218
+ def _two_factor_remove_all_from_session
219
+ nil
220
+ end
221
+
122
222
  def after_close_account
123
223
  super if defined?(super)
124
224
  two_factor_remove
@@ -129,21 +229,25 @@ module Rodauth
129
229
  two_factor_remove_auth_failures
130
230
  after_two_factor_authentication
131
231
  set_notice_flash two_factor_auth_notice_flash
132
- redirect two_factor_auth_redirect
232
+ redirect_two_factor_authenticated
133
233
  end
134
234
 
135
- def two_factor_remove_session
136
- session.delete(two_factor_session_key)
137
- session[two_factor_setup_session_key] = false
235
+ def redirect_two_factor_authenticated
236
+ saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
237
+ redirect saved_two_factor_auth_redirect || two_factor_auth_redirect
138
238
  end
139
239
 
140
- def two_factor_update_session(type)
141
- session[two_factor_session_key] = type
142
- session[two_factor_setup_session_key] = true
240
+ def two_factor_remove_session(type)
241
+ authenticated_by.delete(type)
242
+ remove_session_value(two_factor_setup_session_key)
243
+ if authenticated_by.empty?
244
+ clear_session
245
+ end
143
246
  end
144
247
 
145
- def _two_factor_auth_required_redirect
146
- two_factor_auth_required_redirect || two_factor_auth_fallback_redirect || default_redirect
248
+ def two_factor_update_session(auth_type)
249
+ authenticated_by << auth_type
250
+ set_session_value(two_factor_setup_session_key, true)
147
251
  end
148
252
  end
149
253
  end