devise_token_auth 1.1.4 → 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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/devise_token_auth/application_controller.rb +17 -0
  3. data/app/controllers/devise_token_auth/concerns/resource_finder.rb +14 -1
  4. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +45 -8
  5. data/app/controllers/devise_token_auth/confirmations_controller.rb +8 -4
  6. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +8 -4
  7. data/app/controllers/devise_token_auth/passwords_controller.rb +10 -2
  8. data/app/controllers/devise_token_auth/sessions_controller.rb +21 -3
  9. data/app/controllers/devise_token_auth/unlocks_controller.rb +6 -2
  10. data/app/models/devise_token_auth/concerns/active_record_support.rb +0 -2
  11. data/app/models/devise_token_auth/concerns/confirmable_support.rb +2 -1
  12. data/app/models/devise_token_auth/concerns/tokens_serialization.rb +16 -4
  13. data/app/models/devise_token_auth/concerns/user.rb +31 -15
  14. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +12 -5
  15. data/app/validators/devise_token_auth_email_validator.rb +10 -2
  16. data/app/views/devise_token_auth/omniauth_external_window.html.erb +1 -1
  17. data/config/locales/en.yml +3 -0
  18. data/config/locales/ja.yml +12 -0
  19. data/lib/devise_token_auth/blacklist.rb +5 -1
  20. data/lib/devise_token_auth/controllers/helpers.rb +5 -9
  21. data/lib/devise_token_auth/engine.rb +11 -2
  22. data/lib/devise_token_auth/rails/routes.rb +17 -12
  23. data/lib/devise_token_auth/version.rb +1 -1
  24. data/lib/generators/devise_token_auth/install_generator.rb +1 -1
  25. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +3 -0
  26. data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb.erb +1 -1
  27. data/test/controllers/demo_mang_controller_test.rb +37 -8
  28. data/test/controllers/demo_user_controller_test.rb +37 -8
  29. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +100 -19
  30. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +2 -2
  31. data/test/controllers/devise_token_auth/passwords_controller_test.rb +73 -21
  32. data/test/controllers/devise_token_auth/registrations_controller_test.rb +28 -15
  33. data/test/controllers/devise_token_auth/sessions_controller_test.rb +39 -10
  34. data/test/controllers/devise_token_auth/token_validations_controller_test.rb +41 -1
  35. data/test/controllers/devise_token_auth/unlocks_controller_test.rb +44 -5
  36. data/test/controllers/overrides/confirmations_controller_test.rb +1 -1
  37. data/test/dummy/app/views/layouts/application.html.erb +0 -2
  38. data/test/dummy/config/application.rb +0 -1
  39. data/test/dummy/config/environments/development.rb +0 -10
  40. data/test/dummy/config/environments/production.rb +0 -16
  41. data/test/dummy/db/schema.rb +5 -5
  42. data/test/dummy/tmp/generators/app/models/user.rb +11 -0
  43. data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +60 -0
  44. data/test/dummy/tmp/generators/db/migrate/20220822003050_devise_token_auth_create_users.rb +49 -0
  45. data/test/lib/devise_token_auth/blacklist_test.rb +11 -3
  46. data/test/lib/devise_token_auth/rails/custom_routes_test.rb +29 -0
  47. data/test/lib/devise_token_auth/rails/routes_test.rb +87 -0
  48. data/test/lib/generators/devise_token_auth/install_generator_test.rb +1 -1
  49. data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +1 -1
  50. data/test/models/concerns/tokens_serialization_test.rb +39 -5
  51. data/test/models/user_test.rb +22 -0
  52. data/test/test_helper.rb +35 -4
  53. metadata +16 -26
  54. data/test/dummy/config/initializers/assets.rb +0 -10
  55. data/test/dummy/tmp/generators/app/views/devise/mailer/confirmation_instructions.html.erb +0 -5
  56. data/test/dummy/tmp/generators/app/views/devise/mailer/reset_password_instructions.html.erb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 395c104491ef2762e5c41f0b35af5f2421f8d24c99cc10145231d1cb2cab2d70
4
- data.tar.gz: c637be9bc9c731f1b6218002925c0e558dbc62f2d6fb999fdd187d31d60e20c4
3
+ metadata.gz: be08ae7f01121ebe8c6b9b8fe04bcc2bdc83a2c8108452ffc986f1865278e85f
4
+ data.tar.gz: 272f45dc6f28fba16b6a523f47cbb9ecf3be9c05d4aa644ee0d0998fa5272f43
5
5
  SHA512:
6
- metadata.gz: a1a184d38110e9157c941f1b5e2b8a0cdd7901702f12c7316a4ffba2b5af239455bddc9c288d8fbbd2c909aadfdfe388283c16abcce1814abf595cfe853e3c51
7
- data.tar.gz: 7ac1939d622a50f46e9ce3943826b85e67e9457178bba79326c5656f4c8fbacc5205b44828aa4935be4c2c4dc713f68ab1d44b8d7485ced86fa90416769e1431
6
+ metadata.gz: cc54c90eee4fdf43e6d9b72ca905fc58e4338f1310b289bbede651978bd4f407556392d3d4c61cfaeabf6d4fba179768e02ec9c00bc245b33ba629972676c676
7
+ data.tar.gz: 00e139ae99fe395580ef8f846cca46516792b68cda0e0201e551e90ca9c70679fcf9519d3682ea82095588e78a93bb2def3db2bebe670a0e96f933e7087fee4b
@@ -75,5 +75,22 @@ module DeviseTokenAuth
75
75
  response = response.merge(data) if data
76
76
  render json: response, status: status
77
77
  end
78
+
79
+ def success_message(name, email)
80
+ if Devise.paranoid
81
+ I18n.t("devise_token_auth.#{name}.sended_paranoid")
82
+ else
83
+ I18n.t("devise_token_auth.#{name}.sended", email: email)
84
+ end
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
78
95
  end
79
96
  end
@@ -20,7 +20,7 @@ module DeviseTokenAuth::Concerns::ResourceFinder
20
20
  end
21
21
 
22
22
  def find_resource(field, value)
23
- @resource = if resource_class.try(:connection_config).try(:[], :adapter).try(:include?, 'mysql')
23
+ @resource = if database_adapter&.include?('mysql')
24
24
  # fix for mysql default case insensitivity
25
25
  resource_class.where("BINARY #{field} = ? AND provider= ?", value, provider).first
26
26
  else
@@ -28,6 +28,19 @@ module DeviseTokenAuth::Concerns::ResourceFinder
28
28
  end
29
29
  end
30
30
 
31
+ def database_adapter
32
+ @database_adapter ||= begin
33
+ rails_version = [Rails::VERSION::MAJOR, Rails::VERSION::MINOR].join(".")
34
+
35
+ adapter =
36
+ if rails_version >= "6.1"
37
+ resource_class.try(:connection_db_config)&.try(:adapter)
38
+ else
39
+ resource_class.try(:connection_config)&.try(:[], :adapter)
40
+ end
41
+ end
42
+ end
43
+
31
44
  def resource_class(m = nil)
32
45
  mapping = if m
33
46
  Devise.mappings[m]
@@ -17,7 +17,7 @@ module DeviseTokenAuth::Concerns::SetUserByToken
17
17
  @used_auth_by_token = true
18
18
 
19
19
  # initialize instance variables
20
- @token = DeviseTokenAuth::TokenFactory.new
20
+ @token ||= DeviseTokenAuth::TokenFactory.new
21
21
  @resource ||= nil
22
22
  @is_batch_request ||= nil
23
23
  end
@@ -32,21 +32,36 @@ 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])
42
+
43
+ # gets values from cookie if configured and present
44
+ parsed_auth_cookie = {}
45
+ if DeviseTokenAuth.cookie_enabled
46
+ auth_cookie = request.cookies[DeviseTokenAuth.cookie_name]
47
+ if auth_cookie.present?
48
+ parsed_auth_cookie = JSON.parse(auth_cookie)
49
+ end
50
+ end
37
51
 
38
52
  # parse header for values necessary for authentication
39
- uid = request.headers[uid_name] || params[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]
40
55
  @token = DeviseTokenAuth::TokenFactory.new unless @token
41
- @token.token ||= request.headers[access_token_name] || params[access_token_name]
42
- @token.client ||= request.headers[client_name] || params[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]
43
58
 
44
59
  # client isn't required, set to 'default' if absent
45
60
  @token.client ||= 'default'
46
61
 
47
62
  # check for an existing user, authenticated via warden/devise, if enabled
48
63
  if DeviseTokenAuth.enable_standard_devise_support
49
- devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
64
+ devise_warden_user = warden.user(mapping)
50
65
  if devise_warden_user && devise_warden_user.tokens[@token.client].nil?
51
66
  @used_auth_by_token = false
52
67
  @resource = devise_warden_user
@@ -66,7 +81,7 @@ module DeviseTokenAuth::Concerns::SetUserByToken
66
81
  end
67
82
 
68
83
  # mitigate timing attacks by finding by uid instead of auth token
69
- 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))
70
85
  scope = rc.to_s.underscore.to_sym
71
86
 
72
87
  if user && user.valid_token?(@token.token, @token.client)
@@ -101,9 +116,13 @@ module DeviseTokenAuth::Concerns::SetUserByToken
101
116
  # update the response header
102
117
  response.headers.merge!(auth_header)
103
118
 
119
+ # set a server cookie if configured
120
+ if DeviseTokenAuth.cookie_enabled
121
+ set_cookie(auth_header)
122
+ end
104
123
  else
105
124
  unless @resource.reload.valid?
106
- @resource = resource_class.find(@resource.to_param) # errors remain after reload
125
+ @resource = @resource.class.find(@resource.to_param) # errors remain after reload
107
126
  # if we left the model in a bad state, something is wrong in our app
108
127
  unless @resource.valid?
109
128
  raise DeviseTokenAuth::Errors::InvalidModel, "Cannot set auth token in invalid model. Errors: #{@resource.errors.full_messages}"
@@ -115,6 +134,13 @@ module DeviseTokenAuth::Concerns::SetUserByToken
115
134
 
116
135
  private
117
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
+
118
144
  def refresh_headers
119
145
  # Lock the user record during any auth_header updates to ensure
120
146
  # we don't have write contention from multiple threads
@@ -123,11 +149,22 @@ module DeviseTokenAuth::Concerns::SetUserByToken
123
149
  # cleared by sign out in the meantime
124
150
  return if @used_auth_by_token && @resource.tokens[@token.client].nil?
125
151
 
152
+ _auth_header_from_batch_request = auth_header_from_batch_request
153
+
126
154
  # update the response header
127
- response.headers.merge!(auth_header_from_batch_request)
155
+ response.headers.merge!(_auth_header_from_batch_request)
156
+
157
+ # set a server cookie if configured
158
+ if DeviseTokenAuth.cookie_enabled
159
+ set_cookie(_auth_header_from_batch_request)
160
+ end
128
161
  end # end lock
129
162
  end
130
163
 
164
+ def set_cookie(auth_header)
165
+ cookies[DeviseTokenAuth.cookie_name] = DeviseTokenAuth.cookie_attributes.merge(value: auth_header.to_json)
166
+ end
167
+
131
168
  def is_batch_request?(user, client)
132
169
  !params[:unbatch] &&
133
170
  user.tokens[client] &&
@@ -55,13 +55,17 @@ module DeviseTokenAuth
55
55
 
56
56
  def render_create_success
57
57
  render json: {
58
- success: true,
59
- message: I18n.t('devise_token_auth.confirmations.sended', email: @email)
60
- }
58
+ success: true,
59
+ message: success_message('confirmations', @email)
60
+ }
61
61
  end
62
62
 
63
63
  def render_not_found_error
64
- render_error(404, I18n.t('devise_token_auth.confirmations.user_not_found', email: @email))
64
+ if Devise.paranoid
65
+ render_create_success
66
+ else
67
+ render_error(404, I18n.t('devise_token_auth.confirmations.user_not_found', email: @email))
68
+ end
65
69
  end
66
70
 
67
71
  private
@@ -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,
@@ -128,7 +132,7 @@ module DeviseTokenAuth
128
132
  def render_create_success
129
133
  render json: {
130
134
  success: true,
131
- message: I18n.t('devise_token_auth.passwords.sended', email: @email)
135
+ message: success_message('passwords', @email)
132
136
  }
133
137
  end
134
138
 
@@ -181,7 +185,11 @@ module DeviseTokenAuth
181
185
  end
182
186
 
183
187
  def render_not_found_error
184
- render_error(404, I18n.t('devise_token_auth.passwords.user_not_found', email: @email))
188
+ if Devise.paranoid
189
+ render_create_success
190
+ else
191
+ render_error(404, I18n.t('devise_token_auth.passwords.user_not_found', email: @email))
192
+ end
185
193
  end
186
194
 
187
195
  def validate_redirect_url_param
@@ -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
 
@@ -48,13 +48,19 @@ module DeviseTokenAuth
48
48
  def destroy
49
49
  # remove auth instance variables so that after_action does not run
50
50
  user = remove_instance_variable(:@resource) if @resource
51
- client = @token.client if @token.client
51
+ client = @token.client
52
52
  @token.clear!
53
53
 
54
54
  if user && client && user.tokens[client]
55
55
  user.tokens.delete(client)
56
56
  user.save!
57
57
 
58
+ if DeviseTokenAuth.cookie_enabled
59
+ # If a cookie is set with a domain specified then it must be deleted with that domain specified
60
+ # See https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html
61
+ cookies.delete(DeviseTokenAuth.cookie_name, domain: DeviseTokenAuth.cookie_attributes[:domain])
62
+ end
63
+
58
64
  yield user if block_given?
59
65
 
60
66
  render_destroy_success
@@ -127,5 +133,17 @@ module DeviseTokenAuth
127
133
  def resource_params
128
134
  params.permit(*params_for_resource(:sign_in))
129
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
130
148
  end
131
149
  end
@@ -63,7 +63,7 @@ module DeviseTokenAuth
63
63
  def render_create_success
64
64
  render json: {
65
65
  success: true,
66
- message: I18n.t('devise_token_auth.unlocks.sended', email: @email)
66
+ message: success_message('unlocks', @email)
67
67
  }
68
68
  end
69
69
 
@@ -79,7 +79,11 @@ module DeviseTokenAuth
79
79
  end
80
80
 
81
81
  def render_not_found_error
82
- render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
82
+ if Devise.paranoid
83
+ render_create_success
84
+ else
85
+ render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
86
+ end
83
87
  end
84
88
 
85
89
  def resource_params
@@ -1,5 +1,3 @@
1
- require_relative 'tokens_serialization'
2
-
3
1
  module DeviseTokenAuth::Concerns::ActiveRecordSupport
4
2
  extend ActiveSupport::Concern
5
3
 
@@ -18,7 +18,8 @@ module DeviseTokenAuth::Concerns::ConfirmableSupport
18
18
  protected
19
19
 
20
20
  def email_value_in_database
21
- if Devise.rails51? && respond_to?(:email_in_database)
21
+ rails51 = Rails.gem_version >= Gem::Version.new("5.1.x")
22
+ if rails51 && respond_to?(:email_in_database)
22
23
  email_in_database
23
24
  else
24
25
  email_was
@@ -1,12 +1,14 @@
1
1
  module DeviseTokenAuth::Concerns::TokensSerialization
2
+ extend self
2
3
  # Serialization hash to json
3
- def self.dump(object)
4
- object.each_value(&:compact!) unless object.nil?
5
- JSON.generate(object)
4
+ def dump(object)
5
+ JSON.generate(object && object.transform_values do |token|
6
+ serialize_updated_at(token).compact
7
+ end.compact)
6
8
  end
7
9
 
8
10
  # Deserialization json to hash
9
- def self.load(json)
11
+ def load(json)
10
12
  case json
11
13
  when String
12
14
  JSON.parse(json)
@@ -16,4 +18,14 @@ module DeviseTokenAuth::Concerns::TokensSerialization
16
18
  json
17
19
  end
18
20
  end
21
+
22
+ private
23
+
24
+ def serialize_updated_at(token)
25
+ updated_at_key = ['updated_at', :updated_at].find(&token.method(:[]))
26
+
27
+ return token unless token[updated_at_key].respond_to?(:iso8601)
28
+
29
+ token.merge updated_at_key => token[updated_at_key].iso8601
30
+ end
19
31
  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,8 +171,9 @@ module DeviseTokenAuth::Concerns::User
157
171
 
158
172
  token = create_token(
159
173
  client: client,
160
- last_token: tokens.fetch(client, {})['token'],
161
- updated_at: now.to_s(:rfc822)
174
+ previous_token: tokens.fetch(client, {})['token'],
175
+ last_token: tokens.fetch(client, {})['previous_token'],
176
+ updated_at: now
162
177
  )
163
178
 
164
179
  update_auth_header(token.token, token.client)
@@ -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')
@@ -194,7 +215,7 @@ module DeviseTokenAuth::Concerns::User
194
215
  end
195
216
 
196
217
  def extend_batch_buffer(token, client)
197
- tokens[client]['updated_at'] = Time.zone.now.to_s(:rfc822)
218
+ tokens[client]['updated_at'] = Time.zone.now
198
219
  update_auth_header(token, client)
199
220
  end
200
221
 
@@ -218,13 +239,8 @@ module DeviseTokenAuth::Concerns::User
218
239
  end
219
240
 
220
241
  def should_remove_tokens_after_password_reset?
221
- if Rails::VERSION::MAJOR <= 5 ||defined?('Mongoid')
222
- encrypted_password_changed? &&
223
- DeviseTokenAuth.remove_tokens_after_password_reset
224
- else
225
- saved_change_to_attribute?(:encrypted_password) &&
226
- DeviseTokenAuth.remove_tokens_after_password_reset
227
- end
242
+ DeviseTokenAuth.remove_tokens_after_password_reset &&
243
+ (respond_to?(:encrypted_password_changed?) && encrypted_password_changed?)
228
244
  end
229
245
 
230
246
  def remove_tokens_after_password_reset
@@ -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,11 +18,18 @@ 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
24
28
 
25
29
  def sync_uid
26
- self.uid = email if email_provider?
30
+ unless self.new_record?
31
+ return if devise_modules.include?(:confirmable) && !@bypass_confirmation_postpone && postpone_email_change?
32
+ end
33
+ self.uid = email if uid_and_provider_defined? && email_provider?
27
34
  end
28
35
  end
@@ -1,9 +1,17 @@
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
6
- record.errors[attribute] << email_invalid_message
13
+ unless DeviseTokenAuthEmailValidator.validate?(value)
14
+ record.errors.add(attribute, email_invalid_message)
7
15
  end
8
16
  end
9
17
 
@@ -15,7 +15,7 @@
15
15
  Cordova / PhoneGap)
16
16
  */
17
17
 
18
- var data = JSON.parse(decodeURIComponent('<%= URI::escape( @data.to_json ) %>'));
18
+ var data = JSON.parse(decodeURIComponent('<%= ERB::Util.url_encode( @data.to_json ) %>'));
19
19
 
20
20
  window.addEventListener("message", function(ev) {
21
21
  if (ev.data === "requestCredentials") {
@@ -21,6 +21,7 @@ en:
21
21
  missing_redirect_url: "Missing redirect URL."
22
22
  not_allowed_redirect_url: "Redirect to '%{redirect_url}' not allowed."
23
23
  sended: "An email has been sent to '%{email}' containing instructions for resetting your password."
24
+ sended_paranoid: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
24
25
  user_not_found: "Unable to find user with email '%{email}'."
25
26
  password_not_required: "This account does not require a password. Sign in using your '%{provider}' account instead."
26
27
  missing_passwords: "You must fill out the fields labeled 'Password' and 'Password confirmation'."
@@ -28,9 +29,11 @@ en:
28
29
  unlocks:
29
30
  missing_email: "You must provide an email address."
30
31
  sended: "An email has been sent to '%{email}' containing instructions for unlocking your account."
32
+ sended_paranoid: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
31
33
  user_not_found: "Unable to find user with email '%{email}'."
32
34
  confirmations:
33
35
  sended: "An email has been sent to '%{email}' containing instructions for confirming your account."
36
+ sended_paranoid: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
34
37
  user_not_found: "Unable to find user with email '%{email}'."
35
38
  missing_email: "You must provide an email address."
36
39
 
@@ -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: "リクエストボディに適切なアカウント新規登録データを送信してください。"
@@ -1,2 +1,6 @@
1
1
  # don't serialize tokens
2
- Devise::Models::Authenticatable::BLACKLIST_FOR_SERIALIZATION << :tokens
2
+ if defined? Devise::Models::Authenticatable::UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
3
+ Devise::Models::Authenticatable::UNSAFE_ATTRIBUTES_FOR_SERIALIZATION << :tokens
4
+ else
5
+ Devise::Models::Authenticatable::BLACKLIST_FOR_SERIALIZATION << :tokens
6
+ end
@@ -34,12 +34,6 @@ module DeviseTokenAuth
34
34
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
35
35
  def authenticate_#{group_name}!(favourite=nil, opts={})
36
36
  unless #{group_name}_signed_in?
37
- mappings = #{mappings}
38
- mappings.unshift mappings.delete(favourite.to_sym) if favourite
39
- mappings.each do |mapping|
40
- set_user_by_token(mapping)
41
- end
42
-
43
37
  unless current_#{group_name}
44
38
  render_authenticate_error
45
39
  end
@@ -47,12 +41,14 @@ module DeviseTokenAuth
47
41
  end
48
42
 
49
43
  def #{group_name}_signed_in?
50
- #{mappings}.any? do |mapping|
51
- set_user_by_token(mapping)
52
- end
44
+ !!current_#{group_name}
53
45
  end
54
46
 
55
47
  def current_#{group_name}(favourite=nil)
48
+ @current_#{group_name} ||= set_group_user_by_token(favourite)
49
+ end
50
+
51
+ def set_group_user_by_token(favourite)
56
52
  mappings = #{mappings}
57
53
  mappings.unshift mappings.delete(favourite.to_sym) if favourite
58
54
  mappings.each do |mapping|