rodauth 1.23.0 → 2.0.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +132 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +207 -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 +74 -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/http_basic_auth.rdoc +10 -1
  19. data/doc/jwt.rdoc +22 -22
  20. data/doc/jwt_cors.rdoc +2 -3
  21. data/doc/jwt_refresh.rdoc +12 -8
  22. data/doc/lockout.rdoc +17 -15
  23. data/doc/login.rdoc +10 -2
  24. data/doc/login_password_requirements_base.rdoc +15 -37
  25. data/doc/logout.rdoc +2 -2
  26. data/doc/otp.rdoc +24 -19
  27. data/doc/password_complexity.rdoc +10 -26
  28. data/doc/password_expiration.rdoc +11 -25
  29. data/doc/password_grace_period.rdoc +16 -2
  30. data/doc/recovery_codes.rdoc +18 -12
  31. data/doc/release_notes/2.0.0.txt +361 -0
  32. data/doc/remember.rdoc +40 -64
  33. data/doc/reset_password.rdoc +12 -9
  34. data/doc/session_expiration.rdoc +1 -0
  35. data/doc/single_session.rdoc +16 -25
  36. data/doc/sms_codes.rdoc +24 -14
  37. data/doc/two_factor_base.rdoc +60 -22
  38. data/doc/verify_account.rdoc +14 -12
  39. data/doc/verify_account_grace_period.rdoc +6 -2
  40. data/doc/verify_login_change.rdoc +9 -8
  41. data/doc/webauthn.rdoc +115 -0
  42. data/doc/webauthn_login.rdoc +15 -0
  43. data/doc/webauthn_verify_account.rdoc +9 -0
  44. data/javascript/webauthn_auth.js +45 -0
  45. data/javascript/webauthn_setup.js +35 -0
  46. data/lib/roda/plugins/rodauth.rb +1 -1
  47. data/lib/rodauth.rb +29 -24
  48. data/lib/rodauth/features/account_expiration.rb +5 -5
  49. data/lib/rodauth/features/active_sessions.rb +160 -0
  50. data/lib/rodauth/features/audit_logging.rb +96 -0
  51. data/lib/rodauth/features/base.rb +131 -47
  52. data/lib/rodauth/features/change_password_notify.rb +1 -1
  53. data/lib/rodauth/features/confirm_password.rb +40 -2
  54. data/lib/rodauth/features/create_account.rb +7 -13
  55. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  56. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  57. data/lib/rodauth/features/email_auth.rb +29 -27
  58. data/lib/rodauth/features/email_base.rb +3 -3
  59. data/lib/rodauth/features/http_basic_auth.rb +44 -37
  60. data/lib/rodauth/features/jwt.rb +51 -8
  61. data/lib/rodauth/features/jwt_refresh.rb +3 -3
  62. data/lib/rodauth/features/lockout.rb +11 -13
  63. data/lib/rodauth/features/login.rb +48 -8
  64. data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
  65. data/lib/rodauth/features/otp.rb +71 -81
  66. data/lib/rodauth/features/password_complexity.rb +4 -11
  67. data/lib/rodauth/features/password_expiration.rb +1 -1
  68. data/lib/rodauth/features/password_grace_period.rb +17 -10
  69. data/lib/rodauth/features/recovery_codes.rb +47 -51
  70. data/lib/rodauth/features/remember.rb +11 -27
  71. data/lib/rodauth/features/reset_password.rb +25 -25
  72. data/lib/rodauth/features/session_expiration.rb +6 -4
  73. data/lib/rodauth/features/single_session.rb +7 -5
  74. data/lib/rodauth/features/sms_codes.rb +58 -67
  75. data/lib/rodauth/features/two_factor_base.rb +132 -28
  76. data/lib/rodauth/features/verify_account.rb +23 -20
  77. data/lib/rodauth/features/verify_account_grace_period.rb +19 -8
  78. data/lib/rodauth/features/verify_login_change.rb +11 -10
  79. data/lib/rodauth/features/webauthn.rb +507 -0
  80. data/lib/rodauth/features/webauthn_login.rb +70 -0
  81. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  82. data/lib/rodauth/version.rb +2 -2
  83. data/templates/button.str +1 -3
  84. data/templates/change-login.str +1 -2
  85. data/templates/change-password.str +3 -5
  86. data/templates/close-account.str +2 -2
  87. data/templates/confirm-password.str +1 -1
  88. data/templates/create-account.str +1 -1
  89. data/templates/email-auth-request-form.str +1 -2
  90. data/templates/email-auth.str +1 -1
  91. data/templates/global-logout-field.str +6 -0
  92. data/templates/login-confirm-field.str +2 -4
  93. data/templates/login-display.str +3 -2
  94. data/templates/login-field.str +2 -4
  95. data/templates/login-form-footer.str +6 -0
  96. data/templates/login-form.str +7 -0
  97. data/templates/login.str +1 -9
  98. data/templates/logout.str +1 -1
  99. data/templates/multi-phase-login.str +3 -0
  100. data/templates/otp-auth-code-field.str +5 -3
  101. data/templates/otp-auth.str +1 -1
  102. data/templates/otp-disable.str +1 -1
  103. data/templates/otp-setup.str +3 -3
  104. data/templates/password-confirm-field.str +2 -4
  105. data/templates/password-field.str +2 -4
  106. data/templates/recovery-auth.str +3 -6
  107. data/templates/recovery-codes.str +1 -1
  108. data/templates/remember.str +15 -20
  109. data/templates/reset-password-request.str +2 -2
  110. data/templates/reset-password.str +1 -2
  111. data/templates/sms-auth.str +1 -1
  112. data/templates/sms-code-field.str +5 -3
  113. data/templates/sms-confirm.str +1 -2
  114. data/templates/sms-disable.str +1 -2
  115. data/templates/sms-request.str +1 -1
  116. data/templates/sms-setup.str +6 -4
  117. data/templates/two-factor-auth.str +5 -0
  118. data/templates/two-factor-disable.str +6 -0
  119. data/templates/two-factor-manage.str +16 -0
  120. data/templates/unlock-account-request.str +2 -2
  121. data/templates/unlock-account.str +1 -1
  122. data/templates/verify-account-resend.str +1 -1
  123. data/templates/verify-account.str +1 -2
  124. data/templates/verify-login-change.str +1 -1
  125. data/templates/webauthn-auth.str +11 -0
  126. data/templates/webauthn-remove.str +14 -0
  127. data/templates/webauthn-setup.str +12 -0
  128. metadata +64 -11
  129. data/doc/verify_change_login.rdoc +0 -11
  130. data/lib/rodauth/features/verify_change_login.rb +0 -20
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:session_expiration, :SessionExpiration) do
5
- error_flash "This session has expired, please login again."
5
+ error_flash "This session has expired, please login again"
6
6
 
7
7
  auth_value_method :max_session_lifetime, 86400
8
8
  session_key :session_created_session_key, :session_created_at
9
+ auth_value_method :session_expiration_error_status, 401
9
10
  auth_value_method :session_expiration_default, true
10
11
  auth_value_method :session_inactivity_timeout, 1800
11
12
  session_key :session_last_activity_session_key, :last_session_activity_at
@@ -37,6 +38,7 @@ module Rodauth
37
38
 
38
39
  def expire_session
39
40
  clear_session
41
+ set_redirect_error_status session_expiration_error_status
40
42
  set_redirect_error_flash session_expiration_error_flash
41
43
  redirect session_expiration_redirect
42
44
  end
@@ -45,11 +47,11 @@ module Rodauth
45
47
  require_login_redirect
46
48
  end
47
49
 
48
- private
49
-
50
50
  def update_session
51
51
  super
52
- session[session_last_activity_session_key] = session[session_created_session_key] = Time.now.to_i
52
+ t = Time.now.to_i
53
+ set_session_value(session_last_activity_session_key, t)
54
+ set_session_value(session_created_session_key, t)
53
55
  end
54
56
  end
55
57
  end
@@ -6,6 +6,7 @@ module Rodauth
6
6
  redirect
7
7
 
8
8
  auth_value_method :allow_raw_single_session_key?, false
9
+ auth_value_method :inactive_session_error_status, 401
9
10
  auth_value_method :single_session_id_column, :id
10
11
  auth_value_method :single_session_key_column, :key
11
12
  session_key :single_session_session_key, :single_session_key
@@ -55,6 +56,7 @@ module Rodauth
55
56
 
56
57
  def no_longer_active_session
57
58
  clear_session
59
+ set_redirect_error_status inactive_session_error_status
58
60
  set_redirect_error_flash single_session_error_flash
59
61
  redirect single_session_redirect
60
62
  end
@@ -70,6 +72,11 @@ module Rodauth
70
72
  end
71
73
  end
72
74
 
75
+ def update_session
76
+ super
77
+ update_single_session_key
78
+ end
79
+
73
80
  private
74
81
 
75
82
  def after_close_account
@@ -87,11 +94,6 @@ module Rodauth
87
94
  set_session_value(single_session_session_key, data)
88
95
  end
89
96
 
90
- def update_session
91
- super
92
- update_single_session_key
93
- end
94
-
95
97
  def single_session_ds
96
98
  db[single_session_table].
97
99
  where(single_session_id_column=>session_value)
@@ -28,19 +28,23 @@ 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
@@ -49,7 +53,7 @@ module Rodauth
49
53
  redirect(:sms_needs_confirmation){sms_confirm_path}
50
54
  redirect(:sms_needs_setup){sms_setup_path}
51
55
  redirect(:sms_request){sms_request_path}
52
- redirect(:sms_lockout){_two_factor_auth_required_redirect}
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_path}\">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)
@@ -415,11 +377,11 @@ module Rodauth
415
377
  end
416
378
 
417
379
  def sms_auth_message(code)
418
- "SMS authentication code for #{request.host} is #{code}"
380
+ "SMS authentication code for #{domain} is #{code}"
419
381
  end
420
382
 
421
383
  def sms_confirm_message(code)
422
- "SMS confirmation code for #{request.host} is #{code}"
384
+ "SMS confirmation code for #{domain} is #{code}"
423
385
  end
424
386
 
425
387
  def sms_set_code(code)
@@ -468,10 +430,39 @@ module Rodauth
468
430
  sms_code && sms_code_issued_at + sms_code_allowed_seconds > Time.now
469
431
  end
470
432
 
433
+ def possible_authentication_methods
434
+ methods = super
435
+ methods << 'sms_code' if sms_setup?
436
+ methods
437
+ end
438
+
471
439
  private
472
440
 
441
+ def _two_factor_auth_links
442
+ links = super
443
+ links << [30, sms_request_path, sms_auth_link_text] if sms_available?
444
+ links
445
+ end
446
+
447
+ def _two_factor_setup_links
448
+ links = super
449
+ links << [30, sms_setup_path, sms_setup_link_text] if !sms_setup? && (sms_codes_primary? || uses_two_factor_authentication?)
450
+ links
451
+ end
452
+
453
+ def _two_factor_remove_links
454
+ links = super
455
+ links << [30, sms_disable_path, sms_disable_link_text] if sms_setup?
456
+ links
457
+ end
458
+
459
+ def _two_factor_remove_all_from_session
460
+ two_factor_remove_session('sms_codes')
461
+ super
462
+ end
463
+
473
464
  def sms_codes_primary?
474
- !features.include?(:otp)
465
+ (features & [:otp, :webauthn]).empty?
475
466
  end
476
467
 
477
468
  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
@@ -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
+ (super if defined?(super)) || []
212
+ end
213
+
214
+ def _two_factor_remove_links
215
+ (super if defined?(super)) || []
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