rodauth 2.31.0 → 2.32.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 +26 -0
- data/README.rdoc +1 -1
- data/doc/argon2.rdoc +9 -5
- data/doc/base.rdoc +1 -0
- data/doc/change_login.rdoc +1 -0
- data/doc/change_password.rdoc +1 -0
- data/doc/close_account.rdoc +1 -0
- data/doc/confirm_password.rdoc +1 -0
- data/doc/create_account.rdoc +1 -0
- data/doc/email_auth.rdoc +1 -0
- data/doc/jwt.rdoc +1 -0
- data/doc/lockout.rdoc +4 -2
- data/doc/login.rdoc +2 -1
- data/doc/logout.rdoc +1 -0
- data/doc/otp.rdoc +3 -0
- data/doc/release_notes/2.32.0.txt +65 -0
- data/doc/remember.rdoc +1 -0
- data/doc/reset_password.rdoc +2 -0
- data/doc/sms_codes.rdoc +5 -0
- data/doc/two_factor_base.rdoc +2 -0
- data/doc/verify_account.rdoc +2 -0
- data/doc/verify_login_change.rdoc +1 -0
- data/doc/webauthn.rdoc +2 -0
- data/lib/rodauth/features/active_sessions.rb +10 -4
- data/lib/rodauth/features/argon2.rb +26 -6
- data/lib/rodauth/features/base.rb +39 -4
- data/lib/rodauth/features/change_login.rb +2 -2
- data/lib/rodauth/features/change_password.rb +2 -2
- data/lib/rodauth/features/close_account.rb +2 -2
- data/lib/rodauth/features/confirm_password.rb +2 -2
- data/lib/rodauth/features/create_account.rb +2 -2
- data/lib/rodauth/features/email_auth.rb +7 -12
- data/lib/rodauth/features/email_base.rb +4 -6
- data/lib/rodauth/features/jwt.rb +17 -1
- data/lib/rodauth/features/jwt_refresh.rb +4 -2
- data/lib/rodauth/features/lockout.rb +6 -6
- data/lib/rodauth/features/login.rb +13 -4
- data/lib/rodauth/features/logout.rb +2 -2
- data/lib/rodauth/features/otp.rb +35 -7
- data/lib/rodauth/features/remember.rb +15 -11
- data/lib/rodauth/features/reset_password.rb +5 -5
- data/lib/rodauth/features/single_session.rb +4 -3
- data/lib/rodauth/features/sms_codes.rb +23 -10
- data/lib/rodauth/features/two_factor_base.rb +8 -6
- data/lib/rodauth/features/update_password_hash.rb +2 -1
- data/lib/rodauth/features/verify_account.rb +7 -12
- data/lib/rodauth/features/verify_login_change.rb +2 -2
- data/lib/rodauth/features/webauthn.rb +6 -6
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +16 -0
- metadata +4 -2
@@ -11,6 +11,7 @@ module Rodauth
|
|
11
11
|
button 'Confirm Password'
|
12
12
|
before
|
13
13
|
after
|
14
|
+
response
|
14
15
|
redirect(:password_authentication_required){confirm_password_path}
|
15
16
|
|
16
17
|
session_key :confirm_password_redirect_session_key, :confirm_password_redirect
|
@@ -37,8 +38,7 @@ module Rodauth
|
|
37
38
|
confirm_password
|
38
39
|
after_confirm_password
|
39
40
|
end
|
40
|
-
|
41
|
-
redirect confirm_password_redirect
|
41
|
+
confirm_password_response
|
42
42
|
else
|
43
43
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
44
44
|
set_field_error(password_param, invalid_password_message)
|
@@ -13,6 +13,7 @@ module Rodauth
|
|
13
13
|
button 'Create Account'
|
14
14
|
additional_form_tags
|
15
15
|
redirect
|
16
|
+
response
|
16
17
|
|
17
18
|
auth_value_method :create_account_autologin?, true
|
18
19
|
translatable_method :create_account_link_text, "Create a New Account"
|
@@ -79,8 +80,7 @@ module Rodauth
|
|
79
80
|
if create_account_autologin?
|
80
81
|
autologin_session('create_account')
|
81
82
|
end
|
82
|
-
|
83
|
-
redirect create_account_redirect
|
83
|
+
create_account_response
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -19,6 +19,7 @@ module Rodauth
|
|
19
19
|
button 'Send Login Link Via Email', 'email_auth_request'
|
20
20
|
redirect(:email_auth_email_sent){default_post_email_redirect}
|
21
21
|
redirect(:email_auth_email_recently_sent){default_post_email_redirect}
|
22
|
+
response :email_auth_email_sent
|
22
23
|
email :email_auth, 'Login Link'
|
23
24
|
|
24
25
|
auth_value_method :email_auth_deadline_column, :deadline
|
@@ -57,12 +58,11 @@ module Rodauth
|
|
57
58
|
r.post do
|
58
59
|
if account_from_login(param(login_param)) && open_account?
|
59
60
|
_email_auth_request
|
60
|
-
else
|
61
|
-
set_redirect_error_status(no_matching_login_error_status)
|
62
|
-
set_error_reason :no_matching_login
|
63
|
-
set_redirect_error_flash email_auth_request_error_flash
|
64
61
|
end
|
65
62
|
|
63
|
+
set_redirect_error_status(no_matching_login_error_status)
|
64
|
+
set_error_reason :no_matching_login
|
65
|
+
set_redirect_error_flash email_auth_request_error_flash
|
66
66
|
redirect email_auth_email_sent_redirect
|
67
67
|
end
|
68
68
|
end
|
@@ -150,7 +150,7 @@ module Rodauth
|
|
150
150
|
|
151
151
|
def after_login_entered_during_multi_phase_login
|
152
152
|
# If forcing email auth, just send the email link.
|
153
|
-
|
153
|
+
_email_auth_request if force_email_auth?
|
154
154
|
|
155
155
|
super
|
156
156
|
end
|
@@ -169,7 +169,7 @@ module Rodauth
|
|
169
169
|
|
170
170
|
def _multi_phase_login_forms
|
171
171
|
forms = super
|
172
|
-
forms << [30, email_auth_request_form, :
|
172
|
+
forms << [30, email_auth_request_form, :_email_auth_request] if valid_login_entered? && allow_email_auth?
|
173
173
|
forms
|
174
174
|
end
|
175
175
|
|
@@ -177,11 +177,6 @@ module Rodauth
|
|
177
177
|
(email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
|
178
178
|
end
|
179
179
|
|
180
|
-
def _email_auth_request_and_redirect
|
181
|
-
_email_auth_request
|
182
|
-
redirect email_auth_email_sent_redirect
|
183
|
-
end
|
184
|
-
|
185
180
|
def _email_auth_request
|
186
181
|
if email_auth_email_recently_sent?
|
187
182
|
set_redirect_error_flash email_auth_email_recently_sent_error_flash
|
@@ -196,7 +191,7 @@ module Rodauth
|
|
196
191
|
after_email_auth_request
|
197
192
|
end
|
198
193
|
|
199
|
-
|
194
|
+
email_auth_email_sent_response
|
200
195
|
end
|
201
196
|
|
202
197
|
attr_reader :email_auth_key_value
|
@@ -69,12 +69,10 @@ module Rodauth
|
|
69
69
|
|
70
70
|
return unless actual = yield(id)
|
71
71
|
|
72
|
-
unless timing_safe_eql?(key, convert_email_token_key(actual))
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
return
|
77
|
-
end
|
72
|
+
unless (hmac_secret && timing_safe_eql?(key, convert_email_token_key(actual))) ||
|
73
|
+
(hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual))) ||
|
74
|
+
((!hmac_secret || allow_raw_email_token?) && timing_safe_eql?(key, actual))
|
75
|
+
return
|
78
76
|
end
|
79
77
|
ds = account_ds(id)
|
80
78
|
ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks?
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
require 'jwt'
|
4
|
+
require 'jwt/version'
|
4
5
|
|
5
6
|
module Rodauth
|
6
7
|
Feature.define(:jwt, :Jwt) do
|
@@ -11,6 +12,7 @@ module Rodauth
|
|
11
12
|
auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
|
12
13
|
auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
|
13
14
|
auth_value_method :jwt_decode_opts, {}.freeze
|
15
|
+
auth_value_method :jwt_old_secret, nil
|
14
16
|
auth_value_method :jwt_session_key, nil
|
15
17
|
auth_value_method :jwt_symbolize_deeply?, false
|
16
18
|
|
@@ -111,9 +113,23 @@ module Rodauth
|
|
111
113
|
jwt_decode_opts
|
112
114
|
end
|
113
115
|
|
116
|
+
if JWT::VERSION::MAJOR > 2 || (JWT::VERSION::MAJOR == 2 && JWT::VERSION::MINOR >= 4)
|
117
|
+
def _jwt_decode_secrets
|
118
|
+
secrets = [jwt_secret, jwt_old_secret]
|
119
|
+
secrets.compact!
|
120
|
+
secrets
|
121
|
+
end
|
122
|
+
# :nocov:
|
123
|
+
else
|
124
|
+
def _jwt_decode_secrets
|
125
|
+
jwt_secret
|
126
|
+
end
|
127
|
+
# :nocov:
|
128
|
+
end
|
129
|
+
|
114
130
|
def jwt_payload
|
115
131
|
return @jwt_payload if defined?(@jwt_payload)
|
116
|
-
@jwt_payload = JWT.decode(jwt_token,
|
132
|
+
@jwt_payload = JWT.decode(jwt_token, _jwt_decode_secrets, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
|
117
133
|
rescue JWT::DecodeError => e
|
118
134
|
rescue_jwt_payload(e)
|
119
135
|
end
|
@@ -114,7 +114,7 @@ module Rodauth
|
|
114
114
|
unless key &&
|
115
115
|
(id.to_s == session_value.to_s) &&
|
116
116
|
(actual = get_active_refresh_token(id, token_id)) &&
|
117
|
-
timing_safe_eql?(key, convert_token_key(actual)) &&
|
117
|
+
(timing_safe_eql?(key, convert_token_key(actual)) || (hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual)))) &&
|
118
118
|
jwt_refresh_token_match?(key)
|
119
119
|
return
|
120
120
|
end
|
@@ -150,7 +150,9 @@ module Rodauth
|
|
150
150
|
|
151
151
|
# If allowing with expired jwt access token, check the expired session contains
|
152
152
|
# hmac matching submitted and active refresh token.
|
153
|
-
|
153
|
+
s = session[jwt_refresh_token_hmac_session_key].to_s
|
154
|
+
h = session[jwt_refresh_token_data_session_key].to_s + key
|
155
|
+
timing_safe_eql?(compute_hmac(h), s) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(h), s))
|
154
156
|
end
|
155
157
|
|
156
158
|
def get_active_refresh_token(account_id, token_id)
|
@@ -23,10 +23,12 @@ module Rodauth
|
|
23
23
|
notice_flash "Your account has been unlocked", 'unlock_account'
|
24
24
|
notice_flash "An email has been sent to you with a link to unlock your account", 'unlock_account_request'
|
25
25
|
redirect :unlock_account
|
26
|
+
response :unlock_account
|
27
|
+
response :unlock_account_request
|
26
28
|
redirect(:unlock_account_request){default_post_email_redirect}
|
27
29
|
redirect(:unlock_account_email_recently_sent){default_post_email_redirect}
|
28
30
|
email :unlock_account, 'Unlock Account'
|
29
|
-
|
31
|
+
|
30
32
|
auth_value_method :unlock_account_autologin?, true
|
31
33
|
auth_value_method :max_invalid_logins, 100
|
32
34
|
auth_value_method :account_login_failures_table, :account_login_failures
|
@@ -82,14 +84,13 @@ module Rodauth
|
|
82
84
|
after_unlock_account_request
|
83
85
|
end
|
84
86
|
|
85
|
-
|
87
|
+
unlock_account_request_response
|
86
88
|
else
|
87
89
|
set_redirect_error_status(no_matching_login_error_status)
|
88
90
|
set_error_reason :no_matching_login
|
89
91
|
set_redirect_error_flash no_matching_login_message.to_s.capitalize
|
92
|
+
redirect unlock_account_request_redirect
|
90
93
|
end
|
91
|
-
|
92
|
-
redirect unlock_account_request_redirect
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
@@ -134,8 +135,7 @@ module Rodauth
|
|
134
135
|
end
|
135
136
|
|
136
137
|
remove_session_value(unlock_account_session_key)
|
137
|
-
|
138
|
-
redirect unlock_account_redirect
|
138
|
+
unlock_account_response
|
139
139
|
else
|
140
140
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
141
141
|
set_field_error(password_param, invalid_password_message)
|
@@ -24,7 +24,10 @@ module Rodauth
|
|
24
24
|
|
25
25
|
auth_value_methods :login_return_to_requested_location_path
|
26
26
|
|
27
|
-
auth_private_methods
|
27
|
+
auth_private_methods(
|
28
|
+
:login_form_footer_links,
|
29
|
+
:login_response
|
30
|
+
)
|
28
31
|
|
29
32
|
internal_request_method
|
30
33
|
internal_request_method :valid_login_and_password?
|
@@ -77,17 +80,18 @@ module Rodauth
|
|
77
80
|
end
|
78
81
|
|
79
82
|
attr_reader :login_form_header
|
83
|
+
attr_reader :saved_login_redirect
|
84
|
+
private :saved_login_redirect
|
80
85
|
|
81
86
|
def login(auth_type)
|
82
|
-
saved_login_redirect = remove_session_value(login_redirect_session_key)
|
87
|
+
@saved_login_redirect = remove_session_value(login_redirect_session_key)
|
83
88
|
transaction do
|
84
89
|
before_login
|
85
90
|
login_session(auth_type)
|
86
91
|
yield if block_given?
|
87
92
|
after_login
|
88
93
|
end
|
89
|
-
|
90
|
-
redirect(saved_login_redirect || login_redirect)
|
94
|
+
require_response(:_login_response)
|
91
95
|
end
|
92
96
|
|
93
97
|
def login_required
|
@@ -136,6 +140,11 @@ module Rodauth
|
|
136
140
|
|
137
141
|
private
|
138
142
|
|
143
|
+
def _login_response
|
144
|
+
set_notice_flash login_notice_flash
|
145
|
+
redirect(saved_login_redirect || login_redirect)
|
146
|
+
end
|
147
|
+
|
139
148
|
def _login_form_footer_links
|
140
149
|
[]
|
141
150
|
end
|
@@ -10,6 +10,7 @@ module Rodauth
|
|
10
10
|
after
|
11
11
|
button 'Logout'
|
12
12
|
redirect{require_login_redirect}
|
13
|
+
response
|
13
14
|
|
14
15
|
auth_methods :logout
|
15
16
|
|
@@ -26,8 +27,7 @@ module Rodauth
|
|
26
27
|
logout
|
27
28
|
after_logout
|
28
29
|
end
|
29
|
-
|
30
|
-
redirect logout_redirect
|
30
|
+
logout_response
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -35,6 +35,8 @@ module Rodauth
|
|
35
35
|
redirect :otp_disable
|
36
36
|
redirect :otp_already_setup
|
37
37
|
redirect :otp_setup
|
38
|
+
response :otp_disable
|
39
|
+
response :otp_setup
|
38
40
|
redirect(:otp_lockout){two_factor_auth_required_redirect}
|
39
41
|
|
40
42
|
loaded_templates %w'otp-disable otp-auth otp-setup otp-auth-code-field password-field'
|
@@ -94,7 +96,8 @@ module Rodauth
|
|
94
96
|
|
95
97
|
auth_private_methods(
|
96
98
|
:otp_add_key,
|
97
|
-
:otp_tmp_key
|
99
|
+
:otp_tmp_key,
|
100
|
+
:otp_valid_code_for_old_secret
|
98
101
|
)
|
99
102
|
|
100
103
|
internal_request_method :otp_setup_params
|
@@ -181,8 +184,7 @@ module Rodauth
|
|
181
184
|
end
|
182
185
|
after_otp_setup
|
183
186
|
end
|
184
|
-
|
185
|
-
redirect otp_setup_redirect
|
187
|
+
otp_setup_response
|
186
188
|
end
|
187
189
|
|
188
190
|
set_error_flash otp_setup_error_flash
|
@@ -209,8 +211,7 @@ module Rodauth
|
|
209
211
|
end
|
210
212
|
after_otp_disable
|
211
213
|
end
|
212
|
-
|
213
|
-
redirect otp_disable_redirect
|
214
|
+
otp_disable_response
|
214
215
|
end
|
215
216
|
|
216
217
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
@@ -246,8 +247,19 @@ module Rodauth
|
|
246
247
|
def otp_exists?
|
247
248
|
!otp_key.nil?
|
248
249
|
end
|
249
|
-
|
250
|
+
|
250
251
|
def otp_valid_code?(ot_pass)
|
252
|
+
if _otp_valid_code?(ot_pass, otp)
|
253
|
+
true
|
254
|
+
elsif hmac_secret_rotation? && _otp_valid_code?(ot_pass, _otp_for_key(otp_hmac_old_secret(otp_key)))
|
255
|
+
_otp_valid_code_for_old_secret
|
256
|
+
true
|
257
|
+
else
|
258
|
+
false
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def _otp_valid_code?(ot_pass, otp)
|
251
263
|
return false unless otp_exists?
|
252
264
|
ot_pass = ot_pass.gsub(/\s+/, '')
|
253
265
|
if drift = otp_drift
|
@@ -368,9 +380,17 @@ module Rodauth
|
|
368
380
|
base32_encode(compute_raw_hmac(ROTP::Base32.decode(key)), key.bytesize)
|
369
381
|
end
|
370
382
|
|
383
|
+
def otp_hmac_old_secret(key)
|
384
|
+
base32_encode(compute_raw_hmac_with_secret(ROTP::Base32.decode(key), hmac_old_secret), key.bytesize)
|
385
|
+
end
|
386
|
+
|
371
387
|
def otp_valid_key?(secret)
|
372
388
|
return false unless secret =~ /\A([a-z2-7]{16}|[a-z2-7]{32})\z/
|
373
389
|
if otp_keys_use_hmac?
|
390
|
+
# Purposely do not allow creating new OTPs with old secrets,
|
391
|
+
# since OTP rotation is difficult. The user will get shown
|
392
|
+
# the same page with an updated secret, which they can submit
|
393
|
+
# to setup OTP.
|
374
394
|
timing_safe_eql?(otp_hmac_secret(param(otp_setup_raw_param)), secret)
|
375
395
|
else
|
376
396
|
true
|
@@ -400,6 +420,10 @@ module Rodauth
|
|
400
420
|
@otp_key = secret
|
401
421
|
end
|
402
422
|
|
423
|
+
# Called for valid OTP codes for old secrets
|
424
|
+
def _otp_valid_code_for_old_secret
|
425
|
+
end
|
426
|
+
|
403
427
|
def _otp_add_key(secret)
|
404
428
|
# Uniqueness errors can't be handled here, as we can't be sure the secret provided
|
405
429
|
# is the same as the current secret.
|
@@ -411,8 +435,12 @@ module Rodauth
|
|
411
435
|
otp_key_ds.get(otp_keys_column)
|
412
436
|
end
|
413
437
|
|
438
|
+
def _otp_for_key(key)
|
439
|
+
otp_class.new(key, :issuer=>otp_issuer, :digits=>otp_digits, :interval=>otp_interval)
|
440
|
+
end
|
441
|
+
|
414
442
|
def _otp
|
415
|
-
|
443
|
+
_otp_for_key(otp_user_key)
|
416
444
|
end
|
417
445
|
|
418
446
|
def otp_key_ds
|
@@ -13,6 +13,7 @@ module Rodauth
|
|
13
13
|
after
|
14
14
|
after 'load_memory'
|
15
15
|
redirect
|
16
|
+
response
|
16
17
|
|
17
18
|
auth_value_method :raw_remember_token_deadline, nil
|
18
19
|
auth_value_method :remember_cookie_options, {}.freeze
|
@@ -71,15 +72,14 @@ module Rodauth
|
|
71
72
|
when remember_remember_param_value
|
72
73
|
remember_login
|
73
74
|
when remember_forget_param_value
|
74
|
-
forget_login
|
75
|
+
forget_login
|
75
76
|
when remember_disable_param_value
|
76
|
-
disable_remember_login
|
77
|
+
disable_remember_login
|
77
78
|
end
|
78
79
|
after_remember
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
-
redirect remember_redirect
|
82
|
+
remember_response
|
83
83
|
else
|
84
84
|
set_response_error_reason_status(:invalid_remember_param, invalid_field_error_status)
|
85
85
|
set_error_flash remember_error_flash
|
@@ -96,11 +96,11 @@ module Rodauth
|
|
96
96
|
actual, deadline = active_remember_key_ds(id).get([remember_key_column, remember_deadline_column])
|
97
97
|
return unless actual
|
98
98
|
|
99
|
-
if hmac_secret
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
99
|
+
if hmac_secret && !(valid = timing_safe_eql?(key, compute_hmac(actual)))
|
100
|
+
if hmac_secret_rotation? && (valid = timing_safe_eql?(key, compute_old_hmac(actual)))
|
101
|
+
_set_remember_cookie(id, actual, deadline)
|
102
|
+
elsif !(raw_remember_token_deadline && raw_remember_token_deadline > convert_timestamp(deadline))
|
103
|
+
return
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -177,16 +177,20 @@ module Rodauth
|
|
177
177
|
|
178
178
|
private
|
179
179
|
|
180
|
-
def
|
180
|
+
def _set_remember_cookie(account_id, remember_key_value, deadline)
|
181
181
|
opts = Hash[remember_cookie_options]
|
182
182
|
opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
|
183
|
-
opts[:expires] = convert_timestamp(
|
183
|
+
opts[:expires] = convert_timestamp(deadline)
|
184
184
|
opts[:path] = "/" unless opts.key?(:path)
|
185
185
|
opts[:httponly] = true unless opts.key?(:httponly) || opts.key?(:http_only)
|
186
186
|
opts[:secure] = true unless opts.key?(:secure) || !request.ssl?
|
187
187
|
::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
|
188
188
|
end
|
189
189
|
|
190
|
+
def set_remember_cookie
|
191
|
+
_set_remember_cookie(account_id, remember_key_value, active_remember_key_ds.get(remember_deadline_column))
|
192
|
+
end
|
193
|
+
|
190
194
|
def extend_remember_deadline_while_logged_in?
|
191
195
|
return false unless extend_remember_deadline?
|
192
196
|
|
@@ -24,8 +24,10 @@ module Rodauth
|
|
24
24
|
redirect
|
25
25
|
redirect(:reset_password_email_sent){default_post_email_redirect}
|
26
26
|
redirect(:reset_password_email_recently_sent){default_post_email_redirect}
|
27
|
+
response
|
28
|
+
response :reset_password_email_sent
|
27
29
|
email :reset_password, 'Reset Password'
|
28
|
-
|
30
|
+
|
29
31
|
auth_value_method :reset_password_deadline_column, :deadline
|
30
32
|
auth_value_method :reset_password_deadline_interval, {:days=>1}.freeze
|
31
33
|
auth_value_method :reset_password_key_param, 'key'
|
@@ -88,8 +90,7 @@ module Rodauth
|
|
88
90
|
after_reset_password_request
|
89
91
|
end
|
90
92
|
|
91
|
-
|
92
|
-
redirect reset_password_email_sent_redirect
|
93
|
+
reset_password_email_sent_response
|
93
94
|
end
|
94
95
|
|
95
96
|
set_error_flash reset_password_request_error_flash
|
@@ -154,8 +155,7 @@ module Rodauth
|
|
154
155
|
end
|
155
156
|
|
156
157
|
remove_session_value(reset_password_session_key)
|
157
|
-
|
158
|
-
redirect reset_password_redirect
|
158
|
+
reset_password_response
|
159
159
|
end
|
160
160
|
|
161
161
|
set_error_flash reset_password_error_flash
|
@@ -37,9 +37,10 @@ module Rodauth
|
|
37
37
|
end
|
38
38
|
true
|
39
39
|
elsif current_key
|
40
|
-
if hmac_secret
|
41
|
-
valid = timing_safe_eql?(single_session_key,
|
42
|
-
|
40
|
+
if hmac_secret && !(valid = timing_safe_eql?(single_session_key, hmac = compute_hmac(current_key)))
|
41
|
+
if hmac_secret_rotation? && (valid = timing_safe_eql?(single_session_key, compute_old_hmac(current_key)))
|
42
|
+
session[single_session_session_key] = hmac
|
43
|
+
elsif !allow_raw_single_session_key?
|
43
44
|
return false
|
44
45
|
end
|
45
46
|
end
|
@@ -55,6 +55,10 @@ module Rodauth
|
|
55
55
|
redirect(:sms_request){sms_request_path}
|
56
56
|
redirect(:sms_lockout){two_factor_auth_required_redirect}
|
57
57
|
|
58
|
+
response :sms_confirm
|
59
|
+
response :sms_disable
|
60
|
+
response :sms_needs_confirmation
|
61
|
+
|
58
62
|
loaded_templates %w'sms-auth sms-confirm sms-disable sms-request sms-setup sms-code-field password-field'
|
59
63
|
view 'sms-auth', 'Authenticate via SMS Code', 'sms_auth'
|
60
64
|
view 'sms-confirm', 'Confirm SMS Backup Number', 'sms_confirm'
|
@@ -86,7 +90,11 @@ module Rodauth
|
|
86
90
|
|
87
91
|
auth_cached_method :sms
|
88
92
|
|
89
|
-
auth_value_methods
|
93
|
+
auth_value_methods(
|
94
|
+
:sms_codes_primary?,
|
95
|
+
:sms_needs_confirmation_notice_flash,
|
96
|
+
:sms_request_response
|
97
|
+
)
|
90
98
|
|
91
99
|
auth_methods(
|
92
100
|
:sms_auth_message,
|
@@ -136,9 +144,8 @@ module Rodauth
|
|
136
144
|
sms_send_auth_code
|
137
145
|
after_sms_request
|
138
146
|
end
|
139
|
-
|
140
|
-
|
141
|
-
redirect sms_auth_redirect
|
147
|
+
|
148
|
+
require_response(:_sms_request_response)
|
142
149
|
end
|
143
150
|
end
|
144
151
|
|
@@ -223,8 +230,7 @@ module Rodauth
|
|
223
230
|
after_sms_setup
|
224
231
|
end
|
225
232
|
|
226
|
-
|
227
|
-
redirect sms_needs_confirmation_redirect
|
233
|
+
sms_needs_confirmation_response
|
228
234
|
end
|
229
235
|
|
230
236
|
set_error_flash sms_setup_error_flash
|
@@ -256,8 +262,7 @@ module Rodauth
|
|
256
262
|
end
|
257
263
|
end
|
258
264
|
|
259
|
-
|
260
|
-
redirect sms_confirm_redirect
|
265
|
+
sms_confirm_response
|
261
266
|
end
|
262
267
|
|
263
268
|
sms_confirm_failure
|
@@ -287,8 +292,7 @@ module Rodauth
|
|
287
292
|
end
|
288
293
|
after_sms_disable
|
289
294
|
end
|
290
|
-
|
291
|
-
redirect sms_disable_redirect
|
295
|
+
sms_disable_response
|
292
296
|
end
|
293
297
|
|
294
298
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
@@ -395,6 +399,10 @@ module Rodauth
|
|
395
399
|
"SMS confirmation code for #{domain} is #{code}"
|
396
400
|
end
|
397
401
|
|
402
|
+
def sms_needs_confirmation_notice_flash
|
403
|
+
sms_needs_confirmation_error_flash
|
404
|
+
end
|
405
|
+
|
398
406
|
def sms_set_code(code)
|
399
407
|
update_sms(sms_code_column=>code, sms_issued_at_column=>Sequel::CURRENT_TIMESTAMP)
|
400
408
|
end
|
@@ -449,6 +457,11 @@ module Rodauth
|
|
449
457
|
|
450
458
|
private
|
451
459
|
|
460
|
+
def _sms_request_response
|
461
|
+
set_notice_flash sms_request_notice_flash
|
462
|
+
redirect sms_auth_redirect
|
463
|
+
end
|
464
|
+
|
452
465
|
def _two_factor_auth_links
|
453
466
|
links = super
|
454
467
|
links << [30, sms_request_path, sms_auth_link_text] if sms_available?
|
@@ -23,6 +23,8 @@ module Rodauth
|
|
23
23
|
redirect(:two_factor_need_setup){two_factor_manage_path}
|
24
24
|
redirect(:two_factor_auth_required){two_factor_auth_path}
|
25
25
|
|
26
|
+
response :two_factor_disable
|
27
|
+
|
26
28
|
notice_flash "You have been multifactor authenticated", "two_factor_auth"
|
27
29
|
notice_flash "All multifactor authentication methods have been disabled", "two_factor_disable"
|
28
30
|
|
@@ -55,6 +57,7 @@ module Rodauth
|
|
55
57
|
|
56
58
|
auth_private_methods(
|
57
59
|
:two_factor_auth_links,
|
60
|
+
:two_factor_auth_response,
|
58
61
|
:two_factor_setup_links,
|
59
62
|
:two_factor_remove_links
|
60
63
|
)
|
@@ -106,8 +109,7 @@ module Rodauth
|
|
106
109
|
_two_factor_remove_all_from_session
|
107
110
|
after_two_factor_disable
|
108
111
|
end
|
109
|
-
|
110
|
-
redirect two_factor_disable_redirect
|
112
|
+
two_factor_disable_response
|
111
113
|
end
|
112
114
|
|
113
115
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
@@ -247,13 +249,13 @@ module Rodauth
|
|
247
249
|
two_factor_update_session(type)
|
248
250
|
two_factor_remove_auth_failures
|
249
251
|
after_two_factor_authentication
|
250
|
-
|
251
|
-
redirect_two_factor_authenticated
|
252
|
+
require_response(:_two_factor_auth_response)
|
252
253
|
end
|
253
254
|
|
254
|
-
def
|
255
|
+
def _two_factor_auth_response
|
255
256
|
saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
|
256
|
-
|
257
|
+
set_notice_flash two_factor_auth_notice_flash
|
258
|
+
redirect(saved_two_factor_auth_redirect || two_factor_auth_redirect)
|
257
259
|
end
|
258
260
|
|
259
261
|
def two_factor_remove_session(type)
|
@@ -6,6 +6,7 @@ module Rodauth
|
|
6
6
|
|
7
7
|
def password_match?(password)
|
8
8
|
if (result = super) && update_password_hash?
|
9
|
+
@update_password_hash = false
|
9
10
|
set_password(password)
|
10
11
|
end
|
11
12
|
|
@@ -15,7 +16,7 @@ module Rodauth
|
|
15
16
|
private
|
16
17
|
|
17
18
|
def update_password_hash?
|
18
|
-
password_hash_cost != @current_password_hash_cost
|
19
|
+
password_hash_cost != @current_password_hash_cost || @update_password_hash
|
19
20
|
end
|
20
21
|
|
21
22
|
def get_password_hash
|