rodauth 1.21.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +182 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +211 -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 +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 +22 -0
- data/doc/jwt_refresh.rdoc +18 -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 +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.22.0.txt +11 -0
- 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/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/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 +53 -0
- data/lib/rodauth/features/jwt_refresh.rb +32 -9
- data/lib/rodauth/features/lockout.rb +12 -14
- data/lib/rodauth/features/login.rb +54 -10
- data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
- 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 +6 -4
- 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/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-email.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-email.str +1 -1
- 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-email.str +1 -1
- data/templates/unlock-account-request.str +4 -4
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-email.str +1 -1
- data/templates/verify-account-resend.str +3 -3
- data/templates/verify-account.str +1 -2
- data/templates/verify-login-change-email.str +2 -1
- 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 +110 -52
- 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_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,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
|
|
@@ -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
|
@@ -17,14 +17,14 @@ module Rodauth
|
|
17
17
|
button 'Authenticate via Recovery Code', 'recovery_auth'
|
18
18
|
button 'View Authentication Recovery Codes', 'view_recovery_codes'
|
19
19
|
|
20
|
-
error_flash "Error authenticating via recovery code
|
21
|
-
error_flash "Unable to add recovery codes
|
22
|
-
error_flash "Unable to view recovery codes
|
20
|
+
error_flash "Error authenticating via recovery code", 'invalid_recovery_code'
|
21
|
+
error_flash "Unable to add recovery codes", 'add_recovery_codes'
|
22
|
+
error_flash "Unable to view recovery codes", 'view_recovery_codes'
|
23
23
|
|
24
|
-
notice_flash "Additional authentication recovery codes have been added
|
24
|
+
notice_flash "Additional authentication recovery codes have been added", 'recovery_codes_added'
|
25
25
|
|
26
|
-
redirect(:recovery_auth){
|
27
|
-
redirect(:add_recovery_codes){
|
26
|
+
redirect(:recovery_auth){recovery_auth_path}
|
27
|
+
redirect(:add_recovery_codes){recovery_codes_path}
|
28
28
|
|
29
29
|
loaded_templates %w'add-recovery-codes recovery-auth recovery-codes password-field'
|
30
30
|
view 'add-recovery-codes', 'Authentication Recovery Codes', 'add_recovery_codes'
|
@@ -32,15 +32,19 @@ module Rodauth
|
|
32
32
|
view 'recovery-codes', 'View Authentication Recovery Codes', 'recovery_codes'
|
33
33
|
|
34
34
|
auth_value_method :add_recovery_codes_param, 'add'
|
35
|
-
|
36
|
-
auth_value_method :
|
35
|
+
translatable_method :add_recovery_codes_heading, '<h2>Add Additional Recovery Codes</h2>'
|
36
|
+
auth_value_method :auto_add_recovery_codes?, false
|
37
|
+
translatable_method :invalid_recovery_code_message, "Invalid recovery code"
|
37
38
|
auth_value_method :recovery_codes_limit, 16
|
38
39
|
auth_value_method :recovery_codes_column, :code
|
39
40
|
auth_value_method :recovery_codes_id_column, :id
|
40
|
-
|
41
|
+
translatable_method :recovery_codes_label, 'Recovery Code'
|
41
42
|
auth_value_method :recovery_codes_param, 'recovery-code'
|
42
43
|
auth_value_method :recovery_codes_table, :account_recovery_codes
|
43
44
|
|
45
|
+
translatable_method :recovery_auth_link_text, "Authenticate Using Recovery Code"
|
46
|
+
translatable_method :recovery_codes_link_text, "View Authentication Recovery Codes"
|
47
|
+
|
44
48
|
auth_cached_method :recovery_codes
|
45
49
|
|
46
50
|
auth_value_methods(
|
@@ -59,7 +63,7 @@ module Rodauth
|
|
59
63
|
require_login
|
60
64
|
require_account_session
|
61
65
|
require_two_factor_setup
|
62
|
-
require_two_factor_not_authenticated
|
66
|
+
require_two_factor_not_authenticated('recovery_code')
|
63
67
|
before_recovery_auth_route
|
64
68
|
|
65
69
|
r.get do
|
@@ -69,7 +73,7 @@ module Rodauth
|
|
69
73
|
r.post do
|
70
74
|
if recovery_code_match?(param(recovery_codes_param))
|
71
75
|
before_recovery_auth
|
72
|
-
two_factor_authenticate(
|
76
|
+
two_factor_authenticate('recovery_code')
|
73
77
|
end
|
74
78
|
|
75
79
|
set_response_error_status(invalid_key_error_status)
|
@@ -124,61 +128,24 @@ module Rodauth
|
|
124
128
|
|
125
129
|
attr_accessor :recovery_codes_button
|
126
130
|
|
127
|
-
def two_factor_need_setup_redirect
|
128
|
-
super || (add_recovery_codes_redirect if recovery_codes_primary?)
|
129
|
-
end
|
130
|
-
|
131
|
-
def two_factor_auth_required_redirect
|
132
|
-
super || (recovery_auth_redirect if recovery_codes_primary?)
|
133
|
-
end
|
134
|
-
|
135
|
-
def two_factor_auth_fallback_redirect
|
136
|
-
recovery_auth_redirect
|
137
|
-
end
|
138
|
-
|
139
131
|
def two_factor_remove
|
140
132
|
super
|
141
133
|
recovery_codes_remove
|
142
134
|
end
|
143
135
|
|
144
|
-
def two_factor_authentication_setup?
|
145
|
-
super || (recovery_codes_primary? && !recovery_codes.empty?)
|
146
|
-
end
|
147
|
-
|
148
|
-
def otp_auth_form_footer
|
149
|
-
"#{super if defined?(super)}<p><a href=\"#{recovery_auth_route}\">Authenticate using recovery code</a></p>"
|
150
|
-
end
|
151
|
-
|
152
|
-
def otp_lockout_redirect
|
153
|
-
recovery_auth_redirect
|
154
|
-
end
|
155
|
-
|
156
|
-
def otp_lockout_error_flash
|
157
|
-
"#{super if defined?(super)} Can use recovery code to unlock."
|
158
|
-
end
|
159
|
-
|
160
136
|
def otp_add_key
|
161
137
|
super if defined?(super)
|
162
|
-
|
138
|
+
auto_add_missing_recovery_codes
|
163
139
|
end
|
164
140
|
|
165
141
|
def sms_confirm
|
166
142
|
super if defined?(super)
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
def otp_remove
|
171
|
-
super if defined?(super)
|
172
|
-
unless recovery_codes_primary?
|
173
|
-
recovery_codes_remove
|
174
|
-
end
|
143
|
+
auto_add_missing_recovery_codes
|
175
144
|
end
|
176
145
|
|
177
|
-
def
|
146
|
+
def add_webauthn_credential(_)
|
178
147
|
super if defined?(super)
|
179
|
-
|
180
|
-
recovery_codes_remove
|
181
|
-
end
|
148
|
+
auto_add_missing_recovery_codes
|
182
149
|
end
|
183
150
|
|
184
151
|
def recovery_codes_remove
|
@@ -221,14 +188,43 @@ module Rodauth
|
|
221
188
|
end
|
222
189
|
end
|
223
190
|
|
191
|
+
def possible_authentication_methods
|
192
|
+
methods = super
|
193
|
+
methods << 'recovery_code' unless recovery_codes_ds.empty?
|
194
|
+
methods
|
195
|
+
end
|
196
|
+
|
224
197
|
private
|
225
198
|
|
199
|
+
def _two_factor_auth_links
|
200
|
+
links = super
|
201
|
+
links << [40, recovery_auth_path, recovery_auth_link_text] unless recovery_codes_ds.empty?
|
202
|
+
links
|
203
|
+
end
|
204
|
+
|
205
|
+
def _two_factor_setup_links
|
206
|
+
links = super
|
207
|
+
links << [40, recovery_codes_path, recovery_codes_link_text] if (recovery_codes_primary? || uses_two_factor_authentication?)
|
208
|
+
links
|
209
|
+
end
|
210
|
+
|
211
|
+
def _two_factor_remove_all_from_session
|
212
|
+
two_factor_remove_session('recovery_code')
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
226
216
|
def new_recovery_code
|
227
217
|
random_key
|
228
218
|
end
|
229
219
|
|
230
220
|
def recovery_codes_primary?
|
231
|
-
(features & [:otp, :sms_codes]).empty?
|
221
|
+
(features & [:otp, :sms_codes, :webauthn]).empty?
|
222
|
+
end
|
223
|
+
|
224
|
+
def auto_add_missing_recovery_codes
|
225
|
+
if auto_add_recovery_codes?
|
226
|
+
add_recovery_codes(recovery_codes_limit - recovery_codes.length)
|
227
|
+
end
|
232
228
|
end
|
233
229
|
|
234
230
|
def _recovery_codes
|