devise_token_auth 1.2.0 → 1.2.2
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 +2 -1
- data/app/controllers/devise_token_auth/application_controller.rb +13 -0
- data/app/controllers/devise_token_auth/concerns/resource_finder.rb +2 -1
- data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +20 -7
- data/app/controllers/devise_token_auth/confirmations_controller.rb +8 -5
- data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +18 -21
- data/app/controllers/devise_token_auth/passwords_controller.rb +9 -3
- data/app/controllers/devise_token_auth/sessions_controller.rb +26 -10
- data/app/controllers/devise_token_auth/unlocks_controller.rb +3 -2
- data/app/models/devise_token_auth/concerns/user.rb +34 -11
- data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +9 -5
- data/app/validators/devise_token_auth_email_validator.rb +9 -1
- data/config/locales/ja.yml +12 -0
- data/lib/devise_token_auth/engine.rb +5 -2
- data/lib/devise_token_auth/rails/routes.rb +6 -5
- data/lib/devise_token_auth/version.rb +1 -1
- data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +11 -5
- data/test/controllers/custom/custom_confirmations_controller_test.rb +1 -1
- data/test/controllers/custom/custom_omniauth_callbacks_controller_test.rb +1 -1
- data/test/controllers/demo_mang_controller_test.rb +37 -8
- data/test/controllers/demo_user_controller_test.rb +37 -8
- data/test/controllers/devise_token_auth/confirmations_controller_test.rb +19 -7
- data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +16 -19
- data/test/controllers/devise_token_auth/passwords_controller_test.rb +6 -6
- data/test/controllers/devise_token_auth/registrations_controller_test.rb +2 -2
- data/test/controllers/devise_token_auth/sessions_controller_test.rb +111 -38
- data/test/controllers/devise_token_auth/token_validations_controller_test.rb +41 -1
- data/test/controllers/devise_token_auth/unlocks_controller_test.rb +28 -6
- data/test/controllers/overrides/omniauth_callbacks_controller_test.rb +1 -1
- data/test/dummy/app/controllers/application_controller.rb +2 -6
- data/test/dummy/app/controllers/overrides/confirmations_controller.rb +2 -1
- data/test/dummy/app/controllers/overrides/passwords_controller.rb +2 -1
- data/test/dummy/config/environments/test.rb +6 -2
- data/test/dummy/db/schema.rb +5 -5
- data/test/dummy/tmp/generators/app/models/user.rb +9 -0
- data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +11 -5
- data/test/dummy/tmp/generators/db/migrate/20230415183419_devise_token_auth_create_users.rb +49 -0
- data/test/models/user_test.rb +22 -0
- metadata +94 -94
- data/test/dummy/tmp/generators/app/controllers/application_controller.rb +0 -6
- data/test/dummy/tmp/generators/app/models/azpire/v1/human_resource/user.rb +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 135b5088d17b20d187a6ffe314356dd2a2474d9bd98898bc9e36bc931e6c9fef
|
|
4
|
+
data.tar.gz: cf3c3c6fc19564248bdcb22e3b99624bd25a027383649a45c7e7bf1fddfd5bbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f6e88376261bcea31e98d8af66cc298755d2cbc2724c481a100d78273824b1dcfcc016bbc2af2b43d1dcac945fc6a4015d351f321da9a432ec4c5129f8c58f0
|
|
7
|
+
data.tar.gz: bde72417d1882c6f69076d3bfe8cebd077e39a681898fb0c5603643770a3d81eb0ac3d42dd46f8ae27aff4dae74a138159160974c1996ef4e28d252baf6b52ba
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Devise Token Auth
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/devise_token_auth)
|
|
4
|
-
[](https://github.com/lynndylanhurley/devise_token_auth/actions/workflows/test.yml)
|
|
5
5
|
[](https://codeclimate.com/github/lynndylanhurley/devise_token_auth)
|
|
6
6
|
[](https://codeclimate.com/github/lynndylanhurley/devise_token_auth/coverage)
|
|
7
7
|
[](https://rubygems.org/gems/devise_token_auth)
|
|
@@ -22,6 +22,7 @@ Also, it maintains a session for each client/device, so you can have as many ses
|
|
|
22
22
|
* [Angular-Token](https://github.com/neroniaky/angular-token) for [Angular](https://github.com/angular/angular)
|
|
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
|
+
* [vanilla-token-auth](https://github.com/theblang/vanilla-token-auth) for an unopinionated client
|
|
25
26
|
* Oauth2 authentication using [OmniAuth](https://github.com/intridea/omniauth).
|
|
26
27
|
* Email authentication using [Devise](https://github.com/plataformatec/devise), including:
|
|
27
28
|
* User registration, update and deletion
|
|
@@ -83,5 +83,18 @@ module DeviseTokenAuth
|
|
|
83
83
|
I18n.t("devise_token_auth.#{name}.sended", email: email)
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
def redirect_options
|
|
88
|
+
{}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# When using a cookie to transport the auth token we can set it immediately in flows such as
|
|
92
|
+
# reset password and OmniAuth success, rather than making the client scrape the token from
|
|
93
|
+
# query params (to then send in the initial validate_token request).
|
|
94
|
+
# TODO: We should be able to stop exposing the token in query params when this method is used
|
|
95
|
+
def set_token_in_cookie(resource, token)
|
|
96
|
+
auth_header = resource.build_auth_headers(token.token, token.client)
|
|
97
|
+
cookies[DeviseTokenAuth.cookie_name] = DeviseTokenAuth.cookie_attributes.merge(value: auth_header.to_json)
|
|
98
|
+
end
|
|
86
99
|
end
|
|
87
100
|
end
|
|
@@ -22,7 +22,8 @@ module DeviseTokenAuth::Concerns::ResourceFinder
|
|
|
22
22
|
def find_resource(field, value)
|
|
23
23
|
@resource = if database_adapter&.include?('mysql')
|
|
24
24
|
# fix for mysql default case insensitivity
|
|
25
|
-
resource_class.
|
|
25
|
+
field_sanitized = resource_class.connection.quote_column_name(field)
|
|
26
|
+
resource_class.where("BINARY #{field_sanitized} = ? AND provider= ?", value, provider).first
|
|
26
27
|
else
|
|
27
28
|
resource_class.dta_find_by(field => value, 'provider' => provider)
|
|
28
29
|
end
|
|
@@ -32,8 +32,13 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
32
32
|
|
|
33
33
|
# gets the headers names, which was set in the initialize file
|
|
34
34
|
uid_name = DeviseTokenAuth.headers_names[:'uid']
|
|
35
|
+
other_uid_name = DeviseTokenAuth.other_uid && DeviseTokenAuth.headers_names[DeviseTokenAuth.other_uid.to_sym]
|
|
35
36
|
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
|
|
36
37
|
client_name = DeviseTokenAuth.headers_names[:'client']
|
|
38
|
+
authorization_name = DeviseTokenAuth.headers_names[:"authorization"]
|
|
39
|
+
|
|
40
|
+
# Read Authorization token and decode it if present
|
|
41
|
+
decoded_authorization_token = decode_bearer_token(request.headers[authorization_name])
|
|
37
42
|
|
|
38
43
|
# gets values from cookie if configured and present
|
|
39
44
|
parsed_auth_cookie = {}
|
|
@@ -45,10 +50,11 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
# parse header for values necessary for authentication
|
|
48
|
-
uid = request.headers[uid_name] || params[uid_name] || parsed_auth_cookie[uid_name]
|
|
53
|
+
uid = request.headers[uid_name] || params[uid_name] || parsed_auth_cookie[uid_name] || decoded_authorization_token[uid_name]
|
|
54
|
+
other_uid = other_uid_name && request.headers[other_uid_name] || params[other_uid_name] || parsed_auth_cookie[other_uid_name]
|
|
49
55
|
@token = DeviseTokenAuth::TokenFactory.new unless @token
|
|
50
|
-
@token.token ||= request.headers[access_token_name] || params[access_token_name] || parsed_auth_cookie[access_token_name]
|
|
51
|
-
@token.client ||= request.headers[client_name] || params[client_name] || parsed_auth_cookie[client_name]
|
|
56
|
+
@token.token ||= request.headers[access_token_name] || params[access_token_name] || parsed_auth_cookie[access_token_name] || decoded_authorization_token[access_token_name]
|
|
57
|
+
@token.client ||= request.headers[client_name] || params[client_name] || parsed_auth_cookie[client_name] || decoded_authorization_token[client_name]
|
|
52
58
|
|
|
53
59
|
# client isn't required, set to 'default' if absent
|
|
54
60
|
@token.client ||= 'default'
|
|
@@ -75,7 +81,7 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
# mitigate timing attacks by finding by uid instead of auth token
|
|
78
|
-
user = uid && rc.dta_find_by(uid: uid)
|
|
84
|
+
user = (uid && rc.dta_find_by(uid: uid)) || (other_uid && rc.dta_find_by("#{DeviseTokenAuth.other_uid}": other_uid))
|
|
79
85
|
scope = rc.to_s.underscore.to_sym
|
|
80
86
|
|
|
81
87
|
if user && user.valid_token?(@token.token, @token.client)
|
|
@@ -105,7 +111,7 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
105
111
|
# cleared by sign out in the meantime
|
|
106
112
|
return if @resource.reload.tokens[@token.client].nil?
|
|
107
113
|
|
|
108
|
-
auth_header = @resource.
|
|
114
|
+
auth_header = @resource.build_auth_headers(@token.token, @token.client)
|
|
109
115
|
|
|
110
116
|
# update the response header
|
|
111
117
|
response.headers.merge!(auth_header)
|
|
@@ -128,6 +134,13 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
128
134
|
|
|
129
135
|
private
|
|
130
136
|
|
|
137
|
+
def decode_bearer_token(bearer_token)
|
|
138
|
+
return {} if bearer_token.blank?
|
|
139
|
+
|
|
140
|
+
encoded_token = bearer_token.split.last # Removes the 'Bearer' from the string
|
|
141
|
+
JSON.parse(Base64.strict_decode64(encoded_token)) rescue {}
|
|
142
|
+
end
|
|
143
|
+
|
|
131
144
|
def refresh_headers
|
|
132
145
|
# Lock the user record during any auth_header updates to ensure
|
|
133
146
|
# we don't have write contention from multiple threads
|
|
@@ -141,8 +154,8 @@ module DeviseTokenAuth::Concerns::SetUserByToken
|
|
|
141
154
|
# update the response header
|
|
142
155
|
response.headers.merge!(_auth_header_from_batch_request)
|
|
143
156
|
|
|
144
|
-
# set a server cookie if configured
|
|
145
|
-
if DeviseTokenAuth.cookie_enabled
|
|
157
|
+
# set a server cookie if configured and is not a batch request
|
|
158
|
+
if DeviseTokenAuth.cookie_enabled && !@is_batch_request
|
|
146
159
|
set_cookie(_auth_header_from_batch_request)
|
|
147
160
|
end
|
|
148
161
|
end # end lock
|
|
@@ -22,11 +22,15 @@ module DeviseTokenAuth
|
|
|
22
22
|
redirect_to_link = signed_in_resource.build_auth_url(redirect_url, redirect_headers)
|
|
23
23
|
else
|
|
24
24
|
redirect_to_link = DeviseTokenAuth::Url.generate(redirect_url, redirect_header_options)
|
|
25
|
-
|
|
25
|
+
end
|
|
26
26
|
|
|
27
|
-
redirect_to(redirect_to_link)
|
|
27
|
+
redirect_to(redirect_to_link, redirect_options)
|
|
28
28
|
else
|
|
29
|
-
|
|
29
|
+
if redirect_url
|
|
30
|
+
redirect_to DeviseTokenAuth::Url.generate(redirect_url, account_confirmation_success: false)
|
|
31
|
+
else
|
|
32
|
+
raise ActionController::RoutingError, 'Not Found'
|
|
33
|
+
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
32
36
|
|
|
@@ -62,7 +66,7 @@ module DeviseTokenAuth
|
|
|
62
66
|
|
|
63
67
|
def render_not_found_error
|
|
64
68
|
if Devise.paranoid
|
|
65
|
-
|
|
69
|
+
render_create_success
|
|
66
70
|
else
|
|
67
71
|
render_error(404, I18n.t('devise_token_auth.confirmations.user_not_found', email: @email))
|
|
68
72
|
end
|
|
@@ -81,6 +85,5 @@ module DeviseTokenAuth
|
|
|
81
85
|
DeviseTokenAuth.default_confirm_success_url
|
|
82
86
|
)
|
|
83
87
|
end
|
|
84
|
-
|
|
85
88
|
end
|
|
86
89
|
end
|
|
@@ -23,7 +23,7 @@ module DeviseTokenAuth
|
|
|
23
23
|
session['dta.omniauth.auth'] = request.env['omniauth.auth'].except('extra')
|
|
24
24
|
session['dta.omniauth.params'] = request.env['omniauth.params']
|
|
25
25
|
|
|
26
|
-
redirect_to redirect_route
|
|
26
|
+
redirect_to redirect_route, {status: 307}.merge(redirect_options)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def get_redirect_route(devise_mapping)
|
|
@@ -45,7 +45,7 @@ module DeviseTokenAuth
|
|
|
45
45
|
# find the mapping in `omniauth.params`.
|
|
46
46
|
#
|
|
47
47
|
# One example use-case here is for IDP-initiated SAML login. In that
|
|
48
|
-
# case, there will have been no initial request in which to save
|
|
48
|
+
# case, there will have been no initial request in which to save
|
|
49
49
|
# the devise mapping. If you are in a situation like that, and
|
|
50
50
|
# your app allows for you to determine somehow what the devise
|
|
51
51
|
# mapping should be (because, for example, it is always the same),
|
|
@@ -70,6 +70,10 @@ module DeviseTokenAuth
|
|
|
70
70
|
|
|
71
71
|
yield @resource if block_given?
|
|
72
72
|
|
|
73
|
+
if DeviseTokenAuth.cookie_enabled
|
|
74
|
+
set_token_in_cookie(@resource, @token)
|
|
75
|
+
end
|
|
76
|
+
|
|
73
77
|
render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json)
|
|
74
78
|
end
|
|
75
79
|
|
|
@@ -78,10 +82,10 @@ module DeviseTokenAuth
|
|
|
78
82
|
render_data_or_redirect('authFailure', error: @error)
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
def validate_auth_origin_url_param
|
|
85
|
+
def validate_auth_origin_url_param
|
|
82
86
|
return render_error_not_allowed_auth_origin_url if auth_origin_url && blacklisted_redirect_url?(auth_origin_url)
|
|
83
87
|
end
|
|
84
|
-
|
|
88
|
+
|
|
85
89
|
|
|
86
90
|
protected
|
|
87
91
|
|
|
@@ -107,7 +111,6 @@ module DeviseTokenAuth
|
|
|
107
111
|
end
|
|
108
112
|
end
|
|
109
113
|
@_omniauth_params
|
|
110
|
-
|
|
111
114
|
end
|
|
112
115
|
|
|
113
116
|
# break out provider attribute assignment for easy method extension
|
|
@@ -129,23 +132,19 @@ module DeviseTokenAuth
|
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
def resource_class(mapping = nil)
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
return @resource_class if defined?(@resource_class)
|
|
136
|
+
|
|
137
|
+
constant_name = omniauth_params['resource_class'].presence || params['resource_class'].presence
|
|
138
|
+
@resource_class = ObjectSpace.each_object(Class).detect { |cls| cls.to_s == constant_name && cls.pretty_print_inspect.starts_with?(constant_name) }
|
|
139
|
+
raise 'No resource_class found' if @resource_class.nil?
|
|
140
|
+
|
|
141
|
+
@resource_class
|
|
139
142
|
end
|
|
140
143
|
|
|
141
144
|
def resource_name
|
|
142
145
|
resource_class
|
|
143
146
|
end
|
|
144
147
|
|
|
145
|
-
def omniauth_window_type
|
|
146
|
-
omniauth_params['omniauth_window_type']
|
|
147
|
-
end
|
|
148
|
-
|
|
149
148
|
def unsafe_auth_origin_url
|
|
150
149
|
omniauth_params['auth_origin_url'] || omniauth_params['origin']
|
|
151
150
|
end
|
|
@@ -164,12 +163,11 @@ module DeviseTokenAuth
|
|
|
164
163
|
omniauth_params.nil? ? params['omniauth_window_type'] : omniauth_params['omniauth_window_type']
|
|
165
164
|
end
|
|
166
165
|
|
|
167
|
-
# this
|
|
166
|
+
# this session value is set by the redirect_callbacks method. its purpose
|
|
168
167
|
# is to persist the omniauth auth hash value thru a redirect. the value
|
|
169
|
-
# must be destroyed
|
|
168
|
+
# must be destroyed immediately after it is accessed by omniauth_success
|
|
170
169
|
def auth_hash
|
|
171
170
|
@_auth_hash ||= session.delete('dta.omniauth.auth')
|
|
172
|
-
@_auth_hash
|
|
173
171
|
end
|
|
174
172
|
|
|
175
173
|
# ensure that this controller responds to :devise_controller? conditionals.
|
|
@@ -229,7 +227,7 @@ module DeviseTokenAuth
|
|
|
229
227
|
elsif auth_origin_url # default to same-window implementation, which forwards back to auth_origin_url
|
|
230
228
|
|
|
231
229
|
# build and redirect to destination url
|
|
232
|
-
redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true))
|
|
230
|
+
redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true).merge(redirect_options))
|
|
233
231
|
else
|
|
234
232
|
|
|
235
233
|
# there SHOULD always be an auth_origin_url, but if someone does something silly
|
|
@@ -283,5 +281,4 @@ module DeviseTokenAuth
|
|
|
283
281
|
@resource
|
|
284
282
|
end
|
|
285
283
|
end
|
|
286
|
-
|
|
287
284
|
end
|
|
@@ -49,14 +49,20 @@ module DeviseTokenAuth
|
|
|
49
49
|
yield @resource if block_given?
|
|
50
50
|
|
|
51
51
|
if require_client_password_reset_token?
|
|
52
|
-
redirect_to DeviseTokenAuth::Url.generate(@redirect_url, reset_password_token: resource_params[:reset_password_token])
|
|
52
|
+
redirect_to DeviseTokenAuth::Url.generate(@redirect_url, reset_password_token: resource_params[:reset_password_token]),
|
|
53
|
+
redirect_options
|
|
53
54
|
else
|
|
55
|
+
if DeviseTokenAuth.cookie_enabled
|
|
56
|
+
set_token_in_cookie(@resource, token)
|
|
57
|
+
end
|
|
58
|
+
|
|
54
59
|
redirect_header_options = { reset_password: true }
|
|
55
60
|
redirect_headers = build_redirect_headers(token.token,
|
|
56
61
|
token.client,
|
|
57
62
|
redirect_header_options)
|
|
58
63
|
redirect_to(@resource.build_auth_url(@redirect_url,
|
|
59
|
-
redirect_headers)
|
|
64
|
+
redirect_headers),
|
|
65
|
+
redirect_options)
|
|
60
66
|
end
|
|
61
67
|
else
|
|
62
68
|
render_edit_error
|
|
@@ -182,7 +188,7 @@ module DeviseTokenAuth
|
|
|
182
188
|
|
|
183
189
|
def render_not_found_error
|
|
184
190
|
if Devise.paranoid
|
|
185
|
-
|
|
191
|
+
render_create_success
|
|
186
192
|
else
|
|
187
193
|
render_error(404, I18n.t('devise_token_auth.passwords.user_not_found', email: @email))
|
|
188
194
|
end
|
|
@@ -11,11 +11,7 @@ module DeviseTokenAuth
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def create
|
|
14
|
-
|
|
15
|
-
field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
|
|
16
|
-
|
|
17
|
-
@resource = nil
|
|
18
|
-
if field
|
|
14
|
+
if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
|
|
19
15
|
q_value = get_case_insensitive_field_from_resource_params(field)
|
|
20
16
|
|
|
21
17
|
@resource = find_resource(field, q_value)
|
|
@@ -26,21 +22,22 @@ module DeviseTokenAuth
|
|
|
26
22
|
if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
|
|
27
23
|
return render_create_error_bad_credentials
|
|
28
24
|
end
|
|
29
|
-
@token = @resource.create_token
|
|
30
|
-
@resource.save
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
create_and_assign_token
|
|
27
|
+
|
|
28
|
+
sign_in(@resource, scope: :user, store: false, bypass: false)
|
|
33
29
|
|
|
34
30
|
yield @resource if block_given?
|
|
35
31
|
|
|
36
32
|
render_create_success
|
|
37
|
-
elsif @resource && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
|
|
33
|
+
elsif @resource && !Devise.paranoid && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
|
|
38
34
|
if @resource.respond_to?(:locked_at) && @resource.locked_at
|
|
39
35
|
render_create_error_account_locked
|
|
40
36
|
else
|
|
41
37
|
render_create_error_not_confirmed
|
|
42
38
|
end
|
|
43
39
|
else
|
|
40
|
+
hash_password_in_paranoid_mode
|
|
44
41
|
render_create_error_bad_credentials
|
|
45
42
|
end
|
|
46
43
|
end
|
|
@@ -78,7 +75,6 @@ module DeviseTokenAuth
|
|
|
78
75
|
def get_auth_params
|
|
79
76
|
auth_key = nil
|
|
80
77
|
auth_val = nil
|
|
81
|
-
|
|
82
78
|
# iterate thru allowed auth keys, use first found
|
|
83
79
|
resource_class.authentication_keys.each do |k|
|
|
84
80
|
if resource_params[k]
|
|
@@ -133,5 +129,25 @@ module DeviseTokenAuth
|
|
|
133
129
|
def resource_params
|
|
134
130
|
params.permit(*params_for_resource(:sign_in))
|
|
135
131
|
end
|
|
132
|
+
|
|
133
|
+
def create_and_assign_token
|
|
134
|
+
if @resource.respond_to?(:with_lock)
|
|
135
|
+
@resource.with_lock do
|
|
136
|
+
@token = @resource.create_token
|
|
137
|
+
@resource.save!
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
@token = @resource.create_token
|
|
141
|
+
@resource.save!
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def hash_password_in_paranoid_mode
|
|
146
|
+
# In order to avoid timing attacks in paranoid mode, we want the password hash to be
|
|
147
|
+
# calculated even if no resource has been found. Devise's DatabaseAuthenticatable warden
|
|
148
|
+
# strategy handles this case similarly:
|
|
149
|
+
# https://github.com/heartcombo/devise/blob/main/lib/devise/strategies/database_authenticatable.rb
|
|
150
|
+
resource_class.new.password = resource_params[:password] if Devise.paranoid
|
|
151
|
+
end
|
|
136
152
|
end
|
|
137
153
|
end
|
|
@@ -44,7 +44,8 @@ module DeviseTokenAuth
|
|
|
44
44
|
token.client,
|
|
45
45
|
redirect_header_options)
|
|
46
46
|
redirect_to(@resource.build_auth_url(after_unlock_path_for(@resource),
|
|
47
|
-
redirect_headers)
|
|
47
|
+
redirect_headers),
|
|
48
|
+
redirect_options)
|
|
48
49
|
else
|
|
49
50
|
render_show_error
|
|
50
51
|
end
|
|
@@ -80,7 +81,7 @@ module DeviseTokenAuth
|
|
|
80
81
|
|
|
81
82
|
def render_not_found_error
|
|
82
83
|
if Devise.paranoid
|
|
83
|
-
|
|
84
|
+
render_create_success
|
|
84
85
|
else
|
|
85
86
|
render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
|
|
86
87
|
end
|
|
@@ -120,6 +120,7 @@ module DeviseTokenAuth::Concerns::User
|
|
|
120
120
|
# ghetto HashWithIndifferentAccess
|
|
121
121
|
expiry = tokens[client]['expiry'] || tokens[client][:expiry]
|
|
122
122
|
token_hash = tokens[client]['token'] || tokens[client][:token]
|
|
123
|
+
previous_token_hash = tokens[client]['previous_token'] || tokens[client][:previous_token]
|
|
123
124
|
|
|
124
125
|
return true if (
|
|
125
126
|
# ensure that expiry and token are set
|
|
@@ -129,11 +130,24 @@ module DeviseTokenAuth::Concerns::User
|
|
|
129
130
|
DateTime.strptime(expiry.to_s, '%s') > Time.zone.now &&
|
|
130
131
|
|
|
131
132
|
# ensure that the token is valid
|
|
132
|
-
|
|
133
|
+
(
|
|
134
|
+
# check if the latest token matches
|
|
135
|
+
does_token_match?(token_hash, token) ||
|
|
136
|
+
|
|
137
|
+
# check if the previous token matches
|
|
138
|
+
does_token_match?(previous_token_hash, token)
|
|
139
|
+
)
|
|
133
140
|
)
|
|
134
141
|
end
|
|
135
142
|
|
|
136
|
-
#
|
|
143
|
+
# check if the hash of received token matches the stored token
|
|
144
|
+
def does_token_match?(token_hash, token)
|
|
145
|
+
return false if token_hash.nil?
|
|
146
|
+
|
|
147
|
+
DeviseTokenAuth::Concerns::User.tokens_match?(token_hash, token)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# allow batch requests to use the last token
|
|
137
151
|
def token_can_be_reused?(token, client)
|
|
138
152
|
# ghetto HashWithIndifferentAccess
|
|
139
153
|
updated_at = tokens[client]['updated_at'] || tokens[client][:updated_at]
|
|
@@ -143,7 +157,7 @@ module DeviseTokenAuth::Concerns::User
|
|
|
143
157
|
# ensure that the last token and its creation time exist
|
|
144
158
|
updated_at && last_token_hash &&
|
|
145
159
|
|
|
146
|
-
# ensure that
|
|
160
|
+
# ensure that last token falls within the batch buffer throttle time of the last request
|
|
147
161
|
updated_at.to_time > Time.zone.now - DeviseTokenAuth.batch_request_buffer_throttle &&
|
|
148
162
|
|
|
149
163
|
# ensure that the token is valid
|
|
@@ -157,29 +171,38 @@ module DeviseTokenAuth::Concerns::User
|
|
|
157
171
|
|
|
158
172
|
token = create_token(
|
|
159
173
|
client: client,
|
|
160
|
-
|
|
174
|
+
previous_token: tokens.fetch(client, {})['token'],
|
|
175
|
+
last_token: tokens.fetch(client, {})['previous_token'],
|
|
161
176
|
updated_at: now
|
|
162
177
|
)
|
|
163
178
|
|
|
164
|
-
|
|
179
|
+
update_auth_headers(token.token, token.client)
|
|
165
180
|
end
|
|
166
181
|
|
|
167
|
-
def
|
|
182
|
+
def build_auth_headers(token, client = 'default')
|
|
168
183
|
# client may use expiry to prevent validation request if expired
|
|
169
184
|
# must be cast as string or headers will break
|
|
170
185
|
expiry = tokens[client]['expiry'] || tokens[client][:expiry]
|
|
171
|
-
|
|
172
|
-
{
|
|
186
|
+
headers = {
|
|
173
187
|
DeviseTokenAuth.headers_names[:"access-token"] => token,
|
|
174
188
|
DeviseTokenAuth.headers_names[:"token-type"] => 'Bearer',
|
|
175
189
|
DeviseTokenAuth.headers_names[:"client"] => client,
|
|
176
190
|
DeviseTokenAuth.headers_names[:"expiry"] => expiry.to_s,
|
|
177
191
|
DeviseTokenAuth.headers_names[:"uid"] => uid
|
|
178
192
|
}
|
|
193
|
+
headers.merge(build_bearer_token(headers))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def build_bearer_token(auth)
|
|
197
|
+
return {} if DeviseTokenAuth.cookie_enabled # There is no need for the bearer token if it is using cookies
|
|
198
|
+
|
|
199
|
+
encoded_token = Base64.strict_encode64(auth.to_json)
|
|
200
|
+
bearer_token = "Bearer #{encoded_token}"
|
|
201
|
+
{ DeviseTokenAuth.headers_names[:"authorization"] => bearer_token }
|
|
179
202
|
end
|
|
180
203
|
|
|
181
|
-
def
|
|
182
|
-
headers =
|
|
204
|
+
def update_auth_headers(token, client = 'default')
|
|
205
|
+
headers = build_auth_headers(token, client)
|
|
183
206
|
clean_old_tokens
|
|
184
207
|
save!
|
|
185
208
|
|
|
@@ -195,7 +218,7 @@ module DeviseTokenAuth::Concerns::User
|
|
|
195
218
|
|
|
196
219
|
def extend_batch_buffer(token, client)
|
|
197
220
|
tokens[client]['updated_at'] = Time.zone.now
|
|
198
|
-
|
|
221
|
+
update_auth_headers(token, client)
|
|
199
222
|
end
|
|
200
223
|
|
|
201
224
|
def confirmed?
|
|
@@ -4,12 +4,12 @@ module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
|
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
6
6
|
included do
|
|
7
|
-
validates :email, presence: true,if:
|
|
8
|
-
validates :email, :devise_token_auth_email => true, allow_nil: true, allow_blank: true, if:
|
|
9
|
-
validates_presence_of :uid,
|
|
7
|
+
validates :email, presence: true, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
8
|
+
validates :email, :devise_token_auth_email => true, allow_nil: true, allow_blank: true, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
9
|
+
validates_presence_of :uid, if: lambda { uid_and_provider_defined? && !email_provider? }
|
|
10
10
|
|
|
11
11
|
# only validate unique emails among email registration users
|
|
12
|
-
validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create, if:
|
|
12
|
+
validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
13
13
|
|
|
14
14
|
# keep uid in sync with email
|
|
15
15
|
before_save :sync_uid
|
|
@@ -18,6 +18,10 @@ module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
|
|
|
18
18
|
|
|
19
19
|
protected
|
|
20
20
|
|
|
21
|
+
def uid_and_provider_defined?
|
|
22
|
+
defined?(provider) && defined?(uid)
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
def email_provider?
|
|
22
26
|
provider == 'email'
|
|
23
27
|
end
|
|
@@ -26,6 +30,6 @@ module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
|
|
|
26
30
|
unless self.new_record?
|
|
27
31
|
return if devise_modules.include?(:confirmable) && !@bypass_confirmation_postpone && postpone_email_change?
|
|
28
32
|
end
|
|
29
|
-
self.uid = email if email_provider?
|
|
33
|
+
self.uid = email if uid_and_provider_defined? && email_provider?
|
|
30
34
|
end
|
|
31
35
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class DeviseTokenAuthEmailValidator < ActiveModel::EachValidator
|
|
4
|
+
EMAIL_REGEXP = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def validate?(email)
|
|
8
|
+
email =~ EMAIL_REGEXP
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
4
12
|
def validate_each(record, attribute, value)
|
|
5
|
-
unless value
|
|
13
|
+
unless DeviseTokenAuthEmailValidator.validate?(value)
|
|
6
14
|
record.errors.add(attribute, email_invalid_message)
|
|
7
15
|
end
|
|
8
16
|
end
|
data/config/locales/ja.yml
CHANGED
|
@@ -21,10 +21,22 @@ ja:
|
|
|
21
21
|
missing_redirect_url: "リダイレクト URL が与えられていません。"
|
|
22
22
|
not_allowed_redirect_url: "'%{redirect_url}' へのリダイレクトは許可されていません。"
|
|
23
23
|
sended: "'%{email}' にパスワードリセットの案内が送信されました。"
|
|
24
|
+
sended_paranoid: "すでにメールアドレスがデータベースに登録されている場合、 数分後にパスワード再発行用のリンクを記載したメールをお送りします。"
|
|
24
25
|
user_not_found: "メールアドレス '%{email}' のユーザーが見つかりません。"
|
|
25
26
|
password_not_required: "このアカウントはパスワードを要求していません。'%{provider}' を利用してログインしてください。"
|
|
26
27
|
missing_passwords: "'Password', 'Password confirmation' パラメータが与えられていません。"
|
|
27
28
|
successfully_updated: "パスワードの更新に成功しました。"
|
|
29
|
+
unlocks:
|
|
30
|
+
missing_email: "メールアドレスが与えられていません。"
|
|
31
|
+
sended: "%{email}' にアカウントのロックを解除する方法を記載したメールが送信されました。"
|
|
32
|
+
sended_paranoid: "アカウントが存在する場合、数分後にロックを解除する方法を記載したメールをお送りします。"
|
|
33
|
+
user_not_found: "メールアドレス '%{email}' を持つユーザーが見つかりません。"
|
|
34
|
+
confirmations:
|
|
35
|
+
sended: "'%{email}' にアカウントの確認方法を記載したメールが送信されました。"
|
|
36
|
+
sended_paranoid: "すでにメールアドレスがデータベースに登録されている場合、数分後にメールアドレスの確認方法を記載したメールをお送りします。"
|
|
37
|
+
user_not_found: "メールアドレス '%{email}' を持つユーザーが見つかりません。"
|
|
38
|
+
missing_email: "メールアドレスが与えられていません。"
|
|
39
|
+
|
|
28
40
|
errors:
|
|
29
41
|
messages:
|
|
30
42
|
validate_sign_up_params: "リクエストボディに適切なアカウント新規登録データを送信してください。"
|
|
@@ -30,7 +30,8 @@ module DeviseTokenAuth
|
|
|
30
30
|
:cookie_attributes,
|
|
31
31
|
:bypass_sign_in,
|
|
32
32
|
:send_confirmation_email,
|
|
33
|
-
:require_client_password_reset_token
|
|
33
|
+
:require_client_password_reset_token,
|
|
34
|
+
:other_uid
|
|
34
35
|
|
|
35
36
|
self.change_headers_on_each_request = true
|
|
36
37
|
self.max_number_of_devices = 10
|
|
@@ -45,7 +46,8 @@ module DeviseTokenAuth
|
|
|
45
46
|
self.enable_standard_devise_support = false
|
|
46
47
|
self.remove_tokens_after_password_reset = false
|
|
47
48
|
self.default_callbacks = true
|
|
48
|
-
self.headers_names = { '
|
|
49
|
+
self.headers_names = { 'authorization': 'Authorization',
|
|
50
|
+
'access-token': 'access-token',
|
|
49
51
|
'client': 'client',
|
|
50
52
|
'expiry': 'expiry',
|
|
51
53
|
'uid': 'uid',
|
|
@@ -56,6 +58,7 @@ module DeviseTokenAuth
|
|
|
56
58
|
self.bypass_sign_in = true
|
|
57
59
|
self.send_confirmation_email = false
|
|
58
60
|
self.require_client_password_reset_token = false
|
|
61
|
+
self.other_uid = nil
|
|
59
62
|
|
|
60
63
|
def self.setup(&block)
|
|
61
64
|
yield self
|
|
@@ -65,17 +65,18 @@ module ActionDispatch::Routing
|
|
|
65
65
|
|
|
66
66
|
# omniauth routes. only define if omniauth is installed and not skipped.
|
|
67
67
|
if defined?(::OmniAuth) && !opts[:skip].include?(:omniauth_callbacks)
|
|
68
|
-
match "#{full_path}/failure", controller: omniauth_ctrl, action: 'omniauth_failure', via: [:get]
|
|
69
|
-
match "#{full_path}/:provider/callback", controller: omniauth_ctrl, action: 'omniauth_success', via: [:get]
|
|
68
|
+
match "#{full_path}/failure", controller: omniauth_ctrl, action: 'omniauth_failure', via: [:get, :post]
|
|
69
|
+
match "#{full_path}/:provider/callback", controller: omniauth_ctrl, action: 'omniauth_success', via: [:get, :post]
|
|
70
70
|
|
|
71
71
|
match "#{DeviseTokenAuth.omniauth_prefix}/:provider/callback", controller: omniauth_ctrl, action: 'redirect_callbacks', via: [:get, :post]
|
|
72
72
|
match "#{DeviseTokenAuth.omniauth_prefix}/failure", controller: omniauth_ctrl, action: 'omniauth_failure', via: [:get, :post]
|
|
73
73
|
|
|
74
74
|
# preserve the resource class thru oauth authentication by setting name of
|
|
75
75
|
# resource as "resource_class" param
|
|
76
|
-
match "#{full_path}/:provider", to: redirect{ |params, request|
|
|
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
|
+
qs = CGI::parse(request.env['QUERY_STRING'].empty? ? request.body.read : request.env['QUERY_STRING'] )
|
|
79
80
|
|
|
80
81
|
# append name of current resource
|
|
81
82
|
qs['resource_class'] = [resource]
|
|
@@ -99,7 +100,7 @@ module ActionDispatch::Routing
|
|
|
99
100
|
|
|
100
101
|
# re-construct the path for omniauth
|
|
101
102
|
"#{::OmniAuth.config.path_prefix}/#{params[:provider]}?#{redirect_params.to_param}"
|
|
102
|
-
}, via: [:get]
|
|
103
|
+
}, via: [:get, :post]
|
|
103
104
|
end
|
|
104
105
|
end
|
|
105
106
|
end
|