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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/app/controllers/devise_token_auth/application_controller.rb +13 -0
  4. data/app/controllers/devise_token_auth/concerns/resource_finder.rb +2 -1
  5. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +20 -7
  6. data/app/controllers/devise_token_auth/confirmations_controller.rb +8 -5
  7. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +18 -21
  8. data/app/controllers/devise_token_auth/passwords_controller.rb +9 -3
  9. data/app/controllers/devise_token_auth/sessions_controller.rb +26 -10
  10. data/app/controllers/devise_token_auth/unlocks_controller.rb +3 -2
  11. data/app/models/devise_token_auth/concerns/user.rb +34 -11
  12. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +9 -5
  13. data/app/validators/devise_token_auth_email_validator.rb +9 -1
  14. data/config/locales/ja.yml +12 -0
  15. data/lib/devise_token_auth/engine.rb +5 -2
  16. data/lib/devise_token_auth/rails/routes.rb +6 -5
  17. data/lib/devise_token_auth/version.rb +1 -1
  18. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +11 -5
  19. data/test/controllers/custom/custom_confirmations_controller_test.rb +1 -1
  20. data/test/controllers/custom/custom_omniauth_callbacks_controller_test.rb +1 -1
  21. data/test/controllers/demo_mang_controller_test.rb +37 -8
  22. data/test/controllers/demo_user_controller_test.rb +37 -8
  23. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +19 -7
  24. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +16 -19
  25. data/test/controllers/devise_token_auth/passwords_controller_test.rb +6 -6
  26. data/test/controllers/devise_token_auth/registrations_controller_test.rb +2 -2
  27. data/test/controllers/devise_token_auth/sessions_controller_test.rb +111 -38
  28. data/test/controllers/devise_token_auth/token_validations_controller_test.rb +41 -1
  29. data/test/controllers/devise_token_auth/unlocks_controller_test.rb +28 -6
  30. data/test/controllers/overrides/omniauth_callbacks_controller_test.rb +1 -1
  31. data/test/dummy/app/controllers/application_controller.rb +2 -6
  32. data/test/dummy/app/controllers/overrides/confirmations_controller.rb +2 -1
  33. data/test/dummy/app/controllers/overrides/passwords_controller.rb +2 -1
  34. data/test/dummy/config/environments/test.rb +6 -2
  35. data/test/dummy/db/schema.rb +5 -5
  36. data/test/dummy/tmp/generators/app/models/user.rb +9 -0
  37. data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +11 -5
  38. data/test/dummy/tmp/generators/db/migrate/20230415183419_devise_token_auth_create_users.rb +49 -0
  39. data/test/models/user_test.rb +22 -0
  40. metadata +94 -94
  41. data/test/dummy/tmp/generators/app/controllers/application_controller.rb +0 -6
  42. 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: fb2d73d7859e1754b505d6f554c8d298ba899444b4fe4e1b47d50ca9bab453e8
4
- data.tar.gz: 3572d4ff07d68f62d8e51270959fd20451d9edb4832d576b9342939275390dee
3
+ metadata.gz: 135b5088d17b20d187a6ffe314356dd2a2474d9bd98898bc9e36bc931e6c9fef
4
+ data.tar.gz: cf3c3c6fc19564248bdcb22e3b99624bd25a027383649a45c7e7bf1fddfd5bbd
5
5
  SHA512:
6
- metadata.gz: 50c95181401bedfd959a407d450f222ab185d75000825385dd691a064e831b36263eb1338d25f6378a743ac9009b73f80df3e24cb09ce5680a0e6723fc98acb9
7
- data.tar.gz: 91910874d7e473d31eb39cf40c6860da4ab5b59aa874a0f1296faa17718103124018568cf289486a9d49a3ec1b967f14e23c18afb8d3f6cd3ec2fd837d663a83
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://travis-ci.org/lynndylanhurley/devise_token_auth.svg?branch=master)](https://travis-ci.org/lynndylanhurley/devise_token_auth)
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.where("BINARY #{field} = ? AND provider= ?", value, provider).first
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.build_auth_header(@token.token, @token.client)
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
- end
25
+ end
26
26
 
27
- redirect_to(redirect_to_link)
27
+ redirect_to(redirect_to_link, redirect_options)
28
28
  else
29
- raise ActionController::RoutingError, 'Not Found'
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
- render_error(404, I18n.t('devise_token_auth.confirmations.sended_paranoid'))
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 omniauth_params['resource_class']
133
- omniauth_params['resource_class'].constantize
134
- elsif params['resource_class']
135
- params['resource_class'].constantize
136
- else
137
- raise 'No resource_class found'
138
- end
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 sesison value is set by the redirect_callbacks method. its purpose
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 immediatly after it is accessed by omniauth_success
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
- render_error(404, I18n.t('devise_token_auth.passwords.sended_paranoid'))
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
- # Check
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
- sign_in(:user, @resource, store: false, bypass: false)
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
- render_error(404, I18n.t('devise_token_auth.unlocks.sended_paranoid'))
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
- DeviseTokenAuth::Concerns::User.tokens_match?(token_hash, token)
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
- # allow batch requests to use the previous token
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 previous token falls within the batch buffer throttle time of the last request
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
- last_token: tokens.fetch(client, {})['token'],
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
- update_auth_header(token.token, token.client)
179
+ update_auth_headers(token.token, token.client)
165
180
  end
166
181
 
167
- def build_auth_header(token, client = 'default')
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 update_auth_header(token, client = 'default')
182
- headers = build_auth_header(token, client)
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
- update_auth_header(token, client)
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: :email_provider?
8
- validates :email, :devise_token_auth_email => true, allow_nil: true, allow_blank: true, if: :email_provider?
9
- validates_presence_of :uid, unless: :email_provider?
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: :email_provider?
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 =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
13
+ unless DeviseTokenAuthEmailValidator.validate?(value)
6
14
  record.errors.add(attribute, email_invalid_message)
7
15
  end
8
16
  end
@@ -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 = { 'access-token': 'access-token',
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
- qs = CGI::parse(request.env['QUERY_STRING'])
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeviseTokenAuth
4
- VERSION = '1.2.0'.freeze
4
+ VERSION = '1.2.2'.freeze
5
5
  end