devise_token_auth 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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