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
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- JwtRefresh = Feature.define(:jwt_refresh) do
4
+ Feature.define(:jwt_refresh, :JwtRefresh) do
5
5
  depends :jwt
6
6
 
7
7
  after 'refresh_token'
@@ -10,10 +10,10 @@ module Rodauth
10
10
  auth_value_method :jwt_access_token_key, 'access_token'
11
11
  auth_value_method :jwt_access_token_not_before_period, 5
12
12
  auth_value_method :jwt_access_token_period, 1800
13
- auth_value_method :jwt_refresh_invalid_token_message, 'invalid JWT refresh token'
13
+ translatable_method :jwt_refresh_invalid_token_message, 'invalid JWT refresh token'
14
14
  auth_value_method :jwt_refresh_token_account_id_column, :account_id
15
15
  auth_value_method :jwt_refresh_token_deadline_column, :deadline
16
- auth_value_method :jwt_refresh_token_deadline_interval, {:days=>14}
16
+ auth_value_method :jwt_refresh_token_deadline_interval, {:days=>14}.freeze
17
17
  auth_value_method :jwt_refresh_token_id_column, :id
18
18
  auth_value_method :jwt_refresh_token_key, 'refresh_token'
19
19
  auth_value_method :jwt_refresh_token_key_column, :key
@@ -4,8 +4,6 @@ module Rodauth
4
4
  Feature.define(:lockout, :Lockout) do
5
5
  depends :login, :email_base
6
6
 
7
- def_deprecated_alias :no_matching_unlock_account_key_error_flash, :no_matching_unlock_account_key_message
8
-
9
7
  loaded_templates %w'unlock-account-request unlock-account password-field unlock-account-email'
10
8
  view 'unlock-account-request', 'Request Account Unlock', 'unlock_account_request'
11
9
  view 'unlock-account', 'Unlock Account', 'unlock_account'
@@ -19,7 +17,7 @@ module Rodauth
19
17
  button 'Unlock Account', 'unlock_account'
20
18
  button 'Request Account Unlock', 'unlock_account_request'
21
19
  error_flash "There was an error unlocking your account", 'unlock_account'
22
- error_flash "This account is currently locked out and cannot be logged in to.", "login_lockout"
20
+ error_flash "This account is currently locked out and cannot be logged in to", "login_lockout"
23
21
  error_flash "An email has recently been sent to you with a link to unlock the account", 'unlock_account_email_recently_sent'
24
22
  error_flash "There was an error unlocking your account: invalid or expired unlock account key", 'no_matching_unlock_account_key'
25
23
  notice_flash "Your account has been unlocked", 'unlock_account'
@@ -36,12 +34,12 @@ module Rodauth
36
34
  auth_value_method :account_lockouts_table, :account_lockouts
37
35
  auth_value_method :account_lockouts_id_column, :id
38
36
  auth_value_method :account_lockouts_key_column, :key
39
- auth_value_method :account_lockouts_email_last_sent_column, nil
37
+ auth_value_method :account_lockouts_email_last_sent_column, :email_last_sent
40
38
  auth_value_method :account_lockouts_deadline_column, :deadline
41
- auth_value_method :account_lockouts_deadline_interval, {:days=>1}
42
- auth_value_method :unlock_account_email_subject, 'Unlock Account'
43
- auth_value_method :unlock_account_explanatory_text, '<p>This account is currently locked out. You can unlock the account:</p>'
44
- auth_value_method :unlock_account_request_explanatory_text, '<p>This account is currently locked out. You can request that the account be unlocked:</p>'
39
+ auth_value_method :account_lockouts_deadline_interval, {:days=>1}.freeze
40
+ translatable_method :unlock_account_email_subject, 'Unlock Account'
41
+ translatable_method :unlock_account_explanatory_text, '<p>This account is currently locked out. You can unlock the account:</p>'
42
+ translatable_method :unlock_account_request_explanatory_text, '<p>This account is currently locked out. You can request that the account be unlocked:</p>'
45
43
  auth_value_method :unlock_account_key_param, 'key'
46
44
  auth_value_method :unlock_account_requires_password?, false
47
45
  auth_value_method :unlock_account_skip_resend_email_within, 300
@@ -75,6 +73,7 @@ module Rodauth
75
73
  redirect unlock_account_email_recently_sent_redirect
76
74
  end
77
75
 
76
+ @unlock_account_key_value = get_unlock_account_key
78
77
  transaction do
79
78
  before_unlock_account_request
80
79
  set_unlock_account_email_last_sent
@@ -98,7 +97,7 @@ module Rodauth
98
97
 
99
98
  r.get do
100
99
  if key = param_or_nil(unlock_account_key_param)
101
- session[unlock_account_session_key] = key
100
+ set_session_value(unlock_account_session_key, key)
102
101
  redirect(r.path)
103
102
  end
104
103
 
@@ -106,7 +105,7 @@ module Rodauth
106
105
  if account_from_unlock_key(key)
107
106
  unlock_account_view
108
107
  else
109
- session[unlock_account_session_key] = nil
108
+ remove_session_value(unlock_account_session_key)
110
109
  set_redirect_error_flash no_matching_unlock_account_key_error_flash
111
110
  redirect require_login_redirect
112
111
  end
@@ -127,11 +126,11 @@ module Rodauth
127
126
  unlock_account
128
127
  after_unlock_account
129
128
  if unlock_account_autologin?
130
- update_session
129
+ autologin_session('unlock_account')
131
130
  end
132
131
  end
133
132
 
134
- session[unlock_account_session_key] = nil
133
+ remove_session_value(unlock_account_session_key)
135
134
  set_notice_flash unlock_account_notice_flash
136
135
  redirect unlock_account_redirect
137
136
  else
@@ -217,7 +216,6 @@ module Rodauth
217
216
  end
218
217
 
219
218
  def send_unlock_account_email
220
- @unlock_account_key_value = get_unlock_account_key
221
219
  send_email(create_unlock_account_email)
222
220
  end
223
221
 
@@ -5,16 +5,24 @@ module Rodauth
5
5
  notice_flash "You have been logged in"
6
6
  notice_flash "Login recognized, please enter your password", "need_password"
7
7
  error_flash "There was an error logging in"
8
- loaded_templates %w'login login-field password-field login-display'
8
+ loaded_templates %w'login login-form login-form-footer multi-phase-login login-field password-field login-display'
9
9
  view 'login', 'Login'
10
+ view 'multi-phase-login', 'Login', 'multi_phase_login'
10
11
  additional_form_tags
11
12
  button 'Login'
12
13
  redirect
13
14
 
14
15
  auth_value_method :login_error_status, 401
15
- auth_value_method :login_form_footer, ''
16
+ translatable_method :login_form_footer_links_heading, '<h2 class="rodauth-login-form-footer-links-heading">Other Options</h2>'
17
+ auth_value_method :login_return_to_requested_location?, false
16
18
  auth_value_method :use_multi_phase_login?, false
17
19
 
20
+ session_key :login_redirect_session_key, :login_redirect
21
+
22
+ auth_cached_method :multi_phase_login_forms
23
+ auth_cached_method :login_form_footer_links
24
+ auth_cached_method :login_form_footer
25
+
18
26
  route do |r|
19
27
  check_already_logged_in
20
28
  before_login_route
@@ -24,8 +32,8 @@ module Rodauth
24
32
  end
25
33
 
26
34
  r.post do
27
- clear_session
28
35
  skip_error_flash = false
36
+ view = :login_view
29
37
 
30
38
  catch_error do
31
39
  unless account_from_login(param(login_param))
@@ -40,6 +48,7 @@ module Rodauth
40
48
 
41
49
  if use_multi_phase_login?
42
50
  @valid_login_entered = true
51
+ view = :multi_phase_login_view
43
52
 
44
53
  unless param_or_nil(password_param)
45
54
  after_login_entered_during_multi_phase_login
@@ -53,18 +62,28 @@ module Rodauth
53
62
  throw_error_status(login_error_status, password_param, invalid_password_message)
54
63
  end
55
64
 
56
- _login
65
+ _login('password')
57
66
  end
58
67
 
59
68
  set_error_flash login_error_flash unless skip_error_flash
60
- login_view
69
+ send(view)
61
70
  end
62
71
  end
63
72
 
64
73
  attr_reader :login_form_header
65
74
 
75
+ def login_required
76
+ if login_return_to_requested_location?
77
+ set_session_value(login_redirect_session_key, request.fullpath)
78
+ end
79
+ super
80
+ end
81
+
66
82
  def after_login_entered_during_multi_phase_login
67
83
  set_notice_now_flash need_password_notice_flash
84
+ if multi_phase_login_forms.length == 1 && (meth = multi_phase_login_forms[0][2])
85
+ send(meth)
86
+ end
68
87
  end
69
88
 
70
89
  def skip_login_field_on_login?
@@ -85,16 +104,37 @@ module Rodauth
85
104
  "<input type='hidden' name=\"#{login_param}\" value=\"#{scope.h param(login_param)}\" />"
86
105
  end
87
106
 
107
+ def render_multi_phase_login_forms
108
+ multi_phase_login_forms.sort.map{|_, form, _| form}.join("\n")
109
+ end
110
+
88
111
  private
89
112
 
90
- def _login
113
+ def _login_form_footer_links
114
+ []
115
+ end
116
+
117
+ def _multi_phase_login_forms
118
+ forms = []
119
+ forms << [10, render("login-form"), nil] if has_password?
120
+ forms
121
+ end
122
+
123
+ def _login_form_footer
124
+ return '' if _login_form_footer_links.empty?
125
+ render('login-form-footer')
126
+ end
127
+
128
+ def _login(auth_type)
129
+ saved_login_redirect = remove_session_value(login_redirect_session_key)
91
130
  transaction do
92
131
  before_login
93
- update_session
132
+ login_session(auth_type)
133
+ yield if block_given?
94
134
  after_login
95
135
  end
96
136
  set_notice_flash login_notice_flash
97
- redirect login_redirect
137
+ redirect(saved_login_redirect || login_redirect)
98
138
  end
99
139
  end
100
140
  end
@@ -2,18 +2,18 @@
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
7
  auth_value_method :login_minimum_length, 3
8
8
  auth_value_method :login_maximum_length, 255
9
- auth_value_method :logins_do_not_match_message, 'logins do not match'
9
+ translatable_method :logins_do_not_match_message, 'logins do not match'
10
10
  auth_value_method :password_confirm_param, 'password-confirm'
11
11
  auth_value_method :password_minimum_length, 6
12
- auth_value_method :passwords_do_not_match_message, 'passwords do not match'
12
+ translatable_method :passwords_do_not_match_message, 'passwords do not match'
13
13
  auth_value_method :require_email_address_logins?, true
14
14
  auth_value_method :require_login_confirmation?, true
15
15
  auth_value_method :require_password_confirmation?, true
16
- auth_value_method :same_as_existing_password_message, "invalid password, same as current password"
16
+ translatable_method :same_as_existing_password_message, "invalid password, same as current password"
17
17
 
18
18
  auth_value_methods(
19
19
  :login_confirm_label,
@@ -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
 
@@ -103,18 +100,13 @@ module Rodauth
103
100
  route(:otp_auth) do |r|
104
101
  require_login
105
102
  require_account_session
106
- require_two_factor_not_authenticated
103
+ require_two_factor_not_authenticated('totp')
107
104
  require_otp_setup
108
105
 
109
106
  if otp_locked_out?
110
107
  set_response_error_status(lockout_error_status)
111
108
  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
109
+ redirect otp_lockout_redirect
118
110
  end
119
111
 
120
112
  before_otp_auth_route
@@ -126,7 +118,7 @@ module Rodauth
126
118
  r.post do
127
119
  if otp_valid_code?(param(otp_auth_param)) && otp_update_last_use
128
120
  before_otp_authentication
129
- two_factor_authenticate(:totp)
121
+ two_factor_authenticate('totp')
130
122
  end
131
123
 
132
124
  otp_record_authentication_failure
@@ -178,7 +170,9 @@ module Rodauth
178
170
  transaction do
179
171
  before_otp_setup
180
172
  otp_add_key
181
- two_factor_update_session(:totp)
173
+ unless two_factor_authenticated?
174
+ two_factor_update_session('totp')
175
+ end
182
176
  after_otp_setup
183
177
  end
184
178
  set_notice_flash otp_setup_notice_flash
@@ -204,7 +198,9 @@ module Rodauth
204
198
  transaction do
205
199
  before_otp_disable
206
200
  otp_remove
207
- two_factor_remove_session
201
+ if two_factor_login_type_match?('totp')
202
+ two_factor_remove_session('totp')
203
+ end
208
204
  after_otp_disable
209
205
  end
210
206
  set_notice_flash otp_disable_notice_flash
@@ -218,20 +214,6 @@ module Rodauth
218
214
  end
219
215
  end
220
216
 
221
- def two_factor_authentication_setup?
222
- return true if super
223
- return false if @otp_tmp_key
224
- otp_exists?
225
- end
226
-
227
- def two_factor_need_setup_redirect
228
- otp_setup_path
229
- end
230
-
231
- def two_factor_auth_required_redirect
232
- otp_auth_path
233
- end
234
-
235
217
  def two_factor_remove
236
218
  super
237
219
  otp_remove
@@ -242,19 +224,6 @@ module Rodauth
242
224
  otp_remove_auth_failures
243
225
  end
244
226
 
245
- def otp_auth_form_footer
246
- super if defined?(super)
247
- end
248
-
249
- def otp_lockout_redirect
250
- return super if defined?(super)
251
- nil
252
- end
253
-
254
- def otp_lockout_error_flash
255
- "Authentication code use locked out due to numerous failures.#{super if defined?(super)}"
256
- end
257
-
258
227
  def require_otp_setup
259
228
  unless otp_exists?
260
229
  set_redirect_error_status(two_factor_not_setup_error_status)
@@ -272,11 +241,11 @@ module Rodauth
272
241
  ot_pass = ot_pass.gsub(/\s+/, '')
273
242
  if drift = otp_drift
274
243
  if otp.respond_to?(:verify_with_drift)
244
+ # :nocov:
275
245
  otp.verify_with_drift(ot_pass, drift)
276
- else
277
246
  # :nocov:
247
+ else
278
248
  otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
279
- # :nocov:
280
249
  end
281
250
  else
282
251
  otp.verify(ot_pass)
@@ -285,6 +254,7 @@ module Rodauth
285
254
 
286
255
  def otp_remove
287
256
  otp_key_ds.delete
257
+ @otp_key = nil
288
258
  super if defined?(super)
289
259
  end
290
260
 
@@ -316,7 +286,7 @@ module Rodauth
316
286
  end
317
287
 
318
288
  def otp_issuer
319
- request.host
289
+ domain
320
290
  end
321
291
 
322
292
  def otp_provisioning_name
@@ -339,8 +309,37 @@ module Rodauth
339
309
  !!hmac_secret
340
310
  end
341
311
 
312
+ def possible_authentication_methods
313
+ methods = super
314
+ methods << 'totp' if otp_exists? && !@otp_tmp_key
315
+ methods
316
+ end
317
+
342
318
  private
343
319
 
320
+ def _two_factor_auth_links
321
+ links = super
322
+ links << [20, otp_auth_path, otp_auth_link_text] if otp_exists? && !otp_locked_out?
323
+ links
324
+ end
325
+
326
+ def _two_factor_setup_links
327
+ links = super
328
+ links << [20, otp_setup_path, otp_setup_link_text] unless otp_exists?
329
+ links
330
+ end
331
+
332
+ def _two_factor_remove_links
333
+ links = super
334
+ links << [20, otp_disable_path, otp_disable_link_text] if otp_exists?
335
+ links
336
+ end
337
+
338
+ def _two_factor_remove_all_from_session
339
+ two_factor_remove_session('totp')
340
+ super
341
+ end
342
+
344
343
  def clear_cached_otp
345
344
  remove_instance_variable(:@otp) if defined?(@otp)
346
345
  end
@@ -364,29 +363,20 @@ module Rodauth
364
363
  end
365
364
 
366
365
  if ROTP::Base32.respond_to?(:random_base32)
367
- # :nocov:
368
366
  def otp_new_secret
369
367
  ROTP::Base32.random_base32.downcase
370
368
  end
371
- # :nocov:
372
369
  else
370
+ # :nocov:
373
371
  def otp_new_secret
374
372
  ROTP::Base32.random.downcase
375
373
  end
374
+ # :nocov:
376
375
  end
377
376
 
378
- if RUBY_VERSION < '1.9'
379
- # :nocov:
380
- def base32_encode(data, length)
381
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
382
- length.times.map{|i|chars[data[i] % 32].chr}.join
383
- end
384
- # :nocov:
385
- else
386
- def base32_encode(data, length)
387
- chars = 'abcdefghijklmnopqrstuvwxyz234567'
388
- length.times.map{|i|chars[data[i].ord % 32]}.join
389
- end
377
+ def base32_encode(data, length)
378
+ chars = 'abcdefghijklmnopqrstuvwxyz234567'
379
+ length.times.map{|i|chars[data[i].ord % 32]}.join
390
380
  end
391
381
 
392
382
  def _otp_tmp_key(secret)