rodauth 2.31.0 → 2.33.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 +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
|