rodauth 1.22.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +190 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +210 -80
- 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 +75 -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 +6 -15
- data/doc/guides/admin_activation.rdoc +46 -0
- data/doc/guides/already_authenticated.rdoc +10 -0
- data/doc/guides/alternative_login.rdoc +46 -0
- data/doc/guides/create_account_programmatically.rdoc +38 -0
- data/doc/guides/delay_password.rdoc +25 -0
- data/doc/guides/email_only.rdoc +16 -0
- data/doc/guides/i18n.rdoc +26 -0
- data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
- data/doc/guides/links.rdoc +12 -0
- data/doc/guides/login_return.rdoc +37 -0
- data/doc/guides/password_column.rdoc +25 -0
- data/doc/guides/password_confirmation.rdoc +37 -0
- data/doc/guides/password_requirements.rdoc +30 -0
- data/doc/guides/paths.rdoc +36 -0
- data/doc/guides/query_params.rdoc +9 -0
- data/doc/guides/redirects.rdoc +17 -0
- data/doc/guides/registration_field.rdoc +68 -0
- data/doc/guides/require_mfa.rdoc +30 -0
- data/doc/guides/reset_password_autologin.rdoc +21 -0
- data/doc/guides/status_column.rdoc +28 -0
- data/doc/guides/totp_or_recovery.rdoc +16 -0
- 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 +23 -8
- data/doc/lockout.rdoc +17 -15
- data/doc/login.rdoc +17 -2
- data/doc/login_password_requirements_base.rdoc +18 -37
- data/doc/logout.rdoc +2 -2
- data/doc/otp.rdoc +25 -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/1.23.0.txt +32 -0
- data/doc/release_notes/2.0.0.txt +361 -0
- data/doc/release_notes/2.1.0.txt +31 -0
- data/doc/release_notes/2.2.0.txt +39 -0
- data/doc/release_notes/2.3.0.txt +37 -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 +36 -28
- data/lib/rodauth/features/account_expiration.rb +5 -5
- data/lib/rodauth/features/active_sessions.rb +158 -0
- data/lib/rodauth/features/audit_logging.rb +98 -0
- data/lib/rodauth/features/base.rb +144 -43
- data/lib/rodauth/features/change_password_notify.rb +2 -2
- data/lib/rodauth/features/close_account.rb +8 -6
- data/lib/rodauth/features/confirm_password.rb +40 -2
- data/lib/rodauth/features/create_account.rb +8 -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 +31 -30
- data/lib/rodauth/features/email_base.rb +9 -4
- data/lib/rodauth/features/http_basic_auth.rb +55 -35
- data/lib/rodauth/features/jwt.rb +63 -16
- data/lib/rodauth/features/jwt_cors.rb +15 -15
- data/lib/rodauth/features/jwt_refresh.rb +42 -13
- data/lib/rodauth/features/lockout.rb +12 -14
- data/lib/rodauth/features/login.rb +64 -15
- data/lib/rodauth/features/login_password_requirements_base.rb +13 -8
- data/lib/rodauth/features/otp.rb +77 -80
- data/lib/rodauth/features/password_complexity.rb +8 -13
- data/lib/rodauth/features/password_expiration.rb +2 -2
- data/lib/rodauth/features/password_grace_period.rb +17 -10
- data/lib/rodauth/features/recovery_codes.rb +49 -53
- data/lib/rodauth/features/remember.rb +11 -27
- data/lib/rodauth/features/reset_password.rb +26 -26
- data/lib/rodauth/features/session_expiration.rb +7 -10
- data/lib/rodauth/features/single_session.rb +8 -6
- data/lib/rodauth/features/sms_codes.rb +62 -72
- data/lib/rodauth/features/two_factor_base.rb +134 -30
- data/lib/rodauth/features/verify_account.rb +29 -21
- data/lib/rodauth/features/verify_account_grace_period.rb +18 -9
- data/lib/rodauth/features/verify_login_change.rb +12 -11
- data/lib/rodauth/features/webauthn.rb +505 -0
- data/lib/rodauth/features/webauthn_login.rb +70 -0
- data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
- data/lib/rodauth/migrations.rb +16 -5
- 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 +2 -3
- 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 +3 -3
- 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 +4 -4
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-resend.str +3 -3
- 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 +94 -54
- data/Rakefile +0 -179
- data/doc/verify_change_login.rdoc +0 -11
- data/lib/rodauth/features/verify_change_login.rb +0 -20
- data/spec/account_expiration_spec.rb +0 -225
- data/spec/all.rb +0 -1
- data/spec/change_login_spec.rb +0 -156
- data/spec/change_password_notify_spec.rb +0 -33
- data/spec/change_password_spec.rb +0 -202
- data/spec/close_account_spec.rb +0 -162
- data/spec/confirm_password_spec.rb +0 -70
- data/spec/create_account_spec.rb +0 -127
- data/spec/disallow_common_passwords_spec.rb +0 -93
- data/spec/disallow_password_reuse_spec.rb +0 -179
- data/spec/email_auth_spec.rb +0 -285
- data/spec/http_basic_auth_spec.rb +0 -143
- data/spec/jwt_cors_spec.rb +0 -57
- data/spec/jwt_refresh_spec.rb +0 -256
- data/spec/jwt_spec.rb +0 -235
- data/spec/lockout_spec.rb +0 -250
- data/spec/login_spec.rb +0 -328
- data/spec/migrate/001_tables.rb +0 -184
- data/spec/migrate/002_account_password_hash_column.rb +0 -11
- data/spec/migrate_password/001_tables.rb +0 -73
- data/spec/migrate_travis/001_tables.rb +0 -141
- data/spec/password_complexity_spec.rb +0 -109
- data/spec/password_expiration_spec.rb +0 -244
- data/spec/password_grace_period_spec.rb +0 -93
- data/spec/remember_spec.rb +0 -451
- data/spec/reset_password_spec.rb +0 -229
- data/spec/rodauth_spec.rb +0 -343
- data/spec/session_expiration_spec.rb +0 -58
- data/spec/single_session_spec.rb +0 -127
- data/spec/spec_helper.rb +0 -327
- data/spec/two_factor_spec.rb +0 -1462
- data/spec/update_password_hash_spec.rb +0 -40
- data/spec/verify_account_grace_period_spec.rb +0 -171
- data/spec/verify_account_spec.rb +0 -240
- data/spec/verify_change_login_spec.rb +0 -46
- data/spec/verify_login_change_spec.rb +0 -232
- data/spec/views/layout-other.str +0 -11
- data/spec/views/layout.str +0 -11
- data/spec/views/login.str +0 -21
|
@@ -2,18 +2,20 @@
|
|
|
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
|
+
auth_value_method :login_email_regexp, /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
|
|
7
8
|
auth_value_method :login_minimum_length, 3
|
|
8
9
|
auth_value_method :login_maximum_length, 255
|
|
9
|
-
|
|
10
|
+
translatable_method :login_not_valid_email_message, 'not a valid email address'
|
|
11
|
+
translatable_method :logins_do_not_match_message, 'logins do not match'
|
|
10
12
|
auth_value_method :password_confirm_param, 'password-confirm'
|
|
11
13
|
auth_value_method :password_minimum_length, 6
|
|
12
|
-
|
|
14
|
+
translatable_method :passwords_do_not_match_message, 'passwords do not match'
|
|
13
15
|
auth_value_method :require_email_address_logins?, true
|
|
14
16
|
auth_value_method :require_login_confirmation?, true
|
|
15
17
|
auth_value_method :require_password_confirmation?, true
|
|
16
|
-
|
|
18
|
+
translatable_method :same_as_existing_password_message, "invalid password, same as current password"
|
|
17
19
|
|
|
18
20
|
auth_value_methods(
|
|
19
21
|
:login_confirm_label,
|
|
@@ -28,6 +30,7 @@ module Rodauth
|
|
|
28
30
|
|
|
29
31
|
auth_methods(
|
|
30
32
|
:login_meets_requirements?,
|
|
33
|
+
:login_valid_email?,
|
|
31
34
|
:password_hash,
|
|
32
35
|
:password_meets_requirements?,
|
|
33
36
|
:set_password
|
|
@@ -104,13 +107,15 @@ module Rodauth
|
|
|
104
107
|
|
|
105
108
|
def login_meets_email_requirements?(login)
|
|
106
109
|
return true unless require_email_address_logins?
|
|
107
|
-
if login
|
|
108
|
-
|
|
109
|
-
end
|
|
110
|
-
@login_requirement_message = 'not a valid email address'
|
|
110
|
+
return true if login_valid_email?(login)
|
|
111
|
+
@login_requirement_message = login_not_valid_email_message
|
|
111
112
|
return false
|
|
112
113
|
end
|
|
113
114
|
|
|
115
|
+
def login_valid_email?(login)
|
|
116
|
+
login =~ login_email_regexp
|
|
117
|
+
end
|
|
118
|
+
|
|
114
119
|
def password_meets_length_requirements?(password)
|
|
115
120
|
return true if password_minimum_length <= password.length
|
|
116
121
|
@password_requirement_message = password_too_short_message
|
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
|
|
|
@@ -82,6 +79,7 @@ module Rodauth
|
|
|
82
79
|
:otp,
|
|
83
80
|
:otp_exists?,
|
|
84
81
|
:otp_key,
|
|
82
|
+
:otp_last_use,
|
|
85
83
|
:otp_locked_out?,
|
|
86
84
|
:otp_new_secret,
|
|
87
85
|
:otp_provisioning_name,
|
|
@@ -103,18 +101,13 @@ module Rodauth
|
|
|
103
101
|
route(:otp_auth) do |r|
|
|
104
102
|
require_login
|
|
105
103
|
require_account_session
|
|
106
|
-
require_two_factor_not_authenticated
|
|
104
|
+
require_two_factor_not_authenticated('totp')
|
|
107
105
|
require_otp_setup
|
|
108
106
|
|
|
109
107
|
if otp_locked_out?
|
|
110
108
|
set_response_error_status(lockout_error_status)
|
|
111
109
|
set_redirect_error_flash otp_lockout_error_flash
|
|
112
|
-
|
|
113
|
-
redirect redir
|
|
114
|
-
else
|
|
115
|
-
clear_session
|
|
116
|
-
redirect require_login_redirect
|
|
117
|
-
end
|
|
110
|
+
redirect otp_lockout_redirect
|
|
118
111
|
end
|
|
119
112
|
|
|
120
113
|
before_otp_auth_route
|
|
@@ -126,7 +119,7 @@ module Rodauth
|
|
|
126
119
|
r.post do
|
|
127
120
|
if otp_valid_code?(param(otp_auth_param)) && otp_update_last_use
|
|
128
121
|
before_otp_authentication
|
|
129
|
-
two_factor_authenticate(
|
|
122
|
+
two_factor_authenticate('totp')
|
|
130
123
|
end
|
|
131
124
|
|
|
132
125
|
otp_record_authentication_failure
|
|
@@ -178,7 +171,9 @@ module Rodauth
|
|
|
178
171
|
transaction do
|
|
179
172
|
before_otp_setup
|
|
180
173
|
otp_add_key
|
|
181
|
-
|
|
174
|
+
unless two_factor_authenticated?
|
|
175
|
+
two_factor_update_session('totp')
|
|
176
|
+
end
|
|
182
177
|
after_otp_setup
|
|
183
178
|
end
|
|
184
179
|
set_notice_flash otp_setup_notice_flash
|
|
@@ -204,7 +199,9 @@ module Rodauth
|
|
|
204
199
|
transaction do
|
|
205
200
|
before_otp_disable
|
|
206
201
|
otp_remove
|
|
207
|
-
|
|
202
|
+
if two_factor_login_type_match?('totp')
|
|
203
|
+
two_factor_remove_session('totp')
|
|
204
|
+
end
|
|
208
205
|
after_otp_disable
|
|
209
206
|
end
|
|
210
207
|
set_notice_flash otp_disable_notice_flash
|
|
@@ -218,18 +215,6 @@ module Rodauth
|
|
|
218
215
|
end
|
|
219
216
|
end
|
|
220
217
|
|
|
221
|
-
def two_factor_authentication_setup?
|
|
222
|
-
super || otp_exists?
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def two_factor_need_setup_redirect
|
|
226
|
-
"#{prefix}/#{otp_setup_route}"
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def two_factor_auth_required_redirect
|
|
230
|
-
"#{prefix}/#{otp_auth_route}"
|
|
231
|
-
end
|
|
232
|
-
|
|
233
218
|
def two_factor_remove
|
|
234
219
|
super
|
|
235
220
|
otp_remove
|
|
@@ -240,19 +225,6 @@ module Rodauth
|
|
|
240
225
|
otp_remove_auth_failures
|
|
241
226
|
end
|
|
242
227
|
|
|
243
|
-
def otp_auth_form_footer
|
|
244
|
-
super if defined?(super)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def otp_lockout_redirect
|
|
248
|
-
return super if defined?(super)
|
|
249
|
-
nil
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def otp_lockout_error_flash
|
|
253
|
-
"Authentication code use locked out due to numerous failures.#{super if defined?(super)}"
|
|
254
|
-
end
|
|
255
|
-
|
|
256
228
|
def require_otp_setup
|
|
257
229
|
unless otp_exists?
|
|
258
230
|
set_redirect_error_status(two_factor_not_setup_error_status)
|
|
@@ -270,11 +242,11 @@ module Rodauth
|
|
|
270
242
|
ot_pass = ot_pass.gsub(/\s+/, '')
|
|
271
243
|
if drift = otp_drift
|
|
272
244
|
if otp.respond_to?(:verify_with_drift)
|
|
245
|
+
# :nocov:
|
|
273
246
|
otp.verify_with_drift(ot_pass, drift)
|
|
274
|
-
else
|
|
275
247
|
# :nocov:
|
|
248
|
+
else
|
|
276
249
|
otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
|
|
277
|
-
# :nocov:
|
|
278
250
|
end
|
|
279
251
|
else
|
|
280
252
|
otp.verify(ot_pass)
|
|
@@ -283,7 +255,7 @@ module Rodauth
|
|
|
283
255
|
|
|
284
256
|
def otp_remove
|
|
285
257
|
otp_key_ds.delete
|
|
286
|
-
|
|
258
|
+
@otp_key = nil
|
|
287
259
|
end
|
|
288
260
|
|
|
289
261
|
def otp_add_key
|
|
@@ -297,6 +269,10 @@ module Rodauth
|
|
|
297
269
|
update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
|
|
298
270
|
end
|
|
299
271
|
|
|
272
|
+
def otp_last_use
|
|
273
|
+
convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
|
|
274
|
+
end
|
|
275
|
+
|
|
300
276
|
def otp_record_authentication_failure
|
|
301
277
|
otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
|
|
302
278
|
end
|
|
@@ -314,7 +290,7 @@ module Rodauth
|
|
|
314
290
|
end
|
|
315
291
|
|
|
316
292
|
def otp_issuer
|
|
317
|
-
|
|
293
|
+
domain
|
|
318
294
|
end
|
|
319
295
|
|
|
320
296
|
def otp_provisioning_name
|
|
@@ -337,8 +313,37 @@ module Rodauth
|
|
|
337
313
|
!!hmac_secret
|
|
338
314
|
end
|
|
339
315
|
|
|
316
|
+
def possible_authentication_methods
|
|
317
|
+
methods = super
|
|
318
|
+
methods << 'totp' if otp_exists? && !@otp_tmp_key
|
|
319
|
+
methods
|
|
320
|
+
end
|
|
321
|
+
|
|
340
322
|
private
|
|
341
323
|
|
|
324
|
+
def _two_factor_auth_links
|
|
325
|
+
links = super
|
|
326
|
+
links << [20, otp_auth_path, otp_auth_link_text] if otp_exists? && !otp_locked_out?
|
|
327
|
+
links
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def _two_factor_setup_links
|
|
331
|
+
links = super
|
|
332
|
+
links << [20, otp_setup_path, otp_setup_link_text] unless otp_exists?
|
|
333
|
+
links
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def _two_factor_remove_links
|
|
337
|
+
links = super
|
|
338
|
+
links << [20, otp_disable_path, otp_disable_link_text] if otp_exists?
|
|
339
|
+
links
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def _two_factor_remove_all_from_session
|
|
343
|
+
two_factor_remove_session('totp')
|
|
344
|
+
super
|
|
345
|
+
end
|
|
346
|
+
|
|
342
347
|
def clear_cached_otp
|
|
343
348
|
remove_instance_variable(:@otp) if defined?(@otp)
|
|
344
349
|
end
|
|
@@ -362,32 +367,24 @@ module Rodauth
|
|
|
362
367
|
end
|
|
363
368
|
|
|
364
369
|
if ROTP::Base32.respond_to?(:random_base32)
|
|
365
|
-
# :nocov:
|
|
366
370
|
def otp_new_secret
|
|
367
371
|
ROTP::Base32.random_base32.downcase
|
|
368
372
|
end
|
|
369
|
-
# :nocov:
|
|
370
373
|
else
|
|
374
|
+
# :nocov:
|
|
371
375
|
def otp_new_secret
|
|
372
376
|
ROTP::Base32.random.downcase
|
|
373
377
|
end
|
|
378
|
+
# :nocov:
|
|
374
379
|
end
|
|
375
380
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
chars = 'abcdefghijklmnopqrstuvwxyz234567'
|
|
380
|
-
length.times.map{|i|chars[data[i] % 32].chr}.join
|
|
381
|
-
end
|
|
382
|
-
# :nocov:
|
|
383
|
-
else
|
|
384
|
-
def base32_encode(data, length)
|
|
385
|
-
chars = 'abcdefghijklmnopqrstuvwxyz234567'
|
|
386
|
-
length.times.map{|i|chars[data[i].ord % 32]}.join
|
|
387
|
-
end
|
|
381
|
+
def base32_encode(data, length)
|
|
382
|
+
chars = 'abcdefghijklmnopqrstuvwxyz234567'
|
|
383
|
+
length.times.map{|i|chars[data[i].ord % 32]}.join
|
|
388
384
|
end
|
|
389
385
|
|
|
390
386
|
def _otp_tmp_key(secret)
|
|
387
|
+
@otp_tmp_key = true
|
|
391
388
|
@otp_user_key = nil
|
|
392
389
|
@otp_key = secret
|
|
393
390
|
end
|
|
@@ -11,13 +11,10 @@ module Rodauth
|
|
|
11
11
|
auth_value_method :password_max_length_for_groups_check, 11
|
|
12
12
|
auth_value_method :password_max_repeating_characters, 3
|
|
13
13
|
auth_value_method :password_invalid_pattern, Regexp.union([/qwerty/i, /azerty/i, /asdf/i, /zxcv/i] + (1..8).map{|i| /#{i}#{i+1}#{(i+2)%10}/})
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
auth_value_methods(
|
|
19
|
-
:password_too_many_repeating_characters_message
|
|
20
|
-
)
|
|
14
|
+
translatable_method :password_not_enough_character_groups_message, "does not include uppercase letters, lowercase letters, and numbers"
|
|
15
|
+
translatable_method :password_invalid_pattern_message, "includes common character sequence"
|
|
16
|
+
translatable_method :password_in_dictionary_message, "is a word in a dictionary"
|
|
17
|
+
translatable_method :password_too_many_repeating_characters_message, "contains too many of the same character in a row"
|
|
21
18
|
|
|
22
19
|
def password_meets_requirements?(password)
|
|
23
20
|
super && \
|
|
@@ -29,14 +26,16 @@ module Rodauth
|
|
|
29
26
|
|
|
30
27
|
def post_configure
|
|
31
28
|
super
|
|
32
|
-
return if
|
|
29
|
+
return if method(:password_dictionary).owner != Rodauth::PasswordComplexity
|
|
33
30
|
|
|
34
31
|
case password_dictionary_file
|
|
35
32
|
when false
|
|
36
|
-
|
|
33
|
+
# nothing
|
|
37
34
|
when nil
|
|
38
35
|
default_dictionary_file = '/usr/share/dict/words'
|
|
36
|
+
# :nocov:
|
|
39
37
|
if File.file?(default_dictionary_file)
|
|
38
|
+
# :nocov:
|
|
40
39
|
words = File.read(default_dictionary_file)
|
|
41
40
|
end
|
|
42
41
|
else
|
|
@@ -73,10 +72,6 @@ module Rodauth
|
|
|
73
72
|
false
|
|
74
73
|
end
|
|
75
74
|
|
|
76
|
-
def password_too_many_repeating_characters_message
|
|
77
|
-
"contains #{password_max_repeating_characters} or more of the same character in a row"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
75
|
def password_not_in_dictionary?(password)
|
|
81
76
|
return true unless dict = password_dictionary
|
|
82
77
|
return true unless password =~ /\A(?:\d*)([A-Za-z!@$+|][A-Za-z!@$+|0134578]+[A-Za-z!@$+|])(?:\d*)\z/
|
|
@@ -8,7 +8,7 @@ module Rodauth
|
|
|
8
8
|
error_flash "Your password cannot be changed yet", 'password_not_changeable_yet'
|
|
9
9
|
|
|
10
10
|
redirect :password_not_changeable_yet
|
|
11
|
-
redirect(:password_change_needed){
|
|
11
|
+
redirect(:password_change_needed){change_password_path}
|
|
12
12
|
|
|
13
13
|
auth_value_method :allow_password_change_after, -86400
|
|
14
14
|
auth_value_method :require_password_change_after, 90*86400
|
|
@@ -38,7 +38,7 @@ module Rodauth
|
|
|
38
38
|
|
|
39
39
|
def set_password(password)
|
|
40
40
|
update_password_changed_at
|
|
41
|
-
|
|
41
|
+
set_session_value(password_changed_at_session_key, Time.now.to_i)
|
|
42
42
|
super
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -5,6 +5,8 @@ module Rodauth
|
|
|
5
5
|
auth_value_method :password_grace_period, 300
|
|
6
6
|
session_key :last_password_entry_session_key, :last_password_entry
|
|
7
7
|
|
|
8
|
+
auth_methods :password_recently_entered?
|
|
9
|
+
|
|
8
10
|
def modifications_require_password?
|
|
9
11
|
return false unless super
|
|
10
12
|
!password_recently_entered?
|
|
@@ -17,6 +19,16 @@ module Rodauth
|
|
|
17
19
|
v
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
def password_recently_entered?
|
|
23
|
+
return false unless last_password_entry = session[last_password_entry_session_key]
|
|
24
|
+
last_password_entry + password_grace_period > Time.now.to_i
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update_session
|
|
28
|
+
super
|
|
29
|
+
set_session_value(last_password_entry_session_key, @last_password_entry) if defined?(@last_password_entry)
|
|
30
|
+
end
|
|
31
|
+
|
|
20
32
|
private
|
|
21
33
|
|
|
22
34
|
def after_create_account
|
|
@@ -29,18 +41,13 @@ module Rodauth
|
|
|
29
41
|
@last_password_entry = Time.now.to_i
|
|
30
42
|
end
|
|
31
43
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
session[last_password_entry_session_key] = @last_password_entry if defined?(@last_password_entry)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def password_recently_entered?
|
|
38
|
-
return false unless last_password_entry = session[last_password_entry_session_key]
|
|
39
|
-
last_password_entry + password_grace_period > Time.now.to_i
|
|
44
|
+
def set_last_password_entry
|
|
45
|
+
set_session_value(last_password_entry_session_key, Time.now.to_i)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
|
-
def
|
|
43
|
-
|
|
48
|
+
def require_password_authentication?
|
|
49
|
+
return true if defined?(super) && super
|
|
50
|
+
!password_recently_entered?
|
|
44
51
|
end
|
|
45
52
|
end
|
|
46
53
|
end
|