Skip to content

Commit 470c84b

Browse files
authored
Optimize resource valid check after set the headers (#1188)
1 parent 8bad4e2 commit 470c84b

File tree

4 files changed

+65
-41
lines changed

4 files changed

+65
-41
lines changed

app/controllers/devise_token_auth/concerns/set_user_by_token.rb

+50-40
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ def set_user_by_token(mapping = nil)
9999

100100
def update_auth_header
101101
# cannot save object if model has invalid params
102-
return unless defined?(@resource) && @resource && @resource.valid? && @client_id
102+
103+
return unless @resource && @client_id
103104

104105
# Generate new client_id with existing authentication
105106
@client_id = nil unless @used_auth_by_token
@@ -115,54 +116,63 @@ def update_auth_header
115116
response.headers.merge!(auth_header)
116117

117118
else
118-
119-
ensure_pristine_resource do
120-
# Lock the user record during any auth_header updates to ensure
121-
# we don't have write contention from multiple threads
122-
@resource.with_lock do
123-
# should not append auth header if @resource related token was
124-
# cleared by sign out in the meantime
125-
return if @used_auth_by_token && @resource.tokens[@client_id].nil?
126-
127-
# determine batch request status after request processing, in case
128-
# another processes has updated it during that processing
129-
@is_batch_request = is_batch_request?(@resource, @client_id)
130-
131-
auth_header = {}
132-
133-
# extend expiration of batch buffer to account for the duration of
134-
# this request
135-
if @is_batch_request
136-
auth_header = @resource.extend_batch_buffer(@token, @client_id)
137-
138-
# Do not return token for batch requests to avoid invalidated
139-
# tokens returned to the client in case of race conditions.
140-
# Use a blank string for the header to still be present and
141-
# being passed in a XHR response in case of
142-
# 304 Not Modified responses.
143-
auth_header[DeviseTokenAuth.headers_names[:"access-token"]] = ' '
144-
auth_header[DeviseTokenAuth.headers_names[:"expiry"]] = ' '
145-
146-
# update Authorization response header with new token
147-
else
148-
auth_header = @resource.create_new_auth_token(@client_id)
149-
end
150-
151-
# update the response header
152-
response.headers.merge!(auth_header)
153-
154-
end # end lock
155-
end # end ensure_pristine_resource
119+
unless @resource.reload.valid?
120+
@resource = resource_class.find(@resource.to_param) # errors remain after reload
121+
# if we left the model in a bad state, something is wrong in our app
122+
unless @resource.valid?
123+
raise DeviseTokenAuth::Errors::InvalidModel, "Cannot set auth token in invalid model. Errors: #{@resource.errors.full_messages}"
124+
end
125+
end
126+
refresh_headers
156127
end
157-
158128
end
159129

160130
private
161131

132+
def refresh_headers
133+
ensure_pristine_resource do
134+
# Lock the user record during any auth_header updates to ensure
135+
# we don't have write contention from multiple threads
136+
@resource.with_lock do
137+
# should not append auth header if @resource related token was
138+
# cleared by sign out in the meantime
139+
return if @used_auth_by_token && @resource.tokens[@client_id].nil?
140+
141+
# update the response header
142+
response.headers.merge!(auth_header_from_batch_request)
143+
end # end lock
144+
end # end ensure_pristine_resource
145+
end
146+
162147
def is_batch_request?(user, client_id)
163148
!params[:unbatch] &&
164149
user.tokens[client_id] &&
165150
user.tokens[client_id]['updated_at'] &&
166151
Time.parse(user.tokens[client_id]['updated_at']) > @request_started_at - DeviseTokenAuth.batch_request_buffer_throttle
167152
end
153+
154+
def auth_header_from_batch_request
155+
# determine batch request status after request processing, in case
156+
# another processes has updated it during that processing
157+
@is_batch_request = is_batch_request?(@resource, @client_id)
158+
159+
auth_header = {}
160+
# extend expiration of batch buffer to account for the duration of
161+
# this request
162+
if @is_batch_request
163+
auth_header = @resource.extend_batch_buffer(@token, @client_id)
164+
165+
# Do not return token for batch requests to avoid invalidated
166+
# tokens returned to the client in case of race conditions.
167+
# Use a blank string for the header to still be present and
168+
# being passed in a XHR response in case of
169+
# 304 Not Modified responses.
170+
auth_header[DeviseTokenAuth.headers_names[:"access-token"]] = ' '
171+
auth_header[DeviseTokenAuth.headers_names[:"expiry"]] = ' '
172+
else
173+
# update Authorization response header with new token
174+
auth_header = @resource.create_new_auth_token(@client_id)
175+
end
176+
auth_header
177+
end
168178
end

app/controllers/devise_token_auth/sessions_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def create
2424
if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
2525
valid_password = @resource.valid_password?(resource_params[:password])
2626
if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
27-
return render_create_error_bad_credentials
27+
return render_create_error_bad_credentials
2828
end
2929
@client_id, @token = @resource.create_token
3030
@resource.save

lib/devise_token_auth/errors.rb

+1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
module DeviseTokenAuth
44
module Errors
55
class NoResourceDefinedError < StandardError; end
6+
class InvalidModel < StandardError; end
67
end
78
end

test/controllers/devise_token_auth/token_validations_controller_test.rb

+13
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ class DeviseTokenAuth::TokenValidationsControllerTest < ActionDispatch::Integrat
4545
end
4646
end
4747

48+
describe 'with invalid user' do
49+
before do
50+
@resource.update_column :email, 'invalid'
51+
end
52+
53+
test 'request should raise invalid model error' do
54+
error = assert_raises DeviseTokenAuth::Errors::InvalidModel do
55+
get '/auth/validate_token', params: {}, headers: @auth_headers
56+
end
57+
assert_equal(error.message, "Cannot set auth token in invalid model. Errors: [\"Email is not an email\"]")
58+
end
59+
end
60+
4861
describe 'failure' do
4962
before do
5063
get '/api/v1/auth/validate_token',

0 commit comments

Comments
 (0)