rodauth 2.31.0 → 2.33.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +36 -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/release_notes/2.33.0.txt +18 -0
- data/doc/remember.rdoc +1 -0
- data/doc/reset_password.rdoc +2 -0
- data/doc/sms_codes.rdoc +7 -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 +3 -3
- data/lib/rodauth/features/email_auth.rb +13 -20
- 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 +12 -14
- 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 +11 -13
- data/lib/rodauth/features/single_session.rb +4 -3
- data/lib/rodauth/features/sms_codes.rb +42 -13
- 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 +13 -20
- data/lib/rodauth/features/verify_login_change.rb +8 -10
- data/lib/rodauth/features/webauthn.rb +6 -6
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +16 -0
- metadata +6 -2
@@ -14,6 +14,7 @@ module Rodauth
|
|
14
14
|
additional_form_tags
|
15
15
|
button 'Change Login'
|
16
16
|
redirect
|
17
|
+
response
|
17
18
|
|
18
19
|
auth_value_methods :change_login_requires_password?
|
19
20
|
|
@@ -51,9 +52,8 @@ module Rodauth
|
|
51
52
|
end
|
52
53
|
|
53
54
|
after_change_login
|
54
|
-
set_notice_flash change_login_notice_flash
|
55
|
-
redirect change_login_redirect
|
56
55
|
end
|
56
|
+
change_login_response
|
57
57
|
end
|
58
58
|
|
59
59
|
set_error_flash change_login_error_flash
|
@@ -13,6 +13,7 @@ module Rodauth
|
|
13
13
|
additional_form_tags
|
14
14
|
button 'Change Password'
|
15
15
|
redirect
|
16
|
+
response
|
16
17
|
|
17
18
|
translatable_method :new_password_label, 'New Password'
|
18
19
|
auth_value_method :new_password_param, 'new-password'
|
@@ -56,8 +57,7 @@ module Rodauth
|
|
56
57
|
set_password(password)
|
57
58
|
after_change_password
|
58
59
|
end
|
59
|
-
|
60
|
-
redirect change_password_redirect
|
60
|
+
change_password_response
|
61
61
|
end
|
62
62
|
|
63
63
|
set_error_flash change_password_error_flash
|
@@ -11,6 +11,7 @@ module Rodauth
|
|
11
11
|
after
|
12
12
|
before
|
13
13
|
redirect
|
14
|
+
response
|
14
15
|
|
15
16
|
auth_value_method :account_closed_status_value, 3
|
16
17
|
|
@@ -50,8 +51,7 @@ module Rodauth
|
|
50
51
|
end
|
51
52
|
clear_session
|
52
53
|
|
53
|
-
|
54
|
-
redirect close_account_redirect
|
54
|
+
close_account_response
|
55
55
|
end
|
56
56
|
|
57
57
|
set_error_flash close_account_error_flash
|
@@ -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
|
|
@@ -106,7 +106,7 @@ module Rodauth
|
|
106
106
|
end
|
107
107
|
|
108
108
|
if id
|
109
|
-
account[account_id_column]
|
109
|
+
account[account_id_column] ||= id
|
110
110
|
end
|
111
111
|
|
112
112
|
id && !raised
|
@@ -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
|
@@ -77,14 +77,12 @@ module Rodauth
|
|
77
77
|
redirect(r.path)
|
78
78
|
end
|
79
79
|
|
80
|
-
if key = session[email_auth_session_key]
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
redirect require_login_redirect
|
87
|
-
end
|
80
|
+
if (key = session[email_auth_session_key]) && account_from_email_auth_key(key)
|
81
|
+
email_auth_view
|
82
|
+
else
|
83
|
+
remove_session_value(email_auth_session_key)
|
84
|
+
set_redirect_error_flash no_matching_email_auth_key_error_flash
|
85
|
+
redirect require_login_redirect
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
@@ -150,7 +148,7 @@ module Rodauth
|
|
150
148
|
|
151
149
|
def after_login_entered_during_multi_phase_login
|
152
150
|
# If forcing email auth, just send the email link.
|
153
|
-
|
151
|
+
_email_auth_request if force_email_auth?
|
154
152
|
|
155
153
|
super
|
156
154
|
end
|
@@ -169,7 +167,7 @@ module Rodauth
|
|
169
167
|
|
170
168
|
def _multi_phase_login_forms
|
171
169
|
forms = super
|
172
|
-
forms << [30, email_auth_request_form, :
|
170
|
+
forms << [30, email_auth_request_form, :_email_auth_request] if valid_login_entered? && allow_email_auth?
|
173
171
|
forms
|
174
172
|
end
|
175
173
|
|
@@ -177,11 +175,6 @@ module Rodauth
|
|
177
175
|
(email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
|
178
176
|
end
|
179
177
|
|
180
|
-
def _email_auth_request_and_redirect
|
181
|
-
_email_auth_request
|
182
|
-
redirect email_auth_email_sent_redirect
|
183
|
-
end
|
184
|
-
|
185
178
|
def _email_auth_request
|
186
179
|
if email_auth_email_recently_sent?
|
187
180
|
set_redirect_error_flash email_auth_email_recently_sent_error_flash
|
@@ -196,7 +189,7 @@ module Rodauth
|
|
196
189
|
after_email_auth_request
|
197
190
|
end
|
198
191
|
|
199
|
-
|
192
|
+
email_auth_email_sent_response
|
200
193
|
end
|
201
194
|
|
202
195
|
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
|
|
@@ -103,14 +104,12 @@ module Rodauth
|
|
103
104
|
redirect(r.path)
|
104
105
|
end
|
105
106
|
|
106
|
-
if key = session[unlock_account_session_key]
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
redirect require_login_redirect
|
113
|
-
end
|
107
|
+
if (key = session[unlock_account_session_key]) && account_from_unlock_key(key)
|
108
|
+
unlock_account_view
|
109
|
+
else
|
110
|
+
remove_session_value(unlock_account_session_key)
|
111
|
+
set_redirect_error_flash no_matching_unlock_account_key_error_flash
|
112
|
+
redirect require_login_redirect
|
114
113
|
end
|
115
114
|
end
|
116
115
|
|
@@ -134,8 +133,7 @@ module Rodauth
|
|
134
133
|
end
|
135
134
|
|
136
135
|
remove_session_value(unlock_account_session_key)
|
137
|
-
|
138
|
-
redirect unlock_account_redirect
|
136
|
+
unlock_account_response
|
139
137
|
else
|
140
138
|
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
141
139
|
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
|
@@ -108,14 +109,12 @@ module Rodauth
|
|
108
109
|
redirect(r.path)
|
109
110
|
end
|
110
111
|
|
111
|
-
if key = session[reset_password_session_key]
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
redirect require_login_redirect
|
118
|
-
end
|
112
|
+
if (key = session[reset_password_session_key]) && account_from_reset_password_key(key)
|
113
|
+
reset_password_view
|
114
|
+
else
|
115
|
+
remove_session_value(reset_password_session_key)
|
116
|
+
set_redirect_error_flash no_matching_reset_password_key_error_flash
|
117
|
+
redirect require_login_redirect
|
119
118
|
end
|
120
119
|
end
|
121
120
|
|
@@ -154,8 +153,7 @@ module Rodauth
|
|
154
153
|
end
|
155
154
|
|
156
155
|
remove_session_value(reset_password_session_key)
|
157
|
-
|
158
|
-
redirect reset_password_redirect
|
156
|
+
reset_password_response
|
159
157
|
end
|
160
158
|
|
161
159
|
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
|