devise_token_auth 1.2.0 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/devise_token_auth.svg)](http://badge.fury.io/rb/devise_token_auth)
|
4
|
-
[![Build Status](https://
|
4
|
+
[![Build Status](https://github.com/lynndylanhurley/devise_token_auth/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/lynndylanhurley/devise_token_auth/actions/workflows/test.yml)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/lynndylanhurley/devise_token_auth/badges/gpa.svg)](https://codeclimate.com/github/lynndylanhurley/devise_token_auth)
|
6
6
|
[![Test Coverage](https://codeclimate.com/github/lynndylanhurley/devise_token_auth/badges/coverage.svg)](https://codeclimate.com/github/lynndylanhurley/devise_token_auth/coverage)
|
7
7
|
[![Downloads](https://img.shields.io/gem/dt/devise_token_auth.svg)](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
|