devise_token_auth 1.2.0 → 1.2.1

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/devise_token_auth/application_controller.rb +9 -0
  3. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +17 -4
  4. data/app/controllers/devise_token_auth/confirmations_controller.rb +1 -1
  5. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +8 -4
  6. data/app/controllers/devise_token_auth/passwords_controller.rb +5 -1
  7. data/app/controllers/devise_token_auth/sessions_controller.rb +14 -2
  8. data/app/controllers/devise_token_auth/unlocks_controller.rb +1 -1
  9. data/app/models/devise_token_auth/concerns/user.rb +27 -6
  10. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +9 -5
  11. data/app/validators/devise_token_auth_email_validator.rb +9 -1
  12. data/config/locales/ja.yml +12 -0
  13. data/lib/devise_token_auth/engine.rb +5 -2
  14. data/lib/devise_token_auth/rails/routes.rb +2 -2
  15. data/lib/devise_token_auth/version.rb +1 -1
  16. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +3 -0
  17. data/test/controllers/demo_mang_controller_test.rb +37 -8
  18. data/test/controllers/demo_user_controller_test.rb +37 -8
  19. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +12 -3
  20. data/test/controllers/devise_token_auth/passwords_controller_test.rb +6 -6
  21. data/test/controllers/devise_token_auth/token_validations_controller_test.rb +41 -1
  22. data/test/controllers/devise_token_auth/unlocks_controller_test.rb +28 -6
  23. data/test/dummy/db/schema.rb +5 -5
  24. data/test/dummy/tmp/generators/app/models/user.rb +11 -0
  25. data/test/dummy/tmp/generators/db/migrate/20220822003050_devise_token_auth_create_users.rb +49 -0
  26. data/test/models/user_test.rb +22 -0
  27. metadata +10 -10
  28. data/test/dummy/tmp/generators/app/controllers/application_controller.rb +0 -6
  29. 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: be08ae7f01121ebe8c6b9b8fe04bcc2bdc83a2c8108452ffc986f1865278e85f
4
+ data.tar.gz: 272f45dc6f28fba16b6a523f47cbb9ecf3be9c05d4aa644ee0d0998fa5272f43
5
5
  SHA512:
6
- metadata.gz: 50c95181401bedfd959a407d450f222ab185d75000825385dd691a064e831b36263eb1338d25f6378a743ac9009b73f80df3e24cb09ce5680a0e6723fc98acb9
7
- data.tar.gz: 91910874d7e473d31eb39cf40c6860da4ab5b59aa874a0f1296faa17718103124018568cf289486a9d49a3ec1b967f14e23c18afb8d3f6cd3ec2fd837d663a83
6
+ metadata.gz: cc54c90eee4fdf43e6d9b72ca905fc58e4338f1310b289bbede651978bd4f407556392d3d4c61cfaeabf6d4fba179768e02ec9c00bc245b33ba629972676c676
7
+ data.tar.gz: 00e139ae99fe395580ef8f846cca46516792b68cda0e0201e551e90ca9c70679fcf9519d3682ea82095588e78a93bb2def3db2bebe670a0e96f933e7087fee4b
@@ -83,5 +83,14 @@ module DeviseTokenAuth
83
83
  I18n.t("devise_token_auth.#{name}.sended", email: email)
84
84
  end
85
85
  end
86
+
87
+ # When using a cookie to transport the auth token we can set it immediately in flows such as
88
+ # reset password and OmniAuth success, rather than making the client scrape the token from
89
+ # query params (to then send in the initial validate_token request).
90
+ # TODO: We should be able to stop exposing the token in query params when this method is used
91
+ def set_token_in_cookie(resource, token)
92
+ auth_header = resource.build_auth_header(token.token, token.client)
93
+ cookies[DeviseTokenAuth.cookie_name] = DeviseTokenAuth.cookie_attributes.merge(value: auth_header.to_json)
94
+ end
86
95
  end
87
96
  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)
@@ -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
@@ -62,7 +62,7 @@ module DeviseTokenAuth
62
62
 
63
63
  def render_not_found_error
64
64
  if Devise.paranoid
65
- render_error(404, I18n.t('devise_token_auth.confirmations.sended_paranoid'))
65
+ render_create_success
66
66
  else
67
67
  render_error(404, I18n.t('devise_token_auth.confirmations.user_not_found', email: @email))
68
68
  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
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
 
@@ -51,6 +51,10 @@ module DeviseTokenAuth
51
51
  if require_client_password_reset_token?
52
52
  redirect_to DeviseTokenAuth::Url.generate(@redirect_url, reset_password_token: resource_params[:reset_password_token])
53
53
  else
54
+ if DeviseTokenAuth.cookie_enabled
55
+ set_token_in_cookie(@resource, token)
56
+ end
57
+
54
58
  redirect_header_options = { reset_password: true }
55
59
  redirect_headers = build_redirect_headers(token.token,
56
60
  token.client,
@@ -182,7 +186,7 @@ module DeviseTokenAuth
182
186
 
183
187
  def render_not_found_error
184
188
  if Devise.paranoid
185
- render_error(404, I18n.t('devise_token_auth.passwords.sended_paranoid'))
189
+ render_create_success
186
190
  else
187
191
  render_error(404, I18n.t('devise_token_auth.passwords.user_not_found', email: @email))
188
192
  end
@@ -26,8 +26,8 @@ module DeviseTokenAuth
26
26
  if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
27
27
  return render_create_error_bad_credentials
28
28
  end
29
- @token = @resource.create_token
30
- @resource.save
29
+
30
+ create_and_assign_token
31
31
 
32
32
  sign_in(:user, @resource, store: false, bypass: false)
33
33
 
@@ -133,5 +133,17 @@ module DeviseTokenAuth
133
133
  def resource_params
134
134
  params.permit(*params_for_resource(:sign_in))
135
135
  end
136
+
137
+ def create_and_assign_token
138
+ if @resource.respond_to?(:with_lock)
139
+ @resource.with_lock do
140
+ @token = @resource.create_token
141
+ @resource.save!
142
+ end
143
+ else
144
+ @token = @resource.create_token
145
+ @resource.save!
146
+ end
147
+ end
136
148
  end
137
149
  end
@@ -80,7 +80,7 @@ module DeviseTokenAuth
80
80
 
81
81
  def render_not_found_error
82
82
  if Devise.paranoid
83
- render_error(404, I18n.t('devise_token_auth.unlocks.sended_paranoid'))
83
+ render_create_success
84
84
  else
85
85
  render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
86
86
  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,7 +171,8 @@ 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
 
@@ -168,14 +183,20 @@ module DeviseTokenAuth::Concerns::User
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
+ encoded_token = Base64.strict_encode64(auth.to_json)
198
+ bearer_token = "Bearer #{encoded_token}"
199
+ {DeviseTokenAuth.headers_names[:"authorization"] => bearer_token}
179
200
  end
180
201
 
181
202
  def update_auth_header(token, client = 'default')
@@ -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
@@ -73,7 +73,7 @@ module ActionDispatch::Routing
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
  qs = CGI::parse(request.env['QUERY_STRING'])
79
79
 
@@ -99,7 +99,7 @@ module ActionDispatch::Routing
99
99
 
100
100
  # re-construct the path for omniauth
101
101
  "#{::OmniAuth.config.path_prefix}/#{params[:provider]}?#{redirect_params.to_param}"
102
- }, via: [:get]
102
+ }, via: [:get, :post]
103
103
  end
104
104
  end
105
105
  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.1'.freeze
5
5
  end
@@ -48,6 +48,9 @@ DeviseTokenAuth.setup do |config|
48
48
  # :'uid' => 'uid',
49
49
  # :'token-type' => 'token-type' }
50
50
 
51
+ # Makes it possible to use custom uid column
52
+ # config.other_uid = "foo"
53
+
51
54
  # By default, only Bearer Token authentication is implemented out of the box.
52
55
  # If, however, you wish to integrate with legacy Devise authentication, you can
53
56
  # do so by enabling this flag. NOTE: This feature is highly experimental!
@@ -235,7 +235,7 @@ class DemoMangControllerTest < ActionDispatch::IntegrationTest
235
235
  @resource.reload
236
236
  age_token(@resource, @client_id)
237
237
 
238
- # use expired auth header
238
+ # use previous auth header
239
239
  get '/demo/members_only_mang',
240
240
  params: {},
241
241
  headers: @auth_headers
@@ -244,38 +244,67 @@ class DemoMangControllerTest < ActionDispatch::IntegrationTest
244
244
  @second_user = assigns(:resource)
245
245
  @second_access_token = response.headers['access-token']
246
246
  @second_response_status = response.status
247
+
248
+ @resource.reload
249
+ age_token(@resource, @client_id)
250
+
251
+ # use expired auth headers
252
+ get '/demo/members_only_mang',
253
+ params: {},
254
+ headers: @auth_headers
255
+
256
+ @third_is_batch_request = assigns(:is_batch_request)
257
+ @third_user = assigns(:resource)
258
+ @third_access_token = response.headers['access-token']
259
+ @third_response_status = response.status
247
260
  end
248
261
 
249
262
  it 'should allow the first request through' do
250
263
  assert_equal 200, @first_response_status
251
264
  end
252
265
 
266
+ it 'should allow the second request through' do
267
+ assert_equal 200, @second_response_status
268
+ end
269
+
253
270
  it 'should not allow the second request through' do
254
- assert_equal 401, @second_response_status
271
+ assert_equal 401, @third_response_status
255
272
  end
256
273
 
257
274
  it 'should not treat first request as batch request' do
275
+ refute @first_is_batch_request
276
+ end
277
+
278
+ it 'should not treat second request as batch request' do
258
279
  refute @second_is_batch_request
259
280
  end
260
281
 
282
+ it 'should not treat third request as batch request' do
283
+ refute @third_is_batch_request
284
+ end
285
+
261
286
  it 'should return auth headers from the first request' do
262
287
  assert @first_access_token
263
288
  end
264
289
 
265
- it 'should not treat second request as batch request' do
266
- refute @second_is_batch_request
290
+ it 'should return auth headers from the second request' do
291
+ assert @second_access_token
267
292
  end
268
293
 
269
- it 'should not return auth headers from the second request' do
270
- refute @second_access_token
294
+ it 'should not return auth headers from the third request' do
295
+ refute @third_access_token
271
296
  end
272
297
 
273
298
  it 'should define user during first request' do
274
299
  assert @first_user
275
300
  end
276
301
 
277
- it 'should not define user during second request' do
278
- refute @second_user
302
+ it 'should define user during second request' do
303
+ assert @second_user
304
+ end
305
+
306
+ it 'should not define user during third request' do
307
+ refute @third_user
279
308
  end
280
309
  end
281
310
  end
@@ -265,7 +265,7 @@ class DemoUserControllerTest < ActionDispatch::IntegrationTest
265
265
  @resource.reload
266
266
  age_token(@resource, @client_id)
267
267
 
268
- # use expired auth header
268
+ # use previous auth header
269
269
  get '/demo/members_only',
270
270
  params: {},
271
271
  headers: @auth_headers
@@ -274,38 +274,67 @@ class DemoUserControllerTest < ActionDispatch::IntegrationTest
274
274
  @second_user = assigns(:resource)
275
275
  @second_access_token = response.headers['access-token']
276
276
  @second_response_status = response.status
277
+
278
+ @resource.reload
279
+ age_token(@resource, @client_id)
280
+
281
+ # use expired auth headers
282
+ get '/demo/members_only_mang',
283
+ params: {},
284
+ headers: @auth_headers
285
+
286
+ @third_is_batch_request = assigns(:is_batch_request)
287
+ @third_user = assigns(:resource)
288
+ @third_access_token = response.headers['access-token']
289
+ @third_response_status = response.status
277
290
  end
278
291
 
279
292
  it 'should allow the first request through' do
280
293
  assert_equal 200, @first_response_status
281
294
  end
282
295
 
296
+ it 'should allow the second request through' do
297
+ assert_equal 200, @second_response_status
298
+ end
299
+
283
300
  it 'should not allow the second request through' do
284
- assert_equal 401, @second_response_status
301
+ assert_equal 401, @third_response_status
285
302
  end
286
303
 
287
304
  it 'should not treat first request as batch request' do
305
+ refute @first_is_batch_request
306
+ end
307
+
308
+ it 'should not treat second request as batch request' do
288
309
  refute @second_is_batch_request
289
310
  end
290
311
 
312
+ it 'should not treat third request as batch request' do
313
+ refute @third_is_batch_request
314
+ end
315
+
291
316
  it 'should return auth headers from the first request' do
292
317
  assert @first_access_token
293
318
  end
294
319
 
295
- it 'should not treat second request as batch request' do
296
- refute @second_is_batch_request
320
+ it 'should return auth headers from the second request' do
321
+ assert @second_access_token
297
322
  end
298
323
 
299
- it 'should not return auth headers from the second request' do
300
- refute @second_access_token
324
+ it 'should not return auth headers from the third request' do
325
+ refute @third_access_token
301
326
  end
302
327
 
303
328
  it 'should define user during first request' do
304
329
  assert @first_user
305
330
  end
306
331
 
307
- it 'should not define user during second request' do
308
- refute @second_user
332
+ it 'should define user during second request' do
333
+ assert @second_user
334
+ end
335
+
336
+ it 'should not define user during third request' do
337
+ refute @third_user
309
338
  end
310
339
  end
311
340
  end
@@ -171,21 +171,30 @@ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
171
171
  test 'response should contain message' do
172
172
  assert_equal @data['message'], I18n.t('devise_token_auth.confirmations.sended_paranoid', email: @resource.email)
173
173
  end
174
+
175
+ test 'response should return success status' do
176
+ assert_equal 200, response.status
177
+ end
174
178
  end
175
179
 
176
180
  describe 'on failure' do
177
181
  before do
178
182
  swap Devise, paranoid: true do
183
+ @email = 'chester@cheet.ah'
179
184
  post :create,
180
- params: { email: 'chester@cheet.ah',
185
+ params: { email: @email,
181
186
  redirect_url: @redirect_url },
182
187
  xhr: true
183
188
  @data = JSON.parse(response.body)
184
189
  end
185
190
  end
186
191
 
187
- test 'response should contain errors' do
188
- assert_equal @data['errors'], [I18n.t('devise_token_auth.confirmations.sended_paranoid')]
192
+ test 'response should not contain errors' do
193
+ assert_equal @data['message'], I18n.t('devise_token_auth.confirmations.sended_paranoid', email: @email)
194
+ end
195
+
196
+ test 'response should return success status' do
197
+ assert_equal 200, response.status
189
198
  end
190
199
  end
191
200
  end
@@ -116,14 +116,14 @@ class DeviseTokenAuth::PasswordsControllerTest < ActionController::TestCase
116
116
  end
117
117
  end
118
118
 
119
- test 'unknown user should return 404' do
120
- assert_equal 404, response.status
119
+ test 'response should return success status' do
120
+ assert_equal 200, response.status
121
121
  end
122
122
 
123
- test 'errors should be returned' do
124
- assert @data['errors']
125
- assert_equal @data['errors'],
126
- [I18n.t('devise_token_auth.passwords.sended_paranoid')]
123
+ test 'response should contain message' do
124
+ assert_equal \
125
+ @data['message'],
126
+ I18n.t('devise_token_auth.passwords.sended_paranoid')
127
127
  end
128
128
  end
129
129
  end
@@ -18,11 +18,51 @@ class DeviseTokenAuth::TokenValidationsControllerTest < ActionDispatch::Integrat
18
18
  @token = @auth_headers['access-token']
19
19
  @client_id = @auth_headers['client']
20
20
  @expiry = @auth_headers['expiry']
21
-
21
+ @authorization_header = @auth_headers.slice('Authorization')
22
22
  # ensure that request is not treated as batch request
23
23
  age_token(@resource, @client_id)
24
24
  end
25
25
 
26
+ describe 'using only Authorization header' do
27
+ describe 'using valid Authorization header' do
28
+ before do
29
+ get '/auth/validate_token', params: {}, headers: @authorization_header
30
+ end
31
+
32
+ test 'token valid' do
33
+ assert_equal 200, response.status
34
+ end
35
+ end
36
+
37
+ describe 'using invalid Authorization header' do
38
+ describe 'with invalid base64' do
39
+ before do
40
+ get '/auth/validate_token', params: {}, headers: {'Authorization': 'Bearer invalidtoken=='}
41
+ end
42
+
43
+ test 'returns access denied' do
44
+ assert_equal 401, response.status
45
+ end
46
+ end
47
+
48
+ describe 'with valid base64' do
49
+ before do
50
+ valid_base64 = Base64.strict_encode64({
51
+ "access-token": 'invalidtoken',
52
+ "token-type": 'Bearer',
53
+ "client": 'client',
54
+ "expiry": '1234567'
55
+ }.to_json)
56
+ get '/auth/validate_token', params: {}, headers: {'Authorization': "Bearer #{valid_base64}"}
57
+ end
58
+
59
+ test 'returns access denied' do
60
+ assert_equal 401, response.status
61
+ end
62
+ end
63
+ end
64
+ end
65
+
26
66
  describe 'vanilla user' do
27
67
  before do
28
68
  get '/auth/validate_token', params: {}, headers: @auth_headers
@@ -81,17 +81,19 @@ class DeviseTokenAuth::UnlocksControllerTest < ActionController::TestCase
81
81
  end
82
82
  end
83
83
 
84
- test 'unknown user should return 404' do
85
- assert_equal 404, response.status
84
+ test 'should always return success' do
85
+ assert_equal 200, response.status
86
86
  end
87
87
 
88
- test 'errors should be returned' do
89
- assert @data['errors']
90
- assert_equal @data['errors'], [I18n.t('devise_token_auth.unlocks.sended_paranoid')]
88
+ test 'errors should not be returned' do
89
+ assert @data['success']
90
+ assert_equal \
91
+ @data['message'],
92
+ I18n.t('devise_token_auth.unlocks.sended_paranoid')
91
93
  end
92
94
  end
93
95
 
94
- describe 'successfully requested unlock' do
96
+ describe 'successfully requested unlock without paranoid mode' do
95
97
  before do
96
98
  post :create, params: { email: @resource.email }
97
99
 
@@ -103,6 +105,26 @@ class DeviseTokenAuth::UnlocksControllerTest < ActionController::TestCase
103
105
  end
104
106
  end
105
107
 
108
+ describe 'successfully requested unlock with paranoid mode' do
109
+ before do
110
+ swap Devise, paranoid: true do
111
+ post :create, params: { email: @resource.email }
112
+ @data = JSON.parse(response.body)
113
+ end
114
+ end
115
+
116
+ test 'should always return success' do
117
+ assert_equal 200, response.status
118
+ end
119
+
120
+ test 'errors should not be returned' do
121
+ assert @data['success']
122
+ assert_equal \
123
+ @data['message'],
124
+ I18n.t('devise_token_auth.unlocks.sended_paranoid')
125
+ end
126
+ end
127
+
106
128
  describe 'case-sensitive email' do
107
129
  before do
108
130
  post :create, params: { email: @resource.email }
@@ -2,11 +2,11 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # Note that this schema.rb definition is the authoritative source for your
6
- # database schema. If you need to create the application database on another
7
- # system, you should be using db:schema:load, not running all the migrations
8
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
- # you'll amass, the slower it'll run and the greater likelihood for issues).
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
@@ -0,0 +1,11 @@
1
+ class User < ApplicationRecord
2
+ # Include default devise modules.
3
+ devise :database_authenticatable, :registerable,
4
+ :recoverable, :rememberable, :trackable, :validatable,
5
+ :confirmable, :omniauthable
6
+ include DeviseTokenAuth::Concerns::User
7
+
8
+ def whatever
9
+ puts 'whatever'
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
2
+ def change
3
+
4
+ create_table(:users) do |t|
5
+ ## Required
6
+ t.string :provider, :null => false, :default => "email"
7
+ t.string :uid, :null => false, :default => ""
8
+
9
+ ## Database authenticatable
10
+ t.string :encrypted_password, :null => false, :default => ""
11
+
12
+ ## Recoverable
13
+ t.string :reset_password_token
14
+ t.datetime :reset_password_sent_at
15
+ t.boolean :allow_password_change, :default => false
16
+
17
+ ## Rememberable
18
+ t.datetime :remember_created_at
19
+
20
+ ## Confirmable
21
+ t.string :confirmation_token
22
+ t.datetime :confirmed_at
23
+ t.datetime :confirmation_sent_at
24
+ t.string :unconfirmed_email # Only if using reconfirmable
25
+
26
+ ## Lockable
27
+ # t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
28
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
29
+ # t.datetime :locked_at
30
+
31
+ ## User Info
32
+ t.string :name
33
+ t.string :nickname
34
+ t.string :image
35
+ t.string :email
36
+
37
+ ## Tokens
38
+ t.text :tokens
39
+
40
+ t.timestamps
41
+ end
42
+
43
+ add_index :users, :email, unique: true
44
+ add_index :users, [:uid, :provider], unique: true
45
+ add_index :users, :reset_password_token, unique: true
46
+ add_index :users, :confirmation_token, unique: true
47
+ # add_index :users, :unlock_token, unique: true
48
+ end
49
+ end
@@ -76,6 +76,28 @@ class UserTest < ActiveSupport::TestCase
76
76
  end
77
77
  end
78
78
 
79
+ describe 'previous token' do
80
+ before do
81
+ @resource = create(:user, :confirmed)
82
+
83
+ @auth_headers1 = @resource.create_new_auth_token
84
+ end
85
+
86
+ test 'should properly indicate whether previous token is current' do
87
+ assert @resource.token_is_current?(@auth_headers1['access-token'], @auth_headers1['client'])
88
+ # create another token, emulating a new request
89
+ @auth_headers2 = @resource.create_new_auth_token
90
+
91
+ # should work for previous token
92
+ assert @resource.token_is_current?(@auth_headers1['access-token'], @auth_headers1['client'])
93
+ # should work for latest token as well
94
+ assert @resource.token_is_current?(@auth_headers2['access-token'], @auth_headers2['client'])
95
+
96
+ # after using latest token, previous token should not work
97
+ assert @resource.token_is_current?(@auth_headers1['access-token'], @auth_headers1['client'])
98
+ end
99
+ end
100
+
79
101
  describe 'expired tokens are destroyed on save' do
80
102
  before do
81
103
  @resource = create(:user, :confirmed)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_token_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lynn Hurley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-19 00:00:00.000000000 Z
11
+ date: 2022-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 4.2.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.2'
22
+ version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 4.2.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.2'
32
+ version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: devise
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -319,9 +319,9 @@ files:
319
319
  - test/dummy/db/migrate/20190924101113_devise_token_auth_create_confirmable_users.rb
320
320
  - test/dummy/db/schema.rb
321
321
  - test/dummy/lib/migration_database_helper.rb
322
- - test/dummy/tmp/generators/app/controllers/application_controller.rb
323
- - test/dummy/tmp/generators/app/models/azpire/v1/human_resource/user.rb
322
+ - test/dummy/tmp/generators/app/models/user.rb
324
323
  - test/dummy/tmp/generators/config/initializers/devise_token_auth.rb
324
+ - test/dummy/tmp/generators/db/migrate/20220822003050_devise_token_auth_create_users.rb
325
325
  - test/factories/users.rb
326
326
  - test/lib/devise_token_auth/blacklist_test.rb
327
327
  - test/lib/devise_token_auth/rails/custom_routes_test.rb
@@ -350,14 +350,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
350
350
  requirements:
351
351
  - - ">="
352
352
  - !ruby/object:Gem::Version
353
- version: 2.2.0
353
+ version: 2.3.0
354
354
  required_rubygems_version: !ruby/object:Gem::Requirement
355
355
  requirements:
356
356
  - - ">="
357
357
  - !ruby/object:Gem::Version
358
358
  version: '0'
359
359
  requirements: []
360
- rubygems_version: 3.1.4
360
+ rubygems_version: 3.1.6
361
361
  signing_key:
362
362
  specification_version: 4
363
363
  summary: Token based authentication for rails. Uses Devise + OmniAuth.
@@ -431,9 +431,9 @@ test_files:
431
431
  - test/dummy/db/migrate/20140715061447_devise_token_auth_create_users.rb
432
432
  - test/dummy/db/migrate/20140715061805_devise_token_auth_create_mangs.rb
433
433
  - test/dummy/db/migrate/20190924101113_devise_token_auth_create_confirmable_users.rb
434
- - test/dummy/tmp/generators/app/models/azpire/v1/human_resource/user.rb
435
- - test/dummy/tmp/generators/app/controllers/application_controller.rb
434
+ - test/dummy/tmp/generators/app/models/user.rb
436
435
  - test/dummy/tmp/generators/config/initializers/devise_token_auth.rb
436
+ - test/dummy/tmp/generators/db/migrate/20220822003050_devise_token_auth_create_users.rb
437
437
  - test/dummy/README.rdoc
438
438
  - test/models/only_email_user_test.rb
439
439
  - test/models/confirmable_user_test.rb
@@ -1,6 +0,0 @@
1
- class ApplicationController < ActionController::Base
2
- include DeviseTokenAuth::Concerns::SetUserByToken
3
- def whatever
4
- 'whatever'
5
- end
6
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Azpire::V1::HumanResource::User
4
- include Mongoid::Document
5
- include Mongoid::Timestamps
6
- include Mongoid::Locker
7
-
8
- field :locker_locked_at, type: Time
9
- field :locker_locked_until, type: Time
10
-
11
- locker locked_at_field: :locker_locked_at,
12
- locked_until_field: :locker_locked_until
13
-
14
- ## Database authenticatable
15
- field :email, type: String, default: ''
16
- field :encrypted_password, type: String, default: ''
17
-
18
- ## Recoverable
19
- field :reset_password_token, type: String
20
- field :reset_password_sent_at, type: Time
21
- field :reset_password_redirect_url, type: String
22
- field :allow_password_change, type: Boolean, default: false
23
-
24
- ## Rememberable
25
- field :remember_created_at, type: Time
26
-
27
- ## Confirmable
28
- field :confirmation_token, type: String
29
- field :confirmed_at, type: Time
30
- field :confirmation_sent_at, type: Time
31
- field :unconfirmed_email, type: String # Only if using reconfirmable
32
-
33
- ## Lockable
34
- # field :failed_attempts, type: Integer, default: 0 # Only if lock strategy is :failed_attempts
35
- # field :unlock_token, type: String # Only if unlock strategy is :email or :both
36
- # field :locked_at, type: Time
37
-
38
- ## Required
39
- field :provider, type: String
40
- field :uid, type: String, default: ''
41
-
42
- ## Tokens
43
- field :tokens, type: Hash, default: {}
44
-
45
- # Include default devise modules. Others available are:
46
- # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
47
- devise :database_authenticatable, :registerable,
48
- :recoverable, :rememberable, :validatable
49
- include DeviseTokenAuth::Concerns::User
50
-
51
- index({ email: 1 }, { name: 'email_index', unique: true, background: true })
52
- index({ reset_password_token: 1 }, { name: 'reset_password_token_index', unique: true, sparse: true, background: true })
53
- index({ confirmation_token: 1 }, { name: 'confirmation_token_index', unique: true, sparse: true, background: true })
54
- index({ uid: 1, provider: 1}, { name: 'uid_provider_index', unique: true, background: true })
55
- # index({ unlock_token: 1 }, { name: 'unlock_token_index', unique: true, sparse: true, background: true })
56
- end