rodauth 2.46.0 → 2.47.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/lib/rodauth/features/active_sessions.rb +2 -2
- data/lib/rodauth/features/audit_logging.rb +2 -1
- data/lib/rodauth/features/base.rb +65 -10
- data/lib/rodauth/features/change_password.rb +11 -2
- data/lib/rodauth/features/confirm_password.rb +1 -1
- data/lib/rodauth/features/create_account.rb +2 -1
- data/lib/rodauth/features/email_auth.rb +13 -4
- data/lib/rodauth/features/email_base.rb +36 -1
- data/lib/rodauth/features/internal_request.rb +4 -0
- data/lib/rodauth/features/json.rb +5 -1
- data/lib/rodauth/features/jwt.rb +7 -1
- data/lib/rodauth/features/jwt_cors.rb +2 -0
- data/lib/rodauth/features/jwt_refresh.rb +33 -18
- data/lib/rodauth/features/lockout.rb +22 -19
- data/lib/rodauth/features/login.rb +12 -4
- data/lib/rodauth/features/login_password_requirements_base.rb +39 -9
- data/lib/rodauth/features/otp.rb +19 -10
- data/lib/rodauth/features/password_expiration.rb +0 -1
- data/lib/rodauth/features/password_grace_period.rb +7 -0
- data/lib/rodauth/features/password_pepper.rb +11 -1
- data/lib/rodauth/features/recovery_codes.rb +4 -1
- data/lib/rodauth/features/remember.rb +2 -0
- data/lib/rodauth/features/require_domain.rb +12 -0
- data/lib/rodauth/features/require_hmac_secret.rb +12 -0
- data/lib/rodauth/features/reset_password.rb +25 -20
- data/lib/rodauth/features/single_session.rb +6 -2
- data/lib/rodauth/features/sms_codes.rb +4 -1
- data/lib/rodauth/features/two_factor_base.rb +2 -1
- data/lib/rodauth/features/update_password_hash.rb +5 -1
- data/lib/rodauth/features/verify_account.rb +29 -20
- data/lib/rodauth/features/verify_login_change.rb +19 -11
- data/lib/rodauth/features/webauthn.rb +10 -5
- data/lib/rodauth/features/webauthn_login.rb +1 -10
- data/lib/rodauth/features/webauthn_verify_account.rb +0 -4
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +2 -0
- metadata +3 -1
|
@@ -15,7 +15,7 @@ module Rodauth
|
|
|
15
15
|
auth_value_method :login_error_status, 401
|
|
16
16
|
translatable_method :login_form_footer_links_heading, '<h2 class="rodauth-login-form-footer-links-heading">Other Options</h2>'
|
|
17
17
|
auth_value_method :login_return_to_requested_location?, false
|
|
18
|
-
|
|
18
|
+
def_deprecated_alias :return_to_path_max_size, :login_return_to_requested_location_max_path_size
|
|
19
19
|
auth_value_method :use_multi_phase_login?, false
|
|
20
20
|
|
|
21
21
|
session_key :login_redirect_session_key, :login_redirect
|
|
@@ -106,14 +106,14 @@ module Rodauth
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def login_required
|
|
109
|
-
if login_return_to_requested_location?
|
|
110
|
-
|
|
109
|
+
if login_return_to_requested_location?
|
|
110
|
+
set_session_return_to_path(login_redirect_session_key, login_return_to_requested_location_path)
|
|
111
111
|
end
|
|
112
112
|
super
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def login_return_to_requested_location_path
|
|
116
|
-
request.fullpath
|
|
116
|
+
request.fullpath
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def after_login_entered_during_multi_phase_login
|
|
@@ -153,6 +153,14 @@ module Rodauth
|
|
|
153
153
|
login_path
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
def post_configure
|
|
157
|
+
# RODAUTH3: Remove
|
|
158
|
+
super
|
|
159
|
+
if method(:already_logged_in).owner == Rodauth::Base
|
|
160
|
+
missing_recommended_configuration("The Rodauth 'already_logged_in' configuration method was not used. Use the configuration method to define the behavior when accessing a page while logged in that is expected to be accessed when not logged in (safest to halt or redirect).")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
156
164
|
private
|
|
157
165
|
|
|
158
166
|
def _login_response
|
|
@@ -12,7 +12,7 @@ module Rodauth
|
|
|
12
12
|
translatable_method :logins_do_not_match_message, 'logins do not match'
|
|
13
13
|
auth_value_method :password_confirm_param, 'password-confirm'
|
|
14
14
|
auth_value_method :password_minimum_length, 6
|
|
15
|
-
auth_value_method :password_maximum_bytes, nil
|
|
15
|
+
auth_value_method :password_maximum_bytes, nil # RODAUTH3: 72 if bcrypt is used
|
|
16
16
|
auth_value_method :password_maximum_length, nil
|
|
17
17
|
translatable_method :passwords_do_not_match_message, 'passwords do not match'
|
|
18
18
|
auth_value_method :require_email_address_logins?, true
|
|
@@ -67,13 +67,39 @@ module Rodauth
|
|
|
67
67
|
def set_password(password)
|
|
68
68
|
hash = password_hash(password)
|
|
69
69
|
if account_password_hash_column
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
ds = account_ds
|
|
71
|
+
|
|
72
|
+
if @existing_password_hash_or_salt
|
|
73
|
+
ds = ds.where(account_password_hash_column => @existing_password_hash_or_salt)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless update_account({account_password_hash_column=>hash}, ds) == 1
|
|
77
|
+
raise SetPasswordFailure, "set_password did not update password, either due to missing account, or concurrent password change"
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
ph_ds = ds = password_hash_ds
|
|
81
|
+
if @existing_password_hash_or_salt
|
|
82
|
+
existing_expression = if use_database_authentication_functions?
|
|
83
|
+
Sequel.function(function_name(:rodauth_get_salt), :id)
|
|
84
|
+
# :nocov:
|
|
85
|
+
else
|
|
86
|
+
password_hash_column
|
|
87
|
+
# :nocov:
|
|
88
|
+
end
|
|
89
|
+
ds = ds.where(existing_expression => @existing_password_hash_or_salt)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if ds.update(password_hash_column=>hash) == 0
|
|
93
|
+
if ph_ds.empty?
|
|
94
|
+
# This shouldn't raise a uniqueness error, as the update should only fail for a new user,
|
|
95
|
+
# and an existing user should always have a valid password hash row. If this does
|
|
96
|
+
# fail, retrying it will cause problems, it will override a concurrently running update
|
|
97
|
+
# with potentially a different password.
|
|
98
|
+
db[password_hash_table].insert(password_hash_id_column=>account_id, password_hash_column=>hash)
|
|
99
|
+
else
|
|
100
|
+
raise SetPasswordFailure, "set_password did not update password due to concurrent password change"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
77
103
|
end
|
|
78
104
|
hash
|
|
79
105
|
end
|
|
@@ -174,7 +200,7 @@ module Rodauth
|
|
|
174
200
|
elsif password_maximum_length && password_maximum_length < password.length
|
|
175
201
|
set_password_requirement_error_message(:password_too_long, password_too_long_message)
|
|
176
202
|
false
|
|
177
|
-
elsif password_maximum_bytes && password_maximum_bytes < password
|
|
203
|
+
elsif password_maximum_bytes && password_maximum_bytes < password_bytesize(password)
|
|
178
204
|
set_password_requirement_error_message(:password_too_many_bytes, password_too_many_bytes_message)
|
|
179
205
|
false
|
|
180
206
|
else
|
|
@@ -182,6 +208,10 @@ module Rodauth
|
|
|
182
208
|
end
|
|
183
209
|
end
|
|
184
210
|
|
|
211
|
+
def password_bytesize(password)
|
|
212
|
+
password.bytesize
|
|
213
|
+
end
|
|
214
|
+
|
|
185
215
|
def password_does_not_contain_null_byte?(password)
|
|
186
216
|
return true unless password.include?("\0")
|
|
187
217
|
set_password_requirement_error_message(:password_contains_null_byte, contains_null_byte_message)
|
data/lib/rodauth/features/otp.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'rqrcode'
|
|
|
5
5
|
|
|
6
6
|
module Rodauth
|
|
7
7
|
Feature.define(:otp, :Otp) do
|
|
8
|
-
depends :two_factor_base
|
|
8
|
+
depends :two_factor_base, :require_domain, :require_hmac_secret
|
|
9
9
|
|
|
10
10
|
additional_form_tags 'otp_disable'
|
|
11
11
|
additional_form_tags 'otp_auth'
|
|
@@ -127,13 +127,18 @@ module Rodauth
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
r.post do
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
transaction do
|
|
131
|
+
otp_key_ds.for_update.first
|
|
132
|
+
|
|
133
|
+
if otp_valid_code?(param(otp_auth_param)) && otp_update_last_use
|
|
134
|
+
before_otp_authentication
|
|
135
|
+
two_factor_authenticate('totp')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
otp_record_authentication_failure
|
|
139
|
+
after_otp_authentication_failure
|
|
133
140
|
end
|
|
134
141
|
|
|
135
|
-
otp_record_authentication_failure
|
|
136
|
-
after_otp_authentication_failure
|
|
137
142
|
set_response_error_reason_status(:invalid_otp_auth_code, invalid_key_error_status)
|
|
138
143
|
set_field_error(otp_auth_param, otp_invalid_auth_code_message)
|
|
139
144
|
set_error_flash otp_auth_error_flash
|
|
@@ -265,15 +270,19 @@ module Rodauth
|
|
|
265
270
|
return false unless otp_exists?
|
|
266
271
|
ot_pass = ot_pass.gsub(/\s+/, '')
|
|
267
272
|
if drift = otp_drift
|
|
273
|
+
# :nocov:
|
|
268
274
|
if otp.respond_to?(:verify_with_drift)
|
|
269
|
-
# :nocov:
|
|
270
275
|
otp.verify_with_drift(ot_pass, drift)
|
|
271
|
-
|
|
276
|
+
# :nocov:
|
|
272
277
|
else
|
|
273
|
-
otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
|
|
278
|
+
otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift, :after=>otp_last_use)
|
|
274
279
|
end
|
|
275
|
-
|
|
280
|
+
# :nocov:
|
|
281
|
+
elsif otp.respond_to?(:verify_with_drift)
|
|
276
282
|
otp.verify(ot_pass)
|
|
283
|
+
# :nocov:
|
|
284
|
+
else
|
|
285
|
+
otp.verify(ot_pass, :after=>otp_last_use)
|
|
277
286
|
end
|
|
278
287
|
end
|
|
279
288
|
|
|
@@ -70,7 +70,6 @@ module Rodauth
|
|
|
70
70
|
return password_changed_at + require_password_change_after < Time.now.to_i
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
account_from_session
|
|
74
73
|
if password_changed_at = get_password_changed_at
|
|
75
74
|
set_session_value(password_changed_at_session_key, password_changed_at.to_i)
|
|
76
75
|
password_changed_at + require_password_change_after < Time.now
|
|
@@ -33,6 +33,13 @@ module Rodauth
|
|
|
33
33
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
+
def new_password_matches_current_password?(_)
|
|
37
|
+
if v = super
|
|
38
|
+
session.delete(last_password_entry_session_key)
|
|
39
|
+
end
|
|
40
|
+
v
|
|
41
|
+
end
|
|
42
|
+
|
|
36
43
|
def after_create_account
|
|
37
44
|
super if defined?(super)
|
|
38
45
|
@last_password_entry = Time.now.to_i
|
|
@@ -12,7 +12,11 @@ module Rodauth
|
|
|
12
12
|
|
|
13
13
|
def password_match?(password)
|
|
14
14
|
if (result = super) && @previous_pepper_matched && password_pepper_update?
|
|
15
|
-
|
|
15
|
+
begin
|
|
16
|
+
set_password(password)
|
|
17
|
+
rescue Rodauth::SetPasswordFailure
|
|
18
|
+
return false
|
|
19
|
+
end
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
result
|
|
@@ -24,6 +28,12 @@ module Rodauth
|
|
|
24
28
|
|
|
25
29
|
private
|
|
26
30
|
|
|
31
|
+
def password_bytesize(password)
|
|
32
|
+
n = super
|
|
33
|
+
n += password_pepper.bytesize if password_pepper
|
|
34
|
+
n
|
|
35
|
+
end
|
|
36
|
+
|
|
27
37
|
def password_hash_match?(hash, password)
|
|
28
38
|
return super if password_pepper.nil?
|
|
29
39
|
|
|
@@ -162,7 +162,10 @@ module Rodauth
|
|
|
162
162
|
def recovery_code_match?(code)
|
|
163
163
|
recovery_codes.each do |s|
|
|
164
164
|
if timing_safe_eql?(code, s)
|
|
165
|
-
recovery_codes_ds.where(recovery_codes_column=>code).delete
|
|
165
|
+
unless recovery_codes_ds.where(recovery_codes_column=>code).delete == 1
|
|
166
|
+
return false
|
|
167
|
+
end
|
|
168
|
+
|
|
166
169
|
if recovery_codes_primary?
|
|
167
170
|
add_recovery_code
|
|
168
171
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Rodauth
|
|
4
|
+
Feature.define(:require_domain, :RequireDomain) do
|
|
5
|
+
def post_configure
|
|
6
|
+
super
|
|
7
|
+
if method(:domain).owner == Rodauth::Base
|
|
8
|
+
missing_recommended_configuration("The Rodauth 'domain' configuration method was not used. This can potentially leave the application vulnerable to requests that use malicious or invalid domains.")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Rodauth
|
|
4
|
+
Feature.define(:require_hmac_secret, :RequireHmacSecret) do
|
|
5
|
+
def post_configure
|
|
6
|
+
super
|
|
7
|
+
if method(:hmac_secret).owner == Rodauth::Base
|
|
8
|
+
missing_recommended_configuration("The Rodauth 'hmac_secret' configuration method was not used. This significantly reduces Rodauth's security. Consult the Rodauth README for what this method does, and set an appropriate value.")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -120,29 +120,26 @@ module Rodauth
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
r.post do
|
|
123
|
-
key =
|
|
124
|
-
|
|
125
|
-
set_redirect_error_status(invalid_key_error_status)
|
|
126
|
-
set_error_reason :invalid_reset_password_key
|
|
127
|
-
set_redirect_error_flash reset_password_error_flash
|
|
128
|
-
redirect reset_password_email_sent_redirect
|
|
129
|
-
end
|
|
130
|
-
|
|
123
|
+
key = session_param(reset_password_session_key, reset_password_key_param)
|
|
124
|
+
select_key_for_update!
|
|
131
125
|
password = param(password_param)
|
|
126
|
+
|
|
132
127
|
catch_error do
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
end
|
|
128
|
+
transaction do
|
|
129
|
+
handle_invalid_reset_password_key unless key && account_from_reset_password_key(key)
|
|
136
130
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
unless password_meets_requirements?(password)
|
|
132
|
+
throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
|
|
133
|
+
end
|
|
140
134
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
if password_match?(password)
|
|
136
|
+
throw_error_reason(:same_as_existing_password, invalid_field_error_status, password_param, same_as_existing_password_message)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if require_password_confirmation? && password != param(password_confirm_param)
|
|
140
|
+
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
|
|
141
|
+
end
|
|
144
142
|
|
|
145
|
-
transaction do
|
|
146
143
|
before_reset_password
|
|
147
144
|
set_password(password)
|
|
148
145
|
clear_tokens(:reset_password)
|
|
@@ -184,7 +181,7 @@ module Rodauth
|
|
|
184
181
|
end
|
|
185
182
|
|
|
186
183
|
def account_from_reset_password_key(key)
|
|
187
|
-
|
|
184
|
+
set_account(_account_from_reset_password_key(key), :reset_password)
|
|
188
185
|
end
|
|
189
186
|
|
|
190
187
|
def reset_password_email_link
|
|
@@ -199,7 +196,7 @@ module Rodauth
|
|
|
199
196
|
def get_reset_password_key(id)
|
|
200
197
|
ds = password_reset_ds(id)
|
|
201
198
|
ds.where(Sequel::CURRENT_TIMESTAMP > reset_password_deadline_column).delete
|
|
202
|
-
ds.get(reset_password_key_column)
|
|
199
|
+
apply_key_for_update(ds).get(reset_password_key_column)
|
|
203
200
|
end
|
|
204
201
|
|
|
205
202
|
def set_reset_password_email_last_sent
|
|
@@ -267,5 +264,13 @@ module Rodauth
|
|
|
267
264
|
def reset_password_account_status_value
|
|
268
265
|
account_open_status_value
|
|
269
266
|
end
|
|
267
|
+
|
|
268
|
+
def handle_invalid_reset_password_key
|
|
269
|
+
remove_session_value(reset_password_session_key)
|
|
270
|
+
set_redirect_error_status(invalid_key_error_status)
|
|
271
|
+
set_error_reason :invalid_reset_password_key
|
|
272
|
+
set_redirect_error_flash reset_password_error_flash
|
|
273
|
+
redirect reset_password_email_sent_redirect
|
|
274
|
+
end
|
|
270
275
|
end
|
|
271
276
|
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:single_session, :SingleSession) do
|
|
5
|
+
depends :require_hmac_secret
|
|
6
|
+
|
|
5
7
|
error_flash 'This session has been logged out as another session has become active'
|
|
6
8
|
redirect
|
|
7
9
|
|
|
@@ -29,13 +31,15 @@ module Rodauth
|
|
|
29
31
|
single_session_key = session[single_session_session_key]
|
|
30
32
|
current_key = single_session_ds.get(single_session_key_column)
|
|
31
33
|
if single_session_key.nil?
|
|
32
|
-
|
|
34
|
+
if current_key
|
|
35
|
+
false
|
|
36
|
+
else
|
|
33
37
|
# No row exists for this user, indicating the feature has never
|
|
34
38
|
# been used, so it is OK to treat the current session as a new
|
|
35
39
|
# session.
|
|
36
40
|
update_single_session_key
|
|
41
|
+
true
|
|
37
42
|
end
|
|
38
|
-
true
|
|
39
43
|
elsif current_key
|
|
40
44
|
if hmac_secret && !(valid = timing_safe_eql?(single_session_key, hmac = compute_hmac(current_key)))
|
|
41
45
|
if hmac_secret_rotation? && (valid = timing_safe_eql?(single_session_key, compute_old_hmac(current_key)))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:sms_codes, :SmsCodes) do
|
|
5
|
-
depends :two_factor_base
|
|
5
|
+
depends :two_factor_base, :require_domain
|
|
6
6
|
|
|
7
7
|
additional_form_tags 'sms_auth'
|
|
8
8
|
additional_form_tags 'sms_confirm'
|
|
@@ -176,6 +176,9 @@ module Rodauth
|
|
|
176
176
|
|
|
177
177
|
r.post do
|
|
178
178
|
transaction do
|
|
179
|
+
@sms = sms_ds.for_update.first
|
|
180
|
+
require_sms_available
|
|
181
|
+
|
|
179
182
|
if sms_code_match?(param(sms_code_param))
|
|
180
183
|
before_sms_auth
|
|
181
184
|
sms_remove_failures
|
|
@@ -158,7 +158,7 @@ module Rodauth
|
|
|
158
158
|
def require_two_factor_authenticated
|
|
159
159
|
unless two_factor_authenticated?
|
|
160
160
|
if two_factor_auth_return_to_requested_location?
|
|
161
|
-
|
|
161
|
+
set_session_return_to_path(two_factor_auth_redirect_session_key)
|
|
162
162
|
end
|
|
163
163
|
set_redirect_error_status(two_factor_need_authentication_error_status)
|
|
164
164
|
set_error_reason :two_factor_need_authentication
|
|
@@ -262,6 +262,7 @@ module Rodauth
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
def two_factor_update_session(auth_type)
|
|
265
|
+
raise Error, "attempt to have two factor authentication using same factor" if authenticated_by.include?(auth_type)
|
|
265
266
|
authenticated_by << auth_type
|
|
266
267
|
set_session_value(two_factor_setup_session_key, true)
|
|
267
268
|
end
|
|
@@ -9,7 +9,11 @@ module Rodauth
|
|
|
9
9
|
def password_match?(password)
|
|
10
10
|
if (result = super) && update_password_hash?
|
|
11
11
|
@update_password_hash = false
|
|
12
|
-
|
|
12
|
+
begin
|
|
13
|
+
set_password(password)
|
|
14
|
+
rescue Rodauth::SetPasswordFailure
|
|
15
|
+
return false
|
|
16
|
+
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
result
|
|
@@ -114,30 +114,30 @@ module Rodauth
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
r.post do
|
|
117
|
-
key =
|
|
118
|
-
|
|
119
|
-
set_redirect_error_status(invalid_key_error_status)
|
|
120
|
-
set_error_reason :invalid_verify_account_key
|
|
121
|
-
set_redirect_error_flash verify_account_error_flash
|
|
122
|
-
redirect verify_account_redirect
|
|
123
|
-
end
|
|
117
|
+
key = session_param(verify_account_session_key, verify_account_key_param)
|
|
118
|
+
select_key_for_update!
|
|
124
119
|
|
|
125
120
|
catch_error do
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
transaction do
|
|
122
|
+
handle_invalid_verify_account_key unless key && account_from_verify_account_key(key)
|
|
128
123
|
|
|
129
|
-
if
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
if verify_account_set_password?
|
|
125
|
+
password = param(password_param)
|
|
126
|
+
|
|
127
|
+
if require_password_confirmation? && password != param(password_confirm_param)
|
|
128
|
+
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
|
|
129
|
+
end
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
unless password_meets_requirements?(password)
|
|
132
|
+
throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
|
|
133
|
+
end
|
|
135
134
|
end
|
|
136
|
-
end
|
|
137
135
|
|
|
138
|
-
transaction do
|
|
139
136
|
before_verify_account
|
|
140
|
-
verify_account
|
|
137
|
+
unless verify_account
|
|
138
|
+
remove_verify_account_key
|
|
139
|
+
handle_invalid_verify_account_key
|
|
140
|
+
end
|
|
141
141
|
if verify_account_set_password?
|
|
142
142
|
set_password(password)
|
|
143
143
|
end
|
|
@@ -171,7 +171,8 @@ module Rodauth
|
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
def verify_account
|
|
174
|
-
|
|
174
|
+
ds = account_ds.where(account_status_column=>account_unverified_status_value)
|
|
175
|
+
update_account({account_status_column=>account_open_status_value}, ds) == 1
|
|
175
176
|
end
|
|
176
177
|
|
|
177
178
|
def verify_account_email_resend
|
|
@@ -196,7 +197,7 @@ module Rodauth
|
|
|
196
197
|
end
|
|
197
198
|
|
|
198
199
|
def account_from_verify_account_key(key)
|
|
199
|
-
|
|
200
|
+
set_account(_account_from_verify_account_key(key), :verify_account)
|
|
200
201
|
end
|
|
201
202
|
|
|
202
203
|
def account_initial_status_value
|
|
@@ -208,7 +209,7 @@ module Rodauth
|
|
|
208
209
|
end
|
|
209
210
|
|
|
210
211
|
def get_verify_account_key(id)
|
|
211
|
-
verify_account_ds(id).get(verify_account_key_column)
|
|
212
|
+
apply_key_for_update(verify_account_ds(id)).get(verify_account_key_column)
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
def skip_status_checks?
|
|
@@ -318,5 +319,13 @@ module Rodauth
|
|
|
318
319
|
def _account_from_verify_account_key(token)
|
|
319
320
|
account_from_key(token, account_unverified_status_value){|id| get_verify_account_key(id)}
|
|
320
321
|
end
|
|
322
|
+
|
|
323
|
+
def handle_invalid_verify_account_key
|
|
324
|
+
remove_session_value(verify_account_session_key)
|
|
325
|
+
set_redirect_error_status(invalid_key_error_status)
|
|
326
|
+
set_error_reason :invalid_verify_account_key
|
|
327
|
+
set_redirect_error_flash verify_account_error_flash
|
|
328
|
+
redirect verify_account_redirect
|
|
329
|
+
end
|
|
321
330
|
end
|
|
322
331
|
end
|
|
@@ -74,15 +74,18 @@ module Rodauth
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
r.post do
|
|
77
|
-
key =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
set_error_reason :invalid_verify_login_change_key
|
|
81
|
-
set_redirect_error_flash verify_login_change_error_flash
|
|
82
|
-
redirect verify_login_change_redirect
|
|
83
|
-
end
|
|
77
|
+
key = session_param(verify_login_change_session_key, verify_login_change_key_param)
|
|
78
|
+
remove_session_value(verify_login_change_session_key)
|
|
79
|
+
select_key_for_update!
|
|
84
80
|
|
|
85
81
|
transaction do
|
|
82
|
+
unless key && account_from_verify_login_change_key(key)
|
|
83
|
+
set_redirect_error_status(invalid_key_error_status)
|
|
84
|
+
set_error_reason :invalid_verify_login_change_key
|
|
85
|
+
set_redirect_error_flash verify_login_change_error_flash
|
|
86
|
+
redirect verify_login_change_redirect
|
|
87
|
+
end
|
|
88
|
+
|
|
86
89
|
before_verify_login_change
|
|
87
90
|
unless verify_login_change
|
|
88
91
|
set_redirect_error_status(invalid_key_error_status)
|
|
@@ -98,7 +101,6 @@ module Rodauth
|
|
|
98
101
|
autologin_session('verify_login_change')
|
|
99
102
|
end
|
|
100
103
|
|
|
101
|
-
remove_session_value(verify_login_change_session_key)
|
|
102
104
|
verify_login_change_response
|
|
103
105
|
end
|
|
104
106
|
end
|
|
@@ -120,7 +122,7 @@ module Rodauth
|
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
def account_from_verify_login_change_key(key)
|
|
123
|
-
|
|
125
|
+
set_account(_account_from_verify_login_change_key(key), :verify_login_change)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def send_verify_login_change_email(login)
|
|
@@ -132,7 +134,9 @@ module Rodauth
|
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
def get_verify_login_change_login_and_key(id)
|
|
135
|
-
verify_login_change_ds(id)
|
|
137
|
+
ds = verify_login_change_ds(id)
|
|
138
|
+
ds.where(Sequel::CURRENT_TIMESTAMP > verify_login_change_deadline_column).delete
|
|
139
|
+
apply_key_for_update(ds).get([verify_login_change_login_column, verify_login_change_key_column])
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
def change_login_notice_flash
|
|
@@ -210,10 +214,14 @@ module Rodauth
|
|
|
210
214
|
end
|
|
211
215
|
|
|
212
216
|
def _account_from_verify_login_change_key(token)
|
|
213
|
-
account_from_key(token) do |id|
|
|
217
|
+
account_from_key(token, verify_login_change_account_status_value) do |id|
|
|
214
218
|
@verify_login_change_new_login, key = get_verify_login_change_login_and_key(id)
|
|
215
219
|
key
|
|
216
220
|
end
|
|
217
221
|
end
|
|
222
|
+
|
|
223
|
+
def verify_login_change_account_status_value
|
|
224
|
+
account_open_status_value
|
|
225
|
+
end
|
|
218
226
|
end
|
|
219
227
|
end
|
|
@@ -4,7 +4,7 @@ require 'webauthn'
|
|
|
4
4
|
|
|
5
5
|
module Rodauth
|
|
6
6
|
Feature.define(:webauthn, :Webauthn) do
|
|
7
|
-
depends :two_factor_base
|
|
7
|
+
depends :two_factor_base, :require_domain, :require_hmac_secret
|
|
8
8
|
|
|
9
9
|
loaded_templates %w'webauthn-setup webauthn-auth webauthn-remove'
|
|
10
10
|
|
|
@@ -164,7 +164,7 @@ module Rodauth
|
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
route(:webauthn_setup) do |r|
|
|
167
|
-
require_authentication unless
|
|
167
|
+
require_authentication unless webauthn_modification_authenticated?
|
|
168
168
|
require_account_session
|
|
169
169
|
before_webauthn_setup_route
|
|
170
170
|
|
|
@@ -185,7 +185,7 @@ module Rodauth
|
|
|
185
185
|
raise Sequel::Rollback
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
if !two_factor_authenticated? && !authenticated_by.include?('webauthn')
|
|
189
189
|
webauthn_update_session(webauthn_credential.id)
|
|
190
190
|
two_factor_update_session('webauthn')
|
|
191
191
|
end
|
|
@@ -205,7 +205,7 @@ module Rodauth
|
|
|
205
205
|
end
|
|
206
206
|
|
|
207
207
|
route(:webauthn_remove) do |r|
|
|
208
|
-
require_authentication unless
|
|
208
|
+
require_authentication unless webauthn_modification_authenticated?
|
|
209
209
|
require_account_session
|
|
210
210
|
require_webauthn_setup
|
|
211
211
|
before_webauthn_remove_route
|
|
@@ -469,6 +469,11 @@ module Rodauth
|
|
|
469
469
|
# :nocov:
|
|
470
470
|
end
|
|
471
471
|
|
|
472
|
+
def webauthn_modification_authenticated?
|
|
473
|
+
two_factor_login_type_match?('webauthn') &&
|
|
474
|
+
(!uses_two_factor_authentication? || two_factor_modifications_require_password?)
|
|
475
|
+
end
|
|
476
|
+
|
|
472
477
|
def _two_factor_auth_links
|
|
473
478
|
links = super
|
|
474
479
|
links << [10, webauthn_auth_path, webauthn_auth_link_text] if webauthn_setup? && !two_factor_login_type_match?('webauthn')
|
|
@@ -501,7 +506,7 @@ module Rodauth
|
|
|
501
506
|
end
|
|
502
507
|
|
|
503
508
|
def webauthn_account_id
|
|
504
|
-
|
|
509
|
+
account_id_or_session_value
|
|
505
510
|
end
|
|
506
511
|
|
|
507
512
|
def webauthn_user_ids_ds
|
|
@@ -76,7 +76,7 @@ module Rodauth
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def account_from_webauthn_login
|
|
79
|
-
account_from_login(login_param_value)
|
|
79
|
+
@account_retrieval_type == :login ? @account : account_from_login(login_param_value)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def webauthn_login_options?
|
|
@@ -91,14 +91,5 @@ module Rodauth
|
|
|
91
91
|
end
|
|
92
92
|
forms
|
|
93
93
|
end
|
|
94
|
-
|
|
95
|
-
def webauthn_account_id
|
|
96
|
-
case @current_route
|
|
97
|
-
when :login, :webauthn_login, nil # nil for internal request
|
|
98
|
-
account_id
|
|
99
|
-
else
|
|
100
|
-
super
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
94
|
end
|
|
104
95
|
end
|