devise_token_auth 1.2.5 → 1.2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e031d0a5148c716c1b4f2d9d2f6a71de42d46271b8d5b35c958440bb59e572c9
4
- data.tar.gz: e194048efc1fd92ec64c815d6b6863e1abfcb73f2f06b6da8f4ff7b69dce4da3
3
+ metadata.gz: 2950da8c6b3eac0a2a3ee2b1659d5e321ca9890e5a9b0a1525dcbf11e95d67dd
4
+ data.tar.gz: d04830c1c12f69b3537e80fae17d5914b0b22272a52b78d7226a04be560cadea
5
5
  SHA512:
6
- metadata.gz: 6015bc177925ae7a86cbf2e3f010172bfa6c8e30e1409d749e916896e05bf3fc8b55d046c533f4569faed058126924c8105f89ebebcbb3a4cf252472438031d6
7
- data.tar.gz: 70412779543cf2c98b1623b7873d484ec430c6f5e3c4329f806aafaeaf63e0d1ff95ddbf02af9cd34a194d4a5fed41bbdf848597eee7bf3ba772927970993e7b
6
+ metadata.gz: 0a7e1dcce01dabc1e270926ff05b54c166d861232ef6ecf7547d88442e2d11ab7c104fef833312bf73a69dd5e3dd7df4923f050baf3e4aee877f32df6392d352
7
+ data.tar.gz: b44b9aa57a43fb58e86a7985957e9a5d425787ae65e898b480d7fefe02bf573433f27af356512ae2a4989c3a6d9f0b91a9cc1a1b3b2ef3f95a4b39557472ffa7
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 `will_save_change_to_email?` & `email_changed?` methods.
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 email_changed?; false; end
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,25 @@ module DeviseTokenAuth::Concerns::User
259
258
  end
260
259
 
261
260
  def clean_old_tokens
262
- if tokens.present? && max_client_tokens_exceeded?
263
- # Using Enumerable#sort_by on a Hash will typecast it into an associative
264
- # Array (i.e. an Array of key-value Array pairs). However, since Hashes
265
- # have an internal order in Ruby 1.9+, the resulting sorted associative
266
- # Array can be converted back into a Hash, while maintaining the sorted
267
- # order.
268
- self.tokens = tokens.sort_by { |_cid, v| v[:expiry] || v['expiry'] }.to_h
269
-
270
- # Since the tokens are sorted by expiry, shift the oldest client token
271
- # off the Hash until it no longer exceeds the maximum number of clients
272
- tokens.shift while max_client_tokens_exceeded?
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
+ max_lifespan_expiry = Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i
266
+ tokens_to_keep = tokens.select do |_cid, v|
267
+ expiry = (v[:expiry] || v['expiry']).to_i
268
+ expiry <= max_lifespan_expiry
273
269
  end
270
+
271
+ # Using Enumerable#sort_by on a Hash will typecast it into an associative
272
+ # Array (i.e. an Array of key-value Array pairs). However, since Hashes
273
+ # have an internal order in Ruby 1.9+, the resulting sorted associative
274
+ # Array can be converted back into a Hash, while maintaining the sorted
275
+ # order.
276
+ self.tokens = tokens_to_keep.sort_by { |_cid, v| v[:expiry] || v['expiry'] }.to_h
277
+
278
+ # Since the tokens are sorted by expiry, shift the oldest client token
279
+ # off the Hash until it no longer exceeds the maximum number of clients
280
+ tokens.shift while max_client_tokens_exceeded?
274
281
  end
275
282
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeviseTokenAuth
4
- VERSION = '1.2.5'.freeze
4
+ VERSION = '1.2.6'.freeze
5
5
  end
@@ -229,42 +229,73 @@ class DeviseTokenAuth::PasswordsControllerTest < ActionController::TestCase
229
229
  end
230
230
 
231
231
  describe 'password reset link success' do
232
- before do
233
- get :edit,
234
- params: { reset_password_token: @mail_reset_token,
235
- redirect_url: @mail_redirect_url }
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
- @resource.reload
239
+ @resource.reload
238
240
 
239
- raw_qs = response.location.split('?')[1]
240
- @qs = Rack::Utils.parse_nested_query(raw_qs)
241
+ raw_qs = response.location.split('?')[1]
242
+ @qs = Rack::Utils.parse_nested_query(raw_qs)
241
243
 
242
- @access_token = @qs['access-token']
243
- @client_id = @qs['client_id']
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
- test 'response should have success redirect status' do
252
- assert_equal 302, response.status
253
- end
247
+ test 'response should have success redirect status' do
248
+ assert_equal 302, response.status
249
+ end
254
250
 
255
- test 'response should contain auth params' do
256
- assert @access_token
257
- assert @client
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
- test 'response auth params should be valid' do
266
- assert @resource.valid_token?(@token, @client_id)
267
- assert @resource.valid_token?(@access_token, @client)
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require File.expand_path('boot', __dir__)
4
4
 
5
+ require 'logger'
5
6
  require 'action_controller/railtie'
6
7
  require 'action_mailer/railtie'
7
8
  require 'rails/generators'
@@ -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 > 6 && ENV['DEVISE_TOKEN_AUTH_ORM'] != 'mongoid'
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,11 @@ Rails.application.configure do
32
32
  config.action_controller.perform_caching = false
33
33
 
34
34
  # Raise exceptions instead of rendering exception templates.
35
- config.action_dispatch.show_exceptions = false
35
+ if Rails::VERSION::MAJOR >= 7 && Rails::VERSION::MINOR > 0
36
+ config.action_dispatch.show_exceptions = :none
37
+ else
38
+ config.action_dispatch.show_exceptions = false
39
+ end
36
40
 
37
41
  # Disable request forgery protection in test environment.
38
42
  config.action_controller.allow_forgery_protection = false
@@ -127,4 +127,98 @@ 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
+ end
130
224
  end
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.2.5
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lynn Hurley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-06 00:00:00.000000000 Z
11
+ date: 2025-11-21 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.1'
22
+ version: '8.2'
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.1'
32
+ version: '8.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: devise
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -144,16 +144,22 @@ dependencies:
144
144
  name: mongoid-locker
145
145
  requirement: !ruby/object:Gem::Requirement
146
146
  requirements:
147
- - - "~>"
147
+ - - ">="
148
148
  - !ruby/object:Gem::Version
149
- version: '2.0'
149
+ version: '1.0'
150
+ - - "<"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
150
153
  type: :development
151
154
  prerelease: false
152
155
  version_requirements: !ruby/object:Gem::Requirement
153
156
  requirements:
154
- - - "~>"
157
+ - - ">="
155
158
  - !ruby/object:Gem::Version
156
- version: '2.0'
159
+ version: '1.0'
160
+ - - "<"
161
+ - !ruby/object:Gem::Version
162
+ version: '3.0'
157
163
  description: For use with client side single page apps such as the venerable https://github.com/lynndylanhurley/ng-token-auth.
158
164
  email:
159
165
  - lynn.dylan.hurley@gmail.com
@@ -358,7 +364,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
358
364
  - !ruby/object:Gem::Version
359
365
  version: '0'
360
366
  requirements: []
361
- rubygems_version: 3.3.7
367
+ rubygems_version: 3.4.19
362
368
  signing_key:
363
369
  specification_version: 4
364
370
  summary: Token based authentication for rails. Uses Devise + OmniAuth.