devise_token_auth 1.2.5 → 1.3.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/README.md +1 -0
- data/app/controllers/devise_token_auth/passwords_controller.rb +2 -2
- data/app/controllers/devise_token_auth/sessions_controller.rb +1 -1
- data/app/models/devise_token_auth/concerns/confirmable_support.rb +1 -1
- data/app/models/devise_token_auth/concerns/user.rb +25 -13
- data/lib/devise_token_auth/rails/routes.rb +14 -1
- data/lib/devise_token_auth/version.rb +1 -1
- data/test/controllers/devise_token_auth/confirmations_controller_test.rb +4 -4
- data/test/controllers/devise_token_auth/passwords_controller_test.rb +90 -29
- data/test/dummy/config/application.rb +1 -0
- data/test/dummy/config/environments/test.rb +9 -2
- data/test/lib/devise_token_auth/rails/custom_routes_test.rb +5 -4
- data/test/models/user_test.rb +121 -0
- data/test/test_helper.rb +8 -1
- metadata +27 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d50f68558fe641e4412661bdd4dfc15ce37b047300353cd4736c7e314c9fed9
|
|
4
|
+
data.tar.gz: e73de38339a0d663e78bf8e0abbb416b46078440b922a11cf9e49ff7ec2e6bc5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b2f6973ffc5ba8b3763f36eeda7581fd698cfe1c6a556e714ec32c94b7f21e9f2da9b5b871c59419c1a3f26249dc1d560cb28b03b228fa9c4e5a81a294c12c4
|
|
7
|
+
data.tar.gz: 6d14031a8a4269655bd4f0d9c03f39fc360112e331f1e1e14f583127d58effbe32d23d1a5852cdf5bb5b07fce6a8ae05f6391863ccbff4c8cf9020e09773496d
|
data/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Also, it maintains a session for each client/device, so you can have as many ses
|
|
|
23
23
|
* [redux-token-auth](https://github.com/kylecorbelli/redux-token-auth) for [React with Redux](https://github.com/reactjs/react-redux)
|
|
24
24
|
* [jToker](https://github.com/lynndylanhurley/j-toker) for [jQuery](https://jquery.com/)
|
|
25
25
|
* [vanilla-token-auth](https://github.com/theblang/vanilla-token-auth) for an unopinionated client
|
|
26
|
+
* [flutter_token_auth](https://github.com/diarmuidr3d/flutter_token_auth) for Flutter
|
|
26
27
|
* Oauth2 authentication using [OmniAuth](https://github.com/intridea/omniauth).
|
|
27
28
|
* Email authentication using [Devise](https://github.com/plataformatec/devise), including:
|
|
28
29
|
* User registration, update and deletion
|
|
@@ -56,7 +56,7 @@ module DeviseTokenAuth
|
|
|
56
56
|
set_token_in_cookie(@resource, token)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
redirect_header_options = { reset_password: true }
|
|
59
|
+
redirect_header_options = { reset_password: true, reset_password_token: resource_params[:reset_password_token] }
|
|
60
60
|
redirect_headers = build_redirect_headers(token.token,
|
|
61
61
|
token.client,
|
|
62
62
|
redirect_header_options)
|
|
@@ -73,7 +73,7 @@ module DeviseTokenAuth
|
|
|
73
73
|
# make sure user is authorized
|
|
74
74
|
if require_client_password_reset_token? && resource_params[:reset_password_token]
|
|
75
75
|
@resource = resource_class.with_reset_password_token(resource_params[:reset_password_token])
|
|
76
|
-
return render_update_error_unauthorized unless @resource
|
|
76
|
+
return render_update_error_unauthorized unless @resource && @resource.reset_password_period_valid?
|
|
77
77
|
|
|
78
78
|
@token = @resource.create_token
|
|
79
79
|
else
|
|
@@ -131,7 +131,7 @@ module DeviseTokenAuth
|
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def create_and_assign_token
|
|
134
|
-
if @resource.respond_to?(:with_lock)
|
|
134
|
+
if @resource.respond_to?(:with_lock) && !@resource.changed?
|
|
135
135
|
@resource.with_lock do
|
|
136
136
|
@token = @resource.create_token
|
|
137
137
|
@resource.save!
|
|
@@ -3,7 +3,7 @@ module DeviseTokenAuth::Concerns::ConfirmableSupport
|
|
|
3
3
|
|
|
4
4
|
included do
|
|
5
5
|
# Override standard devise `postpone_email_change?` method
|
|
6
|
-
# for not to use `
|
|
6
|
+
# for not to use `devise_will_save_change_to_email?` methods.
|
|
7
7
|
def postpone_email_change?
|
|
8
8
|
postpone = self.class.reconfirmable &&
|
|
9
9
|
email_value_in_database != email &&
|
|
@@ -41,8 +41,7 @@ module DeviseTokenAuth::Concerns::User
|
|
|
41
41
|
|
|
42
42
|
# don't use default devise email validation
|
|
43
43
|
def email_required?; false; end
|
|
44
|
-
def
|
|
45
|
-
def will_save_change_to_email?; false; end
|
|
44
|
+
def devise_will_save_change_to_email?; false; end
|
|
46
45
|
|
|
47
46
|
if DeviseTokenAuth.send_confirmation_email && devise_modules.include?(:confirmable)
|
|
48
47
|
include DeviseTokenAuth::Concerns::ConfirmableSupport
|
|
@@ -259,17 +258,30 @@ module DeviseTokenAuth::Concerns::User
|
|
|
259
258
|
end
|
|
260
259
|
|
|
261
260
|
def clean_old_tokens
|
|
262
|
-
if tokens.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
261
|
+
return if tokens.blank? || !max_client_tokens_exceeded?
|
|
262
|
+
|
|
263
|
+
# First, remove any tokens with expiry greater than current max allowed lifespan
|
|
264
|
+
# this handles the case where token lifespan was reduced and old tokens exist.
|
|
265
|
+
# The threshold MUST be computed the same way TokenFactory.expiry writes
|
|
266
|
+
# expiries — calendar advance (Time.zone.now + lifespan) — otherwise a
|
|
267
|
+
# calendar Duration (e.g. 2.months) and its average-seconds form
|
|
268
|
+
# (token_lifespan.to_i) disagree by hours, and the freshly minted token
|
|
269
|
+
# gets purged out from under the response.
|
|
270
|
+
max_lifespan_expiry = (Time.zone.now + DeviseTokenAuth.token_lifespan).to_i
|
|
271
|
+
tokens_to_keep = tokens.select do |_cid, v|
|
|
272
|
+
expiry = (v[:expiry] || v['expiry']).to_i
|
|
273
|
+
expiry <= max_lifespan_expiry
|
|
273
274
|
end
|
|
275
|
+
|
|
276
|
+
# Using Enumerable#sort_by on a Hash will typecast it into an associative
|
|
277
|
+
# Array (i.e. an Array of key-value Array pairs). However, since Hashes
|
|
278
|
+
# have an internal order in Ruby 1.9+, the resulting sorted associative
|
|
279
|
+
# Array can be converted back into a Hash, while maintaining the sorted
|
|
280
|
+
# order.
|
|
281
|
+
self.tokens = tokens_to_keep.sort_by { |_cid, v| v[:expiry] || v['expiry'] }.to_h
|
|
282
|
+
|
|
283
|
+
# Since the tokens are sorted by expiry, shift the oldest client token
|
|
284
|
+
# off the Hash until it no longer exceeds the maximum number of clients
|
|
285
|
+
tokens.shift while max_client_tokens_exceeded?
|
|
274
286
|
end
|
|
275
287
|
end
|
|
@@ -76,7 +76,20 @@ module ActionDispatch::Routing
|
|
|
76
76
|
match "#{full_path}/:provider", to: redirect(status: 307) { |params, request|
|
|
77
77
|
# get the current querystring
|
|
78
78
|
# TODO: deprecate in favor of using params
|
|
79
|
-
|
|
79
|
+
# `CGI.parse` is unavailable as of the cgi gem shipped with Ruby 3.5
|
|
80
|
+
# (pulled in by Rails 8.1). `Rack::Utils.parse_query` returns a bare
|
|
81
|
+
# value for single occurrences, so normalize to arrays to keep the
|
|
82
|
+
# shape `CGI.parse` returned.
|
|
83
|
+
# As of Rails 7.1 the request body may already have been read when
|
|
84
|
+
# this runs, in which case `read` returns "" and every param is
|
|
85
|
+
# silently dropped. Rewind first so POSTed params survive.
|
|
86
|
+
query_string = if request.env['QUERY_STRING'].empty?
|
|
87
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
88
|
+
request.body.read
|
|
89
|
+
else
|
|
90
|
+
request.env['QUERY_STRING']
|
|
91
|
+
end
|
|
92
|
+
qs = Rack::Utils.parse_query(query_string).transform_values { |v| Array(v) }
|
|
80
93
|
|
|
81
94
|
# append name of current resource
|
|
82
95
|
qs['resource_class'] = [resource]
|
|
@@ -62,8 +62,8 @@ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
test 'redirect url includes token params' do
|
|
65
|
-
assert @token_params.all? { |param| response.
|
|
66
|
-
assert response.
|
|
65
|
+
assert @token_params.all? { |param| response.location.include?(param) }
|
|
66
|
+
assert response.location.include?('account_confirmation_success')
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -86,8 +86,8 @@ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
test 'redirect url does not include token params' do
|
|
89
|
-
refute @token_params.any? { |param| response.
|
|
90
|
-
assert response.
|
|
89
|
+
refute @token_params.any? { |param| response.location.include?(param) }
|
|
90
|
+
assert response.location.include?('account_confirmation_success')
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
@@ -229,42 +229,73 @@ class DeviseTokenAuth::PasswordsControllerTest < ActionController::TestCase
|
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
describe 'password reset link success' do
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
describe 'with require_client_password_reset_token is enabled' do
|
|
233
|
+
before do
|
|
234
|
+
DeviseTokenAuth.require_client_password_reset_token = true
|
|
235
|
+
get :edit,
|
|
236
|
+
params: { reset_password_token: @mail_reset_token,
|
|
237
|
+
redirect_url: @mail_redirect_url }
|
|
236
238
|
|
|
237
|
-
|
|
239
|
+
@resource.reload
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
raw_qs = response.location.split('?')[1]
|
|
242
|
+
@qs = Rack::Utils.parse_nested_query(raw_qs)
|
|
241
243
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
@client = @qs['client']
|
|
245
|
-
@expiry = @qs['expiry']
|
|
246
|
-
@reset_password = @qs['reset_password']
|
|
247
|
-
@token = @qs['token']
|
|
248
|
-
@uid = @qs['uid']
|
|
249
|
-
end
|
|
244
|
+
@reset_password_token = @qs['reset_password_token']
|
|
245
|
+
end
|
|
250
246
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
247
|
+
test 'response should have success redirect status' do
|
|
248
|
+
assert_equal 302, response.status
|
|
249
|
+
end
|
|
254
250
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
assert @client_id
|
|
259
|
-
assert @expiry
|
|
260
|
-
assert @reset_password
|
|
261
|
-
assert @token
|
|
262
|
-
assert @uid
|
|
251
|
+
test 'response should contain reset_password_token param' do
|
|
252
|
+
assert_equal @mail_reset_token, @qs['reset_password_token']
|
|
253
|
+
end
|
|
263
254
|
end
|
|
264
255
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
256
|
+
describe 'require_client_password_reset_token is disabled' do
|
|
257
|
+
before do
|
|
258
|
+
DeviseTokenAuth.require_client_password_reset_token = false
|
|
259
|
+
get :edit,
|
|
260
|
+
params: { reset_password_token: @mail_reset_token,
|
|
261
|
+
redirect_url: @mail_redirect_url }
|
|
262
|
+
|
|
263
|
+
@resource.reload
|
|
264
|
+
|
|
265
|
+
raw_qs = response.location.split('?')[1]
|
|
266
|
+
@qs = Rack::Utils.parse_nested_query(raw_qs)
|
|
267
|
+
|
|
268
|
+
@access_token = @qs['access-token']
|
|
269
|
+
@client_id = @qs['client_id']
|
|
270
|
+
@client = @qs['client']
|
|
271
|
+
@expiry = @qs['expiry']
|
|
272
|
+
@reset_password = @qs['reset_password']
|
|
273
|
+
@token = @qs['token']
|
|
274
|
+
@uid = @qs['uid']
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
test 'response should have success redirect status' do
|
|
278
|
+
assert_equal 302, response.status
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
test 'response should contain auth params' do
|
|
282
|
+
assert @access_token
|
|
283
|
+
assert @client
|
|
284
|
+
assert @client_id
|
|
285
|
+
assert @expiry
|
|
286
|
+
assert @reset_password
|
|
287
|
+
assert @token
|
|
288
|
+
assert @uid
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
test 'response auth params should be valid' do
|
|
292
|
+
assert @resource.valid_token?(@token, @client_id)
|
|
293
|
+
assert @resource.valid_token?(@access_token, @client)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
test 'response should contain reset_password_token param' do
|
|
297
|
+
assert_equal @mail_reset_token, @qs['reset_password_token']
|
|
298
|
+
end
|
|
268
299
|
end
|
|
269
300
|
end
|
|
270
301
|
end
|
|
@@ -715,6 +746,36 @@ class DeviseTokenAuth::PasswordsControllerTest < ActionController::TestCase
|
|
|
715
746
|
end
|
|
716
747
|
end
|
|
717
748
|
|
|
749
|
+
describe 'with expired reset password token' do
|
|
750
|
+
before do
|
|
751
|
+
DeviseTokenAuth.require_client_password_reset_token = true
|
|
752
|
+
reset_password_token = @resource.send_reset_password_instructions
|
|
753
|
+
@resource.update! reset_password_sent_at: 2.days.ago
|
|
754
|
+
|
|
755
|
+
@new_password = Faker::Internet.password
|
|
756
|
+
@params = { password: @new_password,
|
|
757
|
+
password_confirmation: @new_password,
|
|
758
|
+
reset_password_token: reset_password_token }
|
|
759
|
+
|
|
760
|
+
put :update, params: @params
|
|
761
|
+
|
|
762
|
+
@data = JSON.parse(response.body)
|
|
763
|
+
@resource.reload
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
test 'request should fail' do
|
|
767
|
+
assert_equal 401, response.status
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
test 'new password should not authenticate user' do
|
|
771
|
+
assert !@resource.valid_password?(@new_password)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
teardown do
|
|
775
|
+
DeviseTokenAuth.require_client_password_reset_token = false
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
718
779
|
describe 'with invalid reset password token' do
|
|
719
780
|
before do
|
|
720
781
|
DeviseTokenAuth.require_client_password_reset_token = true
|
|
@@ -23,7 +23,7 @@ Rails.application.configure do
|
|
|
23
23
|
(config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }) :
|
|
24
24
|
(config.static_cache_control = 'public, max-age=3600')
|
|
25
25
|
|
|
26
|
-
if Rails::VERSION::MAJOR
|
|
26
|
+
if Rails::VERSION::MAJOR < 7 && ENV['DEVISE_TOKEN_AUTH_ORM'] != 'mongoid'
|
|
27
27
|
config.active_record.legacy_connection_handling = false
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -32,7 +32,14 @@ Rails.application.configure do
|
|
|
32
32
|
config.action_controller.perform_caching = false
|
|
33
33
|
|
|
34
34
|
# Raise exceptions instead of rendering exception templates.
|
|
35
|
-
|
|
35
|
+
# Rails 7.1 turned this into an enum; `false` is no longer recognised and
|
|
36
|
+
# falls through to the "show everything" default. Note the version check must
|
|
37
|
+
# not be `MAJOR >= 7 && MINOR > 0`, which is false on Rails 8.0.
|
|
38
|
+
if Rails.gem_version >= Gem::Version.new('7.1')
|
|
39
|
+
config.action_dispatch.show_exceptions = :none
|
|
40
|
+
else
|
|
41
|
+
config.action_dispatch.show_exceptions = false
|
|
42
|
+
end
|
|
36
43
|
|
|
37
44
|
# Disable request forgery protection in test environment.
|
|
38
45
|
config.action_controller.allow_forgery_protection = false
|
|
@@ -7,11 +7,12 @@ class DeviseTokenAuth::CustomRoutesTest < ActiveSupport::TestCase
|
|
|
7
7
|
Rails.application.reload_routes!
|
|
8
8
|
end
|
|
9
9
|
test 'custom controllers' do
|
|
10
|
-
class ActionDispatch::Routing::Mapper
|
|
11
|
-
include Mocha::ParameterMatchers
|
|
12
|
-
end
|
|
13
10
|
Rails.application.routes.draw do
|
|
14
|
-
self
|
|
11
|
+
mapper = self
|
|
12
|
+
mapper.singleton_class.include(Mocha::API)
|
|
13
|
+
mapper.singleton_class.include(Mocha::ParameterMatchers)
|
|
14
|
+
|
|
15
|
+
mapper.expects(:devise_for).with(
|
|
15
16
|
:users,
|
|
16
17
|
has_entries(
|
|
17
18
|
controllers: has_entries(
|
data/test/models/user_test.rb
CHANGED
|
@@ -127,4 +127,125 @@ class UserTest < ActiveSupport::TestCase
|
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
|
+
|
|
131
|
+
describe 'clean_old_tokens' do
|
|
132
|
+
before do
|
|
133
|
+
@resource = create(:user, :confirmed)
|
|
134
|
+
@token_lifespan = DeviseTokenAuth.token_lifespan
|
|
135
|
+
@max_client_count = DeviseTokenAuth.max_number_of_devices
|
|
136
|
+
DeviseTokenAuth.max_number_of_devices = 2
|
|
137
|
+
DeviseTokenAuth.token_lifespan = 1.week
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
after do
|
|
141
|
+
DeviseTokenAuth.token_lifespan = @token_lifespan
|
|
142
|
+
DeviseTokenAuth.max_number_of_devices = @max_client_count
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
test 'removes tokens with expiry beyond the maximum lifespan' do
|
|
146
|
+
# Create tokens with different expiry times
|
|
147
|
+
current_time = Time.now.to_i
|
|
148
|
+
|
|
149
|
+
max_lifespan = current_time + DeviseTokenAuth.token_lifespan.to_i
|
|
150
|
+
|
|
151
|
+
# Valid token within lifespan
|
|
152
|
+
@resource.tokens['valid_client'] = {
|
|
153
|
+
'token' => 'valid_token',
|
|
154
|
+
'expiry' => current_time + 1.day.to_i
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Token exactly at max lifespan (should be kept)
|
|
158
|
+
@resource.tokens['edge_client'] = {
|
|
159
|
+
'token' => 'edge_token',
|
|
160
|
+
'expiry' => max_lifespan
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Token beyond max lifespan (should be removed)
|
|
164
|
+
@resource.tokens['expired_client'] = {
|
|
165
|
+
'token' => 'expired_token',
|
|
166
|
+
'expiry' => max_lifespan + 1.day.to_i
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Call the method under test
|
|
170
|
+
@resource.send(:clean_old_tokens)
|
|
171
|
+
|
|
172
|
+
# Assert that tokens beyond lifespan were removed
|
|
173
|
+
assert @resource.tokens.key?('valid_client'), 'Valid token should be kept'
|
|
174
|
+
assert @resource.tokens.key?('edge_client'), 'Edge case token at max lifespan should be kept'
|
|
175
|
+
refute @resource.tokens.key?('expired_client'), 'Token beyond max lifespan should be removed'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
test 'handles token lifespan reduction when creating token' do
|
|
179
|
+
# Setup: Create the maximum allowed number of tokens with a longer lifespan
|
|
180
|
+
DeviseTokenAuth.token_lifespan = 2.weeks
|
|
181
|
+
DeviseTokenAuth.max_number_of_devices = 3
|
|
182
|
+
|
|
183
|
+
# Create tokens at different times but all within the initial long lifespan
|
|
184
|
+
@resource.tokens = {}
|
|
185
|
+
@resource.tokens['client_1'] = {
|
|
186
|
+
'token' => 'token_1',
|
|
187
|
+
'expiry' => Time.now.to_i + 12.days.to_i
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@resource.tokens['client_2'] = {
|
|
191
|
+
'token' => 'token_2',
|
|
192
|
+
'expiry' => Time.now.to_i + 10.days.to_i
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@resource.tokens['client_3'] = {
|
|
196
|
+
'token' => 'token_3',
|
|
197
|
+
'expiry' => Time.now.to_i + 5.days.to_i
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# We've reached the maximum number of devices/tokens
|
|
201
|
+
assert_equal 3, @resource.tokens.length
|
|
202
|
+
|
|
203
|
+
# Now reduce token lifespan - simulating a config change
|
|
204
|
+
DeviseTokenAuth.token_lifespan = 1.week
|
|
205
|
+
|
|
206
|
+
# Create a new token which should trigger clean_old_tokens
|
|
207
|
+
new_auth_headers = @resource.create_new_auth_token
|
|
208
|
+
new_client = new_auth_headers['client']
|
|
209
|
+
|
|
210
|
+
# The new token should exist
|
|
211
|
+
assert @resource.tokens.key?(new_client), 'New token should exist'
|
|
212
|
+
|
|
213
|
+
# Tokens exceeding the new reduced lifespan should be removed
|
|
214
|
+
refute @resource.tokens.key?('client_1'), 'Token with expiry > new lifespan should be removed'
|
|
215
|
+
refute @resource.tokens.key?('client_2'), 'Token with expiry > new lifespan should be removed'
|
|
216
|
+
|
|
217
|
+
# Token within new lifespan should be kept
|
|
218
|
+
assert @resource.tokens.key?('client_3'), 'Token within new reduced lifespan should be kept'
|
|
219
|
+
|
|
220
|
+
# We should have exactly 2 tokens: the new one and client_3
|
|
221
|
+
assert_equal 2, @resource.tokens.length
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Regression: with a calendar ActiveSupport::Duration lifespan, the token
|
|
225
|
+
# expiry is advanced by calendar (Time.zone.now + 2.months) while the old
|
|
226
|
+
# eager purge compared against an average-seconds threshold
|
|
227
|
+
# (token_lifespan.to_i). The freshly minted token's expiry sat just past
|
|
228
|
+
# that threshold and was silently dropped, leaving a 200 response whose
|
|
229
|
+
# advertised access-token no longer existed in user.tokens.
|
|
230
|
+
test 'keeps the freshly created token at the device cap with a calendar lifespan' do
|
|
231
|
+
DeviseTokenAuth.max_number_of_devices = 2
|
|
232
|
+
DeviseTokenAuth.token_lifespan = 2.months
|
|
233
|
+
|
|
234
|
+
# Fill the cap with tokens created a few hours ago (calendar expiry).
|
|
235
|
+
old_time = 4.hours.ago
|
|
236
|
+
@resource.tokens = {}
|
|
237
|
+
%w[client_1 client_2].each do |client|
|
|
238
|
+
@resource.tokens[client] = {
|
|
239
|
+
'token' => 'x',
|
|
240
|
+
'expiry' => (old_time + DeviseTokenAuth.token_lifespan).to_i
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
tok = @resource.create_token
|
|
245
|
+
|
|
246
|
+
assert @resource.tokens.key?(tok.client),
|
|
247
|
+
'Freshly created token must survive clean_old_tokens'
|
|
248
|
+
assert_equal DeviseTokenAuth.max_number_of_devices, @resource.tokens.length
|
|
249
|
+
end
|
|
250
|
+
end
|
|
130
251
|
end
|
data/test/test_helper.rb
CHANGED
|
@@ -38,7 +38,14 @@ end
|
|
|
38
38
|
class ActiveSupport::TestCase
|
|
39
39
|
include FactoryBot::Syntax::Methods
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
42
|
+
# `check_pending!` was deprecated in Rails 7.1 and removed in Rails 8.0
|
|
43
|
+
if ActiveRecord::Migration.respond_to?(:check_all_pending!)
|
|
44
|
+
ActiveRecord::Migration.check_all_pending!
|
|
45
|
+
else
|
|
46
|
+
ActiveRecord::Migration.check_pending!
|
|
47
|
+
end
|
|
48
|
+
end
|
|
42
49
|
|
|
43
50
|
strategies = { active_record: :transaction,
|
|
44
51
|
mongoid: :deletion }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise_token_auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lynn Hurley
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -19,7 +19,7 @@ dependencies:
|
|
|
19
19
|
version: 4.2.0
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '8.
|
|
22
|
+
version: '8.3'
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,7 +29,7 @@ dependencies:
|
|
|
29
29
|
version: 4.2.0
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '8.
|
|
32
|
+
version: '8.3'
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
34
|
name: devise
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
version: 3.5.2
|
|
40
40
|
- - "<"
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: '
|
|
42
|
+
version: '6'
|
|
43
43
|
type: :runtime
|
|
44
44
|
prerelease: false
|
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -49,7 +49,7 @@ dependencies:
|
|
|
49
49
|
version: 3.5.2
|
|
50
50
|
- - "<"
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '
|
|
52
|
+
version: '6'
|
|
53
53
|
- !ruby/object:Gem::Dependency
|
|
54
54
|
name: bcrypt
|
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -82,16 +82,22 @@ dependencies:
|
|
|
82
82
|
name: sqlite3
|
|
83
83
|
requirement: !ruby/object:Gem::Requirement
|
|
84
84
|
requirements:
|
|
85
|
-
- - "
|
|
85
|
+
- - ">="
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
87
|
version: '1.4'
|
|
88
|
+
- - "<"
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '3.0'
|
|
88
91
|
type: :development
|
|
89
92
|
prerelease: false
|
|
90
93
|
version_requirements: !ruby/object:Gem::Requirement
|
|
91
94
|
requirements:
|
|
92
|
-
- - "
|
|
95
|
+
- - ">="
|
|
93
96
|
- !ruby/object:Gem::Version
|
|
94
97
|
version: '1.4'
|
|
98
|
+
- - "<"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '3.0'
|
|
95
101
|
- !ruby/object:Gem::Dependency
|
|
96
102
|
name: pg
|
|
97
103
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -129,7 +135,7 @@ dependencies:
|
|
|
129
135
|
version: '4'
|
|
130
136
|
- - "<"
|
|
131
137
|
- !ruby/object:Gem::Version
|
|
132
|
-
version: '
|
|
138
|
+
version: '10'
|
|
133
139
|
type: :development
|
|
134
140
|
prerelease: false
|
|
135
141
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -139,21 +145,27 @@ dependencies:
|
|
|
139
145
|
version: '4'
|
|
140
146
|
- - "<"
|
|
141
147
|
- !ruby/object:Gem::Version
|
|
142
|
-
version: '
|
|
148
|
+
version: '10'
|
|
143
149
|
- !ruby/object:Gem::Dependency
|
|
144
150
|
name: mongoid-locker
|
|
145
151
|
requirement: !ruby/object:Gem::Requirement
|
|
146
152
|
requirements:
|
|
147
|
-
- - "
|
|
153
|
+
- - ">="
|
|
148
154
|
- !ruby/object:Gem::Version
|
|
149
|
-
version: '
|
|
155
|
+
version: '1.0'
|
|
156
|
+
- - "<"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '3.0'
|
|
150
159
|
type: :development
|
|
151
160
|
prerelease: false
|
|
152
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
153
162
|
requirements:
|
|
154
|
-
- - "
|
|
163
|
+
- - ">="
|
|
155
164
|
- !ruby/object:Gem::Version
|
|
156
|
-
version: '
|
|
165
|
+
version: '1.0'
|
|
166
|
+
- - "<"
|
|
167
|
+
- !ruby/object:Gem::Version
|
|
168
|
+
version: '3.0'
|
|
157
169
|
description: For use with client side single page apps such as the venerable https://github.com/lynndylanhurley/ng-token-auth.
|
|
158
170
|
email:
|
|
159
171
|
- lynn.dylan.hurley@gmail.com
|
|
@@ -358,7 +370,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
358
370
|
- !ruby/object:Gem::Version
|
|
359
371
|
version: '0'
|
|
360
372
|
requirements: []
|
|
361
|
-
rubygems_version: 3.
|
|
373
|
+
rubygems_version: 3.4.19
|
|
362
374
|
signing_key:
|
|
363
375
|
specification_version: 4
|
|
364
376
|
summary: Token based authentication for rails. Uses Devise + OmniAuth.
|