-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
set_user_by_token.rb
186 lines (154 loc) · 6.48 KB
/
set_user_by_token.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# frozen_string_literal: true
module DeviseTokenAuth::Concerns::SetUserByToken
extend ActiveSupport::Concern
include DeviseTokenAuth::Concerns::ResourceFinder
included do
before_action :set_request_start
after_action :update_auth_header
end
protected
# keep track of request duration
def set_request_start
@request_started_at = Time.zone.now
@used_auth_by_token = true
# initialize instance variables
@token ||= DeviseTokenAuth::TokenFactory.new
@resource ||= nil
@is_batch_request ||= nil
end
# user auth
def set_user_by_token(mapping = nil)
# determine target authentication class
rc = resource_class(mapping)
# no default user defined
return unless rc
# gets the headers names, which was set in the initialize file
uid_name = DeviseTokenAuth.headers_names[:'uid']
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
client_name = DeviseTokenAuth.headers_names[:'client']
# gets values from cookie if configured and present
parsed_auth_cookie = {}
if DeviseTokenAuth.cookie_enabled
auth_cookie = request.cookies[DeviseTokenAuth.cookie_name]
if auth_cookie.present?
parsed_auth_cookie = JSON.parse(auth_cookie)
end
end
# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name] || parsed_auth_cookie[uid_name]
@token = DeviseTokenAuth::TokenFactory.new unless @token
@token.token ||= request.headers[access_token_name] || params[access_token_name] || parsed_auth_cookie[access_token_name]
@token.client ||= request.headers[client_name] || params[client_name] || parsed_auth_cookie[client_name]
# client isn't required, set to 'default' if absent
@token.client ||= 'default'
# check for an existing user, authenticated via warden/devise, if enabled
if DeviseTokenAuth.enable_standard_devise_support
devise_warden_user = warden.user(mapping)
if devise_warden_user && devise_warden_user.tokens[@token.client].nil?
@used_auth_by_token = false
@resource = devise_warden_user
# REVIEW: The following line _should_ be safe to remove;
# the generated token does not get used anywhere.
# @resource.create_new_auth_token
end
end
# user has already been found and authenticated
return @resource if @resource && @resource.is_a?(rc)
# ensure we clear the client
unless @token.present?
@token.client = nil
return
end
# mitigate timing attacks by finding by uid instead of auth token
user = uid && rc.dta_find_by(uid: uid)
scope = rc.to_s.underscore.to_sym
if user && user.valid_token?(@token.token, @token.client)
# sign_in with bypass: true will be deprecated in the next version of Devise
if respond_to?(:bypass_sign_in) && DeviseTokenAuth.bypass_sign_in
bypass_sign_in(user, scope: scope)
else
sign_in(scope, user, store: false, event: :fetch, bypass: DeviseTokenAuth.bypass_sign_in)
end
return @resource = user
else
# zero all values previously set values
@token.client = nil
return @resource = nil
end
end
def update_auth_header
# cannot save object if model has invalid params
return unless @resource && @token.client
# Generate new client with existing authentication
@token.client = nil unless @used_auth_by_token
if @used_auth_by_token && !DeviseTokenAuth.change_headers_on_each_request
# should not append auth header if @resource related token was
# cleared by sign out in the meantime
return if @resource.reload.tokens[@token.client].nil?
auth_header = @resource.build_auth_header(@token.token, @token.client)
# update the response header
response.headers.merge!(auth_header)
# set a server cookie if configured
if DeviseTokenAuth.cookie_enabled
set_cookie(auth_header)
end
else
unless @resource.reload.valid?
@resource = @resource.class.find(@resource.to_param) # errors remain after reload
# if we left the model in a bad state, something is wrong in our app
unless @resource.valid?
raise DeviseTokenAuth::Errors::InvalidModel, "Cannot set auth token in invalid model. Errors: #{@resource.errors.full_messages}"
end
end
refresh_headers
end
end
private
def refresh_headers
# Lock the user record during any auth_header updates to ensure
# we don't have write contention from multiple threads
@resource.with_lock do
# should not append auth header if @resource related token was
# cleared by sign out in the meantime
return if @used_auth_by_token && @resource.tokens[@token.client].nil?
_auth_header_from_batch_request = auth_header_from_batch_request
# update the response header
response.headers.merge!(_auth_header_from_batch_request)
# set a server cookie if configured
if DeviseTokenAuth.cookie_enabled
set_cookie(_auth_header_from_batch_request)
end
end # end lock
end
def set_cookie(auth_header)
cookies[DeviseTokenAuth.cookie_name] = DeviseTokenAuth.cookie_attributes.merge(value: auth_header.to_json)
end
def is_batch_request?(user, client)
!params[:unbatch] &&
user.tokens[client] &&
user.tokens[client]['updated_at'] &&
user.tokens[client]['updated_at'].to_time > @request_started_at - DeviseTokenAuth.batch_request_buffer_throttle
end
def auth_header_from_batch_request
# determine batch request status after request processing, in case
# another processes has updated it during that processing
@is_batch_request = is_batch_request?(@resource, @token.client)
auth_header = {}
# extend expiration of batch buffer to account for the duration of
# this request
if @is_batch_request
auth_header = @resource.extend_batch_buffer(@token.token, @token.client)
# Do not return token for batch requests to avoid invalidated
# tokens returned to the client in case of race conditions.
# Use a blank string for the header to still be present and
# being passed in a XHR response in case of
# 304 Not Modified responses.
auth_header[DeviseTokenAuth.headers_names[:"access-token"]] = ' '
auth_header[DeviseTokenAuth.headers_names[:"expiry"]] = ' '
else
# update Authorization response header with new token
auth_header = @resource.create_new_auth_token(@token.client)
end
auth_header
end
end