rodauth 1.23.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +132 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +207 -79
- data/doc/account_expiration.rdoc +12 -26
- data/doc/active_sessions.rdoc +49 -0
- data/doc/audit_logging.rdoc +44 -0
- data/doc/base.rdoc +74 -128
- data/doc/change_login.rdoc +7 -14
- data/doc/change_password.rdoc +9 -13
- data/doc/change_password_notify.rdoc +2 -2
- data/doc/close_account.rdoc +9 -16
- data/doc/confirm_password.rdoc +12 -5
- data/doc/create_account.rdoc +11 -22
- data/doc/disallow_password_reuse.rdoc +6 -13
- data/doc/email_auth.rdoc +15 -14
- data/doc/email_base.rdoc +5 -15
- data/doc/http_basic_auth.rdoc +10 -1
- data/doc/jwt.rdoc +22 -22
- data/doc/jwt_cors.rdoc +2 -3
- data/doc/jwt_refresh.rdoc +12 -8
- data/doc/lockout.rdoc +17 -15
- data/doc/login.rdoc +10 -2
- data/doc/login_password_requirements_base.rdoc +15 -37
- data/doc/logout.rdoc +2 -2
- data/doc/otp.rdoc +24 -19
- data/doc/password_complexity.rdoc +10 -26
- data/doc/password_expiration.rdoc +11 -25
- data/doc/password_grace_period.rdoc +16 -2
- data/doc/recovery_codes.rdoc +18 -12
- data/doc/release_notes/2.0.0.txt +361 -0
- data/doc/remember.rdoc +40 -64
- data/doc/reset_password.rdoc +12 -9
- data/doc/session_expiration.rdoc +1 -0
- data/doc/single_session.rdoc +16 -25
- data/doc/sms_codes.rdoc +24 -14
- data/doc/two_factor_base.rdoc +60 -22
- data/doc/verify_account.rdoc +14 -12
- data/doc/verify_account_grace_period.rdoc +6 -2
- data/doc/verify_login_change.rdoc +9 -8
- data/doc/webauthn.rdoc +115 -0
- data/doc/webauthn_login.rdoc +15 -0
- data/doc/webauthn_verify_account.rdoc +9 -0
- data/javascript/webauthn_auth.js +45 -0
- data/javascript/webauthn_setup.js +35 -0
- data/lib/roda/plugins/rodauth.rb +1 -1
- data/lib/rodauth.rb +29 -24
- data/lib/rodauth/features/account_expiration.rb +5 -5
- data/lib/rodauth/features/active_sessions.rb +160 -0
- data/lib/rodauth/features/audit_logging.rb +96 -0
- data/lib/rodauth/features/base.rb +131 -47
- data/lib/rodauth/features/change_password_notify.rb +1 -1
- data/lib/rodauth/features/confirm_password.rb +40 -2
- data/lib/rodauth/features/create_account.rb +7 -13
- data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
- data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
- data/lib/rodauth/features/email_auth.rb +29 -27
- data/lib/rodauth/features/email_base.rb +3 -3
- data/lib/rodauth/features/http_basic_auth.rb +44 -37
- data/lib/rodauth/features/jwt.rb +51 -8
- data/lib/rodauth/features/jwt_refresh.rb +3 -3
- data/lib/rodauth/features/lockout.rb +11 -13
- data/lib/rodauth/features/login.rb +48 -8
- data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
- data/lib/rodauth/features/otp.rb +71 -81
- data/lib/rodauth/features/password_complexity.rb +4 -11
- data/lib/rodauth/features/password_expiration.rb +1 -1
- data/lib/rodauth/features/password_grace_period.rb +17 -10
- data/lib/rodauth/features/recovery_codes.rb +47 -51
- data/lib/rodauth/features/remember.rb +11 -27
- data/lib/rodauth/features/reset_password.rb +25 -25
- data/lib/rodauth/features/session_expiration.rb +6 -4
- data/lib/rodauth/features/single_session.rb +7 -5
- data/lib/rodauth/features/sms_codes.rb +58 -67
- data/lib/rodauth/features/two_factor_base.rb +132 -28
- data/lib/rodauth/features/verify_account.rb +23 -20
- data/lib/rodauth/features/verify_account_grace_period.rb +19 -8
- data/lib/rodauth/features/verify_login_change.rb +11 -10
- data/lib/rodauth/features/webauthn.rb +507 -0
- data/lib/rodauth/features/webauthn_login.rb +70 -0
- data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
- data/lib/rodauth/version.rb +2 -2
- data/templates/button.str +1 -3
- data/templates/change-login.str +1 -2
- data/templates/change-password.str +3 -5
- data/templates/close-account.str +2 -2
- data/templates/confirm-password.str +1 -1
- data/templates/create-account.str +1 -1
- data/templates/email-auth-request-form.str +1 -2
- data/templates/email-auth.str +1 -1
- data/templates/global-logout-field.str +6 -0
- data/templates/login-confirm-field.str +2 -4
- data/templates/login-display.str +3 -2
- data/templates/login-field.str +2 -4
- data/templates/login-form-footer.str +6 -0
- data/templates/login-form.str +7 -0
- data/templates/login.str +1 -9
- data/templates/logout.str +1 -1
- data/templates/multi-phase-login.str +3 -0
- data/templates/otp-auth-code-field.str +5 -3
- data/templates/otp-auth.str +1 -1
- data/templates/otp-disable.str +1 -1
- data/templates/otp-setup.str +3 -3
- data/templates/password-confirm-field.str +2 -4
- data/templates/password-field.str +2 -4
- data/templates/recovery-auth.str +3 -6
- data/templates/recovery-codes.str +1 -1
- data/templates/remember.str +15 -20
- data/templates/reset-password-request.str +2 -2
- data/templates/reset-password.str +1 -2
- data/templates/sms-auth.str +1 -1
- data/templates/sms-code-field.str +5 -3
- data/templates/sms-confirm.str +1 -2
- data/templates/sms-disable.str +1 -2
- data/templates/sms-request.str +1 -1
- data/templates/sms-setup.str +6 -4
- data/templates/two-factor-auth.str +5 -0
- data/templates/two-factor-disable.str +6 -0
- data/templates/two-factor-manage.str +16 -0
- data/templates/unlock-account-request.str +2 -2
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-resend.str +1 -1
- data/templates/verify-account.str +1 -2
- data/templates/verify-login-change.str +1 -1
- data/templates/webauthn-auth.str +11 -0
- data/templates/webauthn-remove.str +14 -0
- data/templates/webauthn-setup.str +12 -0
- metadata +64 -11
- data/doc/verify_change_login.rdoc +0 -11
- 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
|
-
|
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
|
-
|
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
|
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,
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
129
|
+
autologin_session('unlock_account')
|
131
130
|
end
|
132
131
|
end
|
133
132
|
|
134
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -19,62 +19,59 @@ module Rodauth
|
|
19
19
|
before 'otp_setup'
|
20
20
|
before 'otp_disable'
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
34
|
-
error_flash "Error logging in via
|
35
|
-
error_flash "Error setting up
|
36
|
-
error_flash "You have 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 "
|
39
|
-
notice_flash "
|
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
|
41
|
+
view 'otp-disable', 'Disable TOTP Authentication', 'otp_disable'
|
47
42
|
view 'otp-auth', 'Enter Authentication Code', 'otp_auth'
|
48
|
-
view 'otp-setup', '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
|
-
|
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,
|
54
|
+
auth_value_method :otp_drift, 30
|
56
55
|
auth_value_method :otp_interval, nil
|
57
|
-
|
58
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
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)
|