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.
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