rodauth 2.0.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +58 -0
- data/README.rdoc +14 -0
- data/doc/base.rdoc +2 -0
- data/doc/guides/admin_activation.rdoc +46 -0
- data/doc/guides/already_authenticated.rdoc +10 -0
- data/doc/guides/alternative_login.rdoc +46 -0
- data/doc/guides/create_account_programmatically.rdoc +38 -0
- data/doc/guides/delay_password.rdoc +25 -0
- data/doc/guides/email_only.rdoc +16 -0
- data/doc/guides/i18n.rdoc +26 -0
- data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
- data/doc/guides/links.rdoc +12 -0
- data/doc/guides/login_return.rdoc +37 -0
- data/doc/guides/password_column.rdoc +25 -0
- data/doc/guides/password_confirmation.rdoc +37 -0
- data/doc/guides/password_requirements.rdoc +30 -0
- data/doc/guides/paths.rdoc +36 -0
- data/doc/guides/query_params.rdoc +9 -0
- data/doc/guides/redirects.rdoc +17 -0
- data/doc/guides/registration_field.rdoc +68 -0
- data/doc/guides/require_mfa.rdoc +30 -0
- data/doc/guides/reset_password_autologin.rdoc +21 -0
- data/doc/guides/status_column.rdoc +28 -0
- data/doc/guides/totp_or_recovery.rdoc +16 -0
- data/doc/jwt_refresh.rdoc +11 -0
- data/doc/login.rdoc +8 -0
- data/doc/login_password_requirements_base.rdoc +3 -0
- data/doc/otp.rdoc +1 -0
- data/doc/password_pepper.rdoc +44 -0
- data/doc/release_notes/2.1.0.txt +31 -0
- data/doc/release_notes/2.2.0.txt +39 -0
- data/doc/release_notes/2.3.0.txt +37 -0
- data/doc/release_notes/2.4.0.txt +22 -0
- data/doc/release_notes/2.5.0.txt +20 -0
- data/doc/verify_login_change.rdoc +1 -0
- data/lib/rodauth.rb +5 -5
- data/lib/rodauth/features/active_sessions.rb +5 -7
- data/lib/rodauth/features/audit_logging.rb +2 -0
- data/lib/rodauth/features/base.rb +21 -2
- data/lib/rodauth/features/change_password.rb +1 -1
- data/lib/rodauth/features/close_account.rb +8 -6
- data/lib/rodauth/features/create_account.rb +1 -0
- data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
- data/lib/rodauth/features/email_auth.rb +2 -2
- data/lib/rodauth/features/http_basic_auth.rb +15 -2
- data/lib/rodauth/features/jwt.rb +12 -8
- data/lib/rodauth/features/jwt_cors.rb +15 -15
- data/lib/rodauth/features/jwt_refresh.rb +39 -10
- data/lib/rodauth/features/login.rb +23 -12
- data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
- data/lib/rodauth/features/otp.rb +5 -1
- data/lib/rodauth/features/password_complexity.rb +4 -2
- data/lib/rodauth/features/password_pepper.rb +45 -0
- data/lib/rodauth/features/remember.rb +2 -0
- data/lib/rodauth/features/session_expiration.rb +1 -6
- data/lib/rodauth/features/single_session.rb +1 -1
- data/lib/rodauth/features/sms_codes.rb +0 -1
- data/lib/rodauth/features/two_factor_base.rb +4 -4
- data/lib/rodauth/features/verify_account.rb +5 -0
- data/lib/rodauth/features/verify_account_grace_period.rb +3 -5
- data/lib/rodauth/features/verify_login_change.rb +2 -1
- data/lib/rodauth/features/webauthn.rb +1 -3
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/migrations.rb +16 -5
- data/lib/rodauth/version.rb +1 -1
- data/templates/password-field.str +1 -1
- metadata +37 -5
@@ -0,0 +1,37 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Configuration methods have been added for easier validation of
|
4
|
+
logins when logins must be valid email addresses (the default):
|
5
|
+
|
6
|
+
* login_valid_email?(login) can be used for full control of
|
7
|
+
determining whether the login is valid.
|
8
|
+
|
9
|
+
* login_email_regexp can be used to set the regexp used in the
|
10
|
+
default login_valid_email? check.
|
11
|
+
|
12
|
+
* login_not_valid_email_message can be used to set the field
|
13
|
+
error message if the login is not a valid email. Previously, this
|
14
|
+
value was hardcoded and not translatable.
|
15
|
+
|
16
|
+
* The {create,drop}_database_authentication_functions now work
|
17
|
+
correctly with uuid keys on PostgreSQL. All other parts of
|
18
|
+
Rodauth already worked correctly with uuid keys.
|
19
|
+
|
20
|
+
= Other Improvements
|
21
|
+
|
22
|
+
* The before_jwt_refresh_route hook is now called before the route
|
23
|
+
is taken. Previously, the configuration method had no effect.
|
24
|
+
|
25
|
+
* rodauth.login can now be used by external code to login the current
|
26
|
+
account (the account that rodauth.account returns). This should be
|
27
|
+
passed the authentication type string used to login, such as
|
28
|
+
password.
|
29
|
+
|
30
|
+
* The jwt_refresh route now returns an error for requests where a
|
31
|
+
valid access token for a logged in session is not provided. You
|
32
|
+
can use the jwt_refresh_without_access_token_message and
|
33
|
+
jwt_refresh_without_access_token_status configuration methods
|
34
|
+
to configure the error response.
|
35
|
+
|
36
|
+
* The new refresh token is now available to the after_refresh_token
|
37
|
+
hook by looking in json_response[jwt_refresh_token_key].
|
@@ -0,0 +1,22 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A password_pepper feature has been added. This allows you to use a
|
4
|
+
secret key (called a pepper) to append to passwords before hashing
|
5
|
+
and hash checking. Using this approach, if an attacker obtains the
|
6
|
+
password hash, it is unusable for cracking unless they can also
|
7
|
+
get access to the pepper.
|
8
|
+
|
9
|
+
The password_pepper feature also supports a list of previous peppers
|
10
|
+
that can be used to implement secret rotation and to support
|
11
|
+
compatibility with unpeppered passwords.
|
12
|
+
|
13
|
+
Rodauth by default uses database functions for password hash
|
14
|
+
checking on PostgreSQL, MySQL, and Microsoft SQL Server, which in
|
15
|
+
general provides more security than a password pepper, but both
|
16
|
+
approaches can be used simultaneously.
|
17
|
+
|
18
|
+
* A session_key_prefix configuration method has been added for
|
19
|
+
prefixing the values of all default session keys. This can be
|
20
|
+
useful if you are using multiple Rodauth configurations in the same
|
21
|
+
application and want to make sure the session keys for the separate
|
22
|
+
configurations do not overlap.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A login_return_to_requested_location_path configuration method has
|
4
|
+
been added to the login feature. This controls the path to redirect
|
5
|
+
to if using login_return_to_requested_location?. By default, this
|
6
|
+
is the same as the fullpath of the request that required login if
|
7
|
+
that request was a GET request, and nil if that request was not a
|
8
|
+
GET request. Previously, the fullpath of that request was used even
|
9
|
+
if it was not a GET request, which caused problems as browsers use a
|
10
|
+
GET request for redirects, and it is a bad idea to redirect to a path
|
11
|
+
that may not handle GET requests.
|
12
|
+
|
13
|
+
* A change_login_needs_verification_notice_flash configuration method
|
14
|
+
has been added to the verify_login_change feature, for allowing
|
15
|
+
translations when using the feature and not using the
|
16
|
+
change_login_notice_flash configuration method.
|
17
|
+
|
18
|
+
= Other Improvements
|
19
|
+
|
20
|
+
* new_password_label is now translatable.
|
@@ -14,6 +14,7 @@ control. Depends on the change login and email base features.
|
|
14
14
|
== Auth Value Methods
|
15
15
|
|
16
16
|
no_matching_verify_login_change_key_error_flash :: The flash error message to show when an invalid verify login change key is used.
|
17
|
+
change_login_needs_verification_notice_flash :: The flash notice to show after changing a login when using this feature, if +change_login_notice_flash+ is not overridden.
|
17
18
|
verify_login_change_additional_form_tags :: HTML fragment containing additional form tags to use on the verify login change form.
|
18
19
|
verify_login_change_autologin? :: Whether to autologin the user after successful login change verification, false by default.
|
19
20
|
verify_login_change_button :: The text to use for the verify login change button.
|
data/lib/rodauth.rb
CHANGED
@@ -119,7 +119,7 @@ module Rodauth
|
|
119
119
|
|
120
120
|
define_method(handle_meth) do
|
121
121
|
request.is send(route_meth) do
|
122
|
-
|
122
|
+
check_csrf if check_csrf?
|
123
123
|
before_rodauth
|
124
124
|
send(internal_handle_meth, request)
|
125
125
|
end
|
@@ -137,7 +137,9 @@ module Rodauth
|
|
137
137
|
feature.module_eval(&block)
|
138
138
|
configuration.def_configuration_methods(feature)
|
139
139
|
|
140
|
+
# :nocov:
|
140
141
|
if constant
|
142
|
+
# :nocov:
|
141
143
|
Rodauth.const_set(constant, feature)
|
142
144
|
Rodauth::FeatureConfiguration.const_set(constant, configuration)
|
143
145
|
end
|
@@ -336,10 +338,8 @@ module Rodauth
|
|
336
338
|
end
|
337
339
|
|
338
340
|
def freeze
|
339
|
-
|
340
|
-
|
341
|
-
opts[:rodauths].freeze
|
342
|
-
end
|
341
|
+
opts[:rodauths].each_value(&:freeze)
|
342
|
+
opts[:rodauths].freeze
|
343
343
|
super
|
344
344
|
end
|
345
345
|
end
|
@@ -118,14 +118,12 @@ module Rodauth
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def before_logout
|
121
|
-
if
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
remove_current_session
|
126
|
-
end
|
121
|
+
if param_or_nil(global_logout_param)
|
122
|
+
remove_all_active_sessions
|
123
|
+
else
|
124
|
+
remove_current_session
|
127
125
|
end
|
128
|
-
super
|
126
|
+
super
|
129
127
|
end
|
130
128
|
|
131
129
|
def session_inactivity_deadline_condition
|
@@ -82,7 +82,9 @@ module Rodauth
|
|
82
82
|
|
83
83
|
def audit_log_ds
|
84
84
|
ds = db[audit_logging_table]
|
85
|
+
# :nocov:
|
85
86
|
if db.database_type == :postgres
|
87
|
+
# :nocov:
|
86
88
|
# For PostgreSQL, use RETURNING NULL. This allows the feature
|
87
89
|
# to be used with INSERT but not SELECT permissions on the
|
88
90
|
# table, useful for audit logging where the database user
|
@@ -47,6 +47,7 @@ module Rodauth
|
|
47
47
|
session_key :authenticated_by_session_key, :authenticated_by
|
48
48
|
session_key :autologin_type_session_key, :autologin_type
|
49
49
|
auth_value_method :prefix, ''
|
50
|
+
auth_value_method :session_key_prefix, nil
|
50
51
|
auth_value_method :require_bcrypt?, true
|
51
52
|
auth_value_method :mark_input_fields_as_required?, true
|
52
53
|
auth_value_method :mark_input_fields_with_autocomplete?, true
|
@@ -82,6 +83,7 @@ module Rodauth
|
|
82
83
|
:already_logged_in,
|
83
84
|
:authenticated?,
|
84
85
|
:autocomplete_for_field?,
|
86
|
+
:check_csrf,
|
85
87
|
:clear_session,
|
86
88
|
:csrf_tag,
|
87
89
|
:function_name,
|
@@ -254,6 +256,10 @@ module Rodauth
|
|
254
256
|
Sequel::DATABASES.first
|
255
257
|
end
|
256
258
|
|
259
|
+
def password_field_autocomplete_value
|
260
|
+
@password_field_autocomplete_value || 'current-password'
|
261
|
+
end
|
262
|
+
|
257
263
|
# If the account_password_hash_column is set, the password hash is verified in
|
258
264
|
# ruby, it will not use a database function to do so, it will check the password
|
259
265
|
# hash using bcrypt.
|
@@ -333,6 +339,10 @@ module Rodauth
|
|
333
339
|
@account = _account_from_session
|
334
340
|
end
|
335
341
|
|
342
|
+
def check_csrf
|
343
|
+
scope.check_csrf!(check_csrf_opts, &check_csrf_block)
|
344
|
+
end
|
345
|
+
|
336
346
|
def csrf_tag(path=request.path)
|
337
347
|
return unless scope.respond_to?(:csrf_tag)
|
338
348
|
|
@@ -384,9 +394,9 @@ module Rodauth
|
|
384
394
|
def password_match?(password)
|
385
395
|
if hash = get_password_hash
|
386
396
|
if account_password_hash_column || !use_database_authentication_functions?
|
387
|
-
|
397
|
+
password_hash_match?(hash, password)
|
388
398
|
else
|
389
|
-
|
399
|
+
database_function_password_match?(:rodauth_valid_password_hash, account_id, password, hash)
|
390
400
|
end
|
391
401
|
end
|
392
402
|
end
|
@@ -449,6 +459,14 @@ module Rodauth
|
|
449
459
|
|
450
460
|
private
|
451
461
|
|
462
|
+
def database_function_password_match?(name, hash_id, password, salt)
|
463
|
+
db.get(Sequel.function(function_name(name), hash_id, BCrypt::Engine.hash_secret(password, salt)))
|
464
|
+
end
|
465
|
+
|
466
|
+
def password_hash_match?(hash, password)
|
467
|
+
BCrypt::Password.new(hash) == password
|
468
|
+
end
|
469
|
+
|
452
470
|
def convert_token_key(key)
|
453
471
|
if key && hmac_secret
|
454
472
|
compute_hmac(key)
|
@@ -484,6 +502,7 @@ module Rodauth
|
|
484
502
|
end
|
485
503
|
|
486
504
|
def convert_session_key(key)
|
505
|
+
key = "#{session_key_prefix}#{key}".to_sym if session_key_prefix
|
487
506
|
scope.opts[:sessions_convert_symbols] ? key.to_s : key
|
488
507
|
end
|
489
508
|
|
@@ -33,7 +33,11 @@ module Rodauth
|
|
33
33
|
end
|
34
34
|
|
35
35
|
r.post do
|
36
|
-
|
36
|
+
catch_error do
|
37
|
+
if close_account_requires_password? && !password_match?(param(password_param))
|
38
|
+
throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
|
39
|
+
end
|
40
|
+
|
37
41
|
transaction do
|
38
42
|
before_close_account
|
39
43
|
close_account
|
@@ -46,12 +50,10 @@ module Rodauth
|
|
46
50
|
|
47
51
|
set_notice_flash close_account_notice_flash
|
48
52
|
redirect close_account_redirect
|
49
|
-
else
|
50
|
-
set_response_error_status(invalid_password_error_status)
|
51
|
-
set_field_error(password_param, invalid_password_message)
|
52
|
-
set_error_flash close_account_error_flash
|
53
|
-
close_account_view
|
54
53
|
end
|
54
|
+
|
55
|
+
set_error_flash close_account_error_flash
|
56
|
+
close_account_view
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -51,11 +51,13 @@ module Rodauth
|
|
51
51
|
return true if salts.empty?
|
52
52
|
|
53
53
|
salts.any? do |hash_id, salt|
|
54
|
-
|
54
|
+
database_function_password_match?(:rodauth_previous_password_hash_match, hash_id, password, salt)
|
55
55
|
end
|
56
56
|
else
|
57
57
|
# :nocov:
|
58
|
-
previous_password_ds.select_map(previous_password_hash_column).any?
|
58
|
+
previous_password_ds.select_map(previous_password_hash_column).any? do |hash|
|
59
|
+
password_hash_match?(hash, password)
|
60
|
+
end
|
59
61
|
# :nocov:
|
60
62
|
end
|
61
63
|
|
@@ -94,7 +94,7 @@ module Rodauth
|
|
94
94
|
redirect email_auth_email_sent_redirect
|
95
95
|
end
|
96
96
|
|
97
|
-
|
97
|
+
login('email_auth')
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
@@ -215,7 +215,7 @@ module Rodauth
|
|
215
215
|
# that allows login access to the account becomes a
|
216
216
|
# security liability, and it is best to remove it.
|
217
217
|
remove_email_auth_key
|
218
|
-
super
|
218
|
+
super
|
219
219
|
end
|
220
220
|
|
221
221
|
def after_close_account
|
@@ -5,11 +5,20 @@ module Rodauth
|
|
5
5
|
auth_value_method :http_basic_auth_realm, "protected"
|
6
6
|
auth_value_method :require_http_basic_auth?, false
|
7
7
|
|
8
|
+
def logged_in?
|
9
|
+
ret = super
|
10
|
+
|
11
|
+
if !ret && !defined?(@checked_http_basic_auth)
|
12
|
+
http_basic_auth
|
13
|
+
ret = super
|
14
|
+
end
|
15
|
+
|
16
|
+
ret
|
17
|
+
end
|
18
|
+
|
8
19
|
def require_login
|
9
20
|
if require_http_basic_auth?
|
10
21
|
require_http_basic_auth
|
11
|
-
elsif !logged_in?
|
12
|
-
http_basic_auth
|
13
22
|
end
|
14
23
|
|
15
24
|
super
|
@@ -23,6 +32,9 @@ module Rodauth
|
|
23
32
|
end
|
24
33
|
|
25
34
|
def http_basic_auth
|
35
|
+
return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
|
36
|
+
|
37
|
+
@checked_http_basic_auth = nil
|
26
38
|
return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
|
27
39
|
|
28
40
|
username, password = token.unpack("m*").first.split(/:/, 2)
|
@@ -50,6 +62,7 @@ module Rodauth
|
|
50
62
|
after_login
|
51
63
|
end
|
52
64
|
|
65
|
+
@checked_http_basic_auth = true
|
53
66
|
return true
|
54
67
|
end
|
55
68
|
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -50,7 +50,7 @@ module Rodauth
|
|
50
50
|
json_response[json_response_error_key] = invalid_jwt_format_error_message
|
51
51
|
response.status ||= json_response_error_status
|
52
52
|
response['Content-Type'] ||= json_response_content_type
|
53
|
-
response.write(
|
53
|
+
response.write(_json_response_body(json_response))
|
54
54
|
request.halt
|
55
55
|
end
|
56
56
|
|
@@ -138,15 +138,25 @@ module Rodauth
|
|
138
138
|
!!(jwt_token && jwt_payload)
|
139
139
|
end
|
140
140
|
|
141
|
+
def view(page, title)
|
142
|
+
return super unless use_jwt?
|
143
|
+
return_json_response
|
144
|
+
end
|
145
|
+
|
141
146
|
private
|
142
147
|
|
148
|
+
def check_csrf?
|
149
|
+
return false if use_jwt?
|
150
|
+
super
|
151
|
+
end
|
152
|
+
|
143
153
|
def before_rodauth
|
144
154
|
if json_request?
|
145
155
|
if jwt_check_accept? && (accept = request.env['HTTP_ACCEPT']) && accept !~ json_accept_regexp
|
146
156
|
response.status = 406
|
147
157
|
json_response[json_response_error_key] = json_not_accepted_error_message
|
148
158
|
response['Content-Type'] ||= json_response_content_type
|
149
|
-
response.write(
|
159
|
+
response.write(_json_response_body(json_response))
|
150
160
|
request.halt
|
151
161
|
end
|
152
162
|
|
@@ -251,12 +261,6 @@ module Rodauth
|
|
251
261
|
@json_response ||= {}
|
252
262
|
end
|
253
263
|
|
254
|
-
def _view(meth, page)
|
255
|
-
return super unless use_jwt?
|
256
|
-
return super if meth == :render
|
257
|
-
return_json_response
|
258
|
-
end
|
259
|
-
|
260
264
|
def _json_response_body(hash)
|
261
265
|
request.send(:convert_to_json, hash)
|
262
266
|
end
|
@@ -13,27 +13,27 @@ module Rodauth
|
|
13
13
|
auth_methods(:jwt_cors_allow?)
|
14
14
|
|
15
15
|
def jwt_cors_allow?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
return false unless origin = request.env['HTTP_ORIGIN']
|
17
|
+
|
18
|
+
case allowed = jwt_cors_allow_origin
|
19
|
+
when String
|
20
|
+
timing_safe_eql?(origin, allowed)
|
21
|
+
when Array
|
22
|
+
allowed.any?{|s| timing_safe_eql?(origin, s)}
|
23
|
+
when Regexp
|
24
|
+
allowed =~ origin
|
25
|
+
when true
|
26
|
+
true
|
27
|
+
else
|
28
|
+
false
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def before_rodauth
|
35
|
-
if
|
36
|
-
response['Access-Control-Allow-Origin'] =
|
35
|
+
if jwt_cors_allow?
|
36
|
+
response['Access-Control-Allow-Origin'] = request.env['HTTP_ORIGIN']
|
37
37
|
|
38
38
|
# Handle CORS preflight request
|
39
39
|
if request.request_method == 'OPTIONS'
|
@@ -19,23 +19,29 @@ module Rodauth
|
|
19
19
|
auth_value_method :jwt_refresh_token_key_column, :key
|
20
20
|
auth_value_method :jwt_refresh_token_key_param, 'refresh_token'
|
21
21
|
auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
|
22
|
+
translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
|
23
|
+
auth_value_method :jwt_refresh_without_access_token_status, 401
|
22
24
|
|
23
25
|
auth_private_methods(
|
24
26
|
:account_from_refresh_token
|
25
27
|
)
|
26
28
|
|
27
29
|
route do |r|
|
30
|
+
before_jwt_refresh_route
|
31
|
+
|
28
32
|
r.post do
|
29
|
-
if
|
30
|
-
|
33
|
+
if !session_value
|
34
|
+
response.status ||= jwt_refresh_without_access_token_status
|
35
|
+
json_response[json_response_error_key] = jwt_refresh_without_access_token_message
|
36
|
+
elsif (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
|
31
37
|
transaction do
|
32
38
|
before_refresh_token
|
33
39
|
formatted_token = generate_refresh_token
|
34
40
|
remove_jwt_refresh_token_key(refresh_token)
|
41
|
+
json_response[jwt_refresh_token_key] = formatted_token
|
42
|
+
json_response[jwt_access_token_key] = session_jwt
|
35
43
|
after_refresh_token
|
36
44
|
end
|
37
|
-
json_response[jwt_refresh_token_key] = formatted_token
|
38
|
-
json_response[jwt_access_token_key] = session_jwt
|
39
45
|
else
|
40
46
|
json_response[json_response_error_key] = jwt_refresh_invalid_token_message
|
41
47
|
response.status ||= json_response_error_status
|
@@ -80,14 +86,10 @@ module Rodauth
|
|
80
86
|
private
|
81
87
|
|
82
88
|
def _account_from_refresh_token(token)
|
83
|
-
id,
|
84
|
-
return unless id && token
|
85
|
-
|
86
|
-
token_id, key = split_token(token)
|
87
|
-
return unless token_id && key
|
89
|
+
id, token_id, key = _account_refresh_token_split(token)
|
88
90
|
|
91
|
+
return unless key
|
89
92
|
return unless actual = get_active_refresh_token(id, token_id)
|
90
|
-
|
91
93
|
return unless timing_safe_eql?(key, convert_token_key(actual))
|
92
94
|
|
93
95
|
ds = account_ds(id)
|
@@ -95,6 +97,16 @@ module Rodauth
|
|
95
97
|
ds.first
|
96
98
|
end
|
97
99
|
|
100
|
+
def _account_refresh_token_split(token)
|
101
|
+
id, token = split_token(token)
|
102
|
+
return unless id && token
|
103
|
+
|
104
|
+
token_id, key = split_token(token)
|
105
|
+
return unless token_id && key
|
106
|
+
|
107
|
+
[id, token_id, key]
|
108
|
+
end
|
109
|
+
|
98
110
|
def get_active_refresh_token(account_id, token_id)
|
99
111
|
jwt_refresh_token_account_ds(account_id).
|
100
112
|
where(Sequel::CURRENT_TIMESTAMP > jwt_refresh_token_deadline_column).
|
@@ -134,6 +146,23 @@ module Rodauth
|
|
134
146
|
hash
|
135
147
|
end
|
136
148
|
|
149
|
+
def before_logout
|
150
|
+
if token = param_or_nil(jwt_refresh_token_key_param)
|
151
|
+
if token == 'all'
|
152
|
+
jwt_refresh_token_account_ds(session_value).delete
|
153
|
+
else
|
154
|
+
id, token_id, key = _account_refresh_token_split(token)
|
155
|
+
|
156
|
+
if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
|
157
|
+
jwt_refresh_token_account_ds(id).
|
158
|
+
where(jwt_refresh_token_id_column=>token_id).
|
159
|
+
delete
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
super if defined?(super)
|
164
|
+
end
|
165
|
+
|
137
166
|
def after_close_account
|
138
167
|
jwt_refresh_token_account_ds(account_id).delete
|
139
168
|
super if defined?(super)
|