rodauth 1.23.0 → 2.0.0

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