devise_token_auth 1.0.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 (130) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +4 -2
  3. data/app/controllers/devise_token_auth/application_controller.rb +19 -3
  4. data/app/controllers/devise_token_auth/concerns/resource_finder.rb +23 -11
  5. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +78 -57
  6. data/app/controllers/devise_token_auth/confirmations_controller.rb +67 -20
  7. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +82 -30
  8. data/app/controllers/devise_token_auth/passwords_controller.rb +53 -31
  9. data/app/controllers/devise_token_auth/registrations_controller.rb +33 -40
  10. data/app/controllers/devise_token_auth/sessions_controller.rb +24 -6
  11. data/app/controllers/devise_token_auth/unlocks_controller.rb +10 -6
  12. data/app/models/devise_token_auth/concerns/active_record_support.rb +14 -0
  13. data/app/models/devise_token_auth/concerns/confirmable_support.rb +28 -0
  14. data/app/models/devise_token_auth/concerns/mongoid_support.rb +19 -0
  15. data/app/models/devise_token_auth/concerns/tokens_serialization.rb +31 -0
  16. data/app/models/devise_token_auth/concerns/user.rb +77 -80
  17. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +12 -5
  18. data/app/validators/{email_validator.rb → devise_token_auth_email_validator.rb} +11 -3
  19. data/app/views/devise_token_auth/omniauth_external_window.html.erb +1 -1
  20. data/config/locales/da-DK.yml +2 -0
  21. data/config/locales/de.yml +2 -0
  22. data/config/locales/en.yml +10 -0
  23. data/config/locales/es.yml +2 -0
  24. data/config/locales/fr.yml +2 -0
  25. data/config/locales/he.yml +52 -0
  26. data/config/locales/it.yml +2 -0
  27. data/config/locales/ja.yml +16 -2
  28. data/config/locales/ko.yml +51 -0
  29. data/config/locales/nl.yml +2 -0
  30. data/config/locales/pl.yml +6 -3
  31. data/config/locales/pt-BR.yml +2 -0
  32. data/config/locales/pt.yml +6 -3
  33. data/config/locales/ro.yml +2 -0
  34. data/config/locales/ru.yml +2 -0
  35. data/config/locales/sq.yml +2 -0
  36. data/config/locales/sv.yml +2 -0
  37. data/config/locales/uk.yml +2 -0
  38. data/config/locales/vi.yml +2 -0
  39. data/config/locales/zh-CN.yml +2 -0
  40. data/config/locales/zh-HK.yml +2 -0
  41. data/config/locales/zh-TW.yml +2 -0
  42. data/lib/devise_token_auth/blacklist.rb +6 -0
  43. data/lib/devise_token_auth/controllers/helpers.rb +5 -9
  44. data/lib/devise_token_auth/engine.rb +17 -2
  45. data/lib/devise_token_auth/rails/routes.rb +18 -13
  46. data/lib/devise_token_auth/token_factory.rb +126 -0
  47. data/lib/devise_token_auth/url.rb +3 -0
  48. data/lib/devise_token_auth/version.rb +1 -1
  49. data/lib/devise_token_auth.rb +6 -3
  50. data/lib/generators/devise_token_auth/USAGE +1 -1
  51. data/lib/generators/devise_token_auth/install_generator.rb +7 -91
  52. data/lib/generators/devise_token_auth/install_generator_helpers.rb +98 -0
  53. data/lib/generators/devise_token_auth/install_mongoid_generator.rb +46 -0
  54. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +13 -0
  55. data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb.erb +1 -8
  56. data/lib/generators/devise_token_auth/templates/user.rb.erb +2 -2
  57. data/lib/generators/devise_token_auth/templates/user_mongoid.rb.erb +56 -0
  58. data/test/controllers/custom/custom_confirmations_controller_test.rb +1 -1
  59. data/test/controllers/demo_mang_controller_test.rb +37 -8
  60. data/test/controllers/demo_user_controller_test.rb +39 -10
  61. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +163 -18
  62. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +110 -43
  63. data/test/controllers/devise_token_auth/passwords_controller_test.rb +299 -122
  64. data/test/controllers/devise_token_auth/registrations_controller_test.rb +54 -14
  65. data/test/controllers/devise_token_auth/sessions_controller_test.rb +31 -40
  66. data/test/controllers/devise_token_auth/token_validations_controller_test.rb +43 -2
  67. data/test/controllers/devise_token_auth/unlocks_controller_test.rb +44 -5
  68. data/test/controllers/overrides/confirmations_controller_test.rb +1 -1
  69. data/test/dummy/app/active_record/confirmable_user.rb +11 -0
  70. data/test/dummy/app/{models → active_record}/scoped_user.rb +2 -2
  71. data/test/dummy/app/{models → active_record}/unconfirmable_user.rb +1 -2
  72. data/test/dummy/app/{models → active_record}/unregisterable_user.rb +3 -3
  73. data/test/dummy/app/active_record/user.rb +6 -0
  74. data/test/dummy/app/controllers/overrides/confirmations_controller.rb +3 -3
  75. data/test/dummy/app/controllers/overrides/passwords_controller.rb +3 -3
  76. data/test/dummy/app/controllers/overrides/registrations_controller.rb +1 -1
  77. data/test/dummy/app/controllers/overrides/sessions_controller.rb +2 -2
  78. data/test/dummy/app/models/{user.rb → concerns/favorite_color.rb} +7 -8
  79. data/test/dummy/app/mongoid/confirmable_user.rb +52 -0
  80. data/test/dummy/app/mongoid/lockable_user.rb +38 -0
  81. data/test/dummy/app/mongoid/mang.rb +46 -0
  82. data/test/dummy/app/mongoid/only_email_user.rb +33 -0
  83. data/test/dummy/app/mongoid/scoped_user.rb +50 -0
  84. data/test/dummy/app/mongoid/unconfirmable_user.rb +44 -0
  85. data/test/dummy/app/mongoid/unregisterable_user.rb +47 -0
  86. data/test/dummy/app/mongoid/user.rb +49 -0
  87. data/test/dummy/app/views/layouts/application.html.erb +0 -2
  88. data/test/dummy/config/application.rb +22 -1
  89. data/test/dummy/config/boot.rb +4 -0
  90. data/test/dummy/config/environments/development.rb +0 -10
  91. data/test/dummy/config/environments/production.rb +0 -16
  92. data/test/dummy/config/initializers/devise.rb +285 -0
  93. data/test/dummy/config/initializers/devise_token_auth.rb +35 -4
  94. data/test/dummy/config/initializers/figaro.rb +1 -1
  95. data/test/dummy/config/initializers/omniauth.rb +1 -0
  96. data/test/dummy/config/routes.rb +2 -0
  97. data/test/dummy/db/migrate/20140715061447_devise_token_auth_create_users.rb +0 -7
  98. data/test/dummy/db/migrate/20140715061805_devise_token_auth_create_mangs.rb +0 -7
  99. data/test/dummy/db/migrate/20141222035835_devise_token_auth_create_only_email_users.rb +0 -7
  100. data/test/dummy/db/migrate/20141222053502_devise_token_auth_create_unregisterable_users.rb +0 -7
  101. data/test/dummy/db/migrate/20150708104536_devise_token_auth_create_unconfirmable_users.rb +0 -7
  102. data/test/dummy/db/migrate/20160103235141_devise_token_auth_create_scoped_users.rb +0 -7
  103. data/test/dummy/db/migrate/20160629184441_devise_token_auth_create_lockable_users.rb +0 -7
  104. data/test/dummy/db/migrate/20190924101113_devise_token_auth_create_confirmable_users.rb +49 -0
  105. data/test/dummy/db/schema.rb +31 -33
  106. data/test/dummy/tmp/generators/app/models/user.rb +11 -0
  107. data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +60 -0
  108. data/test/dummy/tmp/generators/db/migrate/20220822003050_devise_token_auth_create_users.rb +49 -0
  109. data/test/factories/users.rb +3 -2
  110. data/test/lib/devise_token_auth/blacklist_test.rb +19 -0
  111. data/test/lib/devise_token_auth/rails/custom_routes_test.rb +29 -0
  112. data/test/lib/devise_token_auth/rails/routes_test.rb +87 -0
  113. data/test/lib/devise_token_auth/token_factory_test.rb +191 -0
  114. data/test/lib/devise_token_auth/url_test.rb +2 -2
  115. data/test/lib/generators/devise_token_auth/install_generator_test.rb +51 -31
  116. data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +51 -31
  117. data/test/models/concerns/mongoid_support_test.rb +31 -0
  118. data/test/models/concerns/tokens_serialization_test.rb +104 -0
  119. data/test/models/confirmable_user_test.rb +35 -0
  120. data/test/models/only_email_user_test.rb +0 -8
  121. data/test/models/user_test.rb +13 -23
  122. data/test/test_helper.rb +45 -4
  123. metadata +126 -33
  124. data/config/initializers/devise.rb +0 -198
  125. data/test/dummy/config/initializers/assets.rb +0 -10
  126. data/test/dummy/tmp/generators/app/views/devise/mailer/confirmation_instructions.html.erb +0 -5
  127. data/test/dummy/tmp/generators/app/views/devise/mailer/reset_password_instructions.html.erb +0 -8
  128. /data/test/dummy/app/{models → active_record}/lockable_user.rb +0 -0
  129. /data/test/dummy/app/{models → active_record}/mang.rb +0 -0
  130. /data/test/dummy/app/{models → active_record}/only_email_user.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 916c84285bb6b805d3a70a5adc8e86d83efb348e
4
- data.tar.gz: 38deed793c1466de04cd75ab4861627a5eb18447
2
+ SHA256:
3
+ metadata.gz: be08ae7f01121ebe8c6b9b8fe04bcc2bdc83a2c8108452ffc986f1865278e85f
4
+ data.tar.gz: 272f45dc6f28fba16b6a523f47cbb9ecf3be9c05d4aa644ee0d0998fa5272f43
5
5
  SHA512:
6
- metadata.gz: 4b631710faf5afc08a2f0f0fc89a96e63c7fe07d0c7a985a19d3b5c0a18801401afe67525aa6e0a078195ca4d24c58b6a6651da667c2a7a7f40723d4974dc850
7
- data.tar.gz: 8707ad62656749fc88de275533456b93c17545f854e40f03920365c2ba9a11c170af44787118c4d8bc59ef593c5535ce8e09c512338c0de8af860c7e2500746f
6
+ metadata.gz: cc54c90eee4fdf43e6d9b72ca905fc58e4338f1310b289bbede651978bd4f407556392d3d4c61cfaeabf6d4fba179768e02ec9c00bc245b33ba629972676c676
7
+ data.tar.gz: 00e139ae99fe395580ef8f846cca46516792b68cda0e0201e551e90ca9c70679fcf9519d3682ea82095588e78a93bb2def3db2bebe670a0e96f933e7087fee4b
data/README.md CHANGED
@@ -19,7 +19,7 @@ Also, it maintains a session for each client/device, so you can have as many ses
19
19
 
20
20
  * Seamless integration with:
21
21
  * [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) for [AngularJS](https://github.com/angular/angular.js)
22
- * [Angular2-Token](https://github.com/neroniaky/angular2-token) for [Angular2](https://github.com/angular/angular)
22
+ * [Angular-Token](https://github.com/neroniaky/angular-token) for [Angular](https://github.com/angular/angular)
23
23
  * [redux-token-auth](https://github.com/kylecorbelli/redux-token-auth) for [React with Redux](https://github.com/reactjs/react-redux)
24
24
  * [jToker](https://github.com/lynndylanhurley/j-toker) for [jQuery](https://jquery.com/)
25
25
  * Oauth2 authentication using [OmniAuth](https://github.com/intridea/omniauth).
@@ -65,11 +65,13 @@ Please read the [issue template](https://github.com/lynndylanhurley/devise_token
65
65
 
66
66
  See our [Contribution Guidelines](https://github.com/lynndylanhurley/devise_token_auth/blob/master/.github/CONTRIBUTING.md). Feel free to submit pull requests, review pull requests, or review open issues. If you'd like to get in contact, [Zach Feldman](https://github.com/zachfeldman) has been wrangling this effort, you can reach him with his name @gmail. Further discussion of this in [this issue](https://github.com/lynndylanhurley/devise_token_auth/issues/969).
67
67
 
68
+ We have some bounties for some issues, [check them out](https://github.com/lynndylanhurley/devise_token_auth/issues?q=is%3Aopen+is%3Aissue+label%3Abounty)!
69
+
68
70
  ## Live Demos
69
71
 
70
72
  [Here is a demo](http://ng-token-auth-demo.herokuapp.com/) of this app running with the [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) module and [AngularJS](https://github.com/angular/angular.js).
71
73
 
72
- [Here is a demo](https://angular2-token.herokuapp.com) of this app running with the [Angular2-Token](https://github.com/neroniaky/angular2-token) service and [Angular2](https://github.com/angular/angular).
74
+ [Here is a demo](https://stackblitz.com/github/neroniaky/angular-token) of this app running with the [Angular-Token](https://github.com/neroniaky/angular-token) service and [Angular](https://github.com/angular/angular).
73
75
 
74
76
  [Here is a demo](https://j-toker-demo.herokuapp.com/) of this app using the [jToker](https://github.com/lynndylanhurley/j-toker) plugin and [React](http://facebook.github.io/react/).
75
77
 
@@ -3,7 +3,6 @@
3
3
  module DeviseTokenAuth
4
4
  class ApplicationController < DeviseController
5
5
  include DeviseTokenAuth::Concerns::SetUserByToken
6
- include DeviseTokenAuth::Concerns::ResourceFinder
7
6
 
8
7
  def resource_data(opts = {})
9
8
  response_data = opts[:resource_json] || @resource.as_json
@@ -17,8 +16,8 @@ module DeviseTokenAuth
17
16
 
18
17
  protected
19
18
 
20
- def blacklisted_redirect_url?
21
- DeviseTokenAuth.redirect_whitelist && !DeviseTokenAuth::Url.whitelisted?(@redirect_url)
19
+ def blacklisted_redirect_url?(redirect_url)
20
+ DeviseTokenAuth.redirect_whitelist && !DeviseTokenAuth::Url.whitelisted?(redirect_url)
22
21
  end
23
22
 
24
23
  def build_redirect_headers(access_token, client, redirect_header_options = {})
@@ -76,5 +75,22 @@ module DeviseTokenAuth
76
75
  response = response.merge(data) if data
77
76
  render json: response, status: status
78
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
79
95
  end
80
96
  end
@@ -20,21 +20,33 @@ module DeviseTokenAuth::Concerns::ResourceFinder
20
20
  end
21
21
 
22
22
  def find_resource(field, value)
23
- # fix for mysql default case insensitivity
24
- q = "#{field.to_s} = ? AND provider='#{provider.to_s}'"
25
- if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'mysql'
26
- q = 'BINARY ' + q
27
- end
23
+ @resource = if database_adapter&.include?('mysql')
24
+ # fix for mysql default case insensitivity
25
+ resource_class.where("BINARY #{field} = ? AND provider= ?", value, provider).first
26
+ else
27
+ resource_class.dta_find_by(field => value, 'provider' => provider)
28
+ end
29
+ end
30
+
31
+ def database_adapter
32
+ @database_adapter ||= begin
33
+ rails_version = [Rails::VERSION::MAJOR, Rails::VERSION::MINOR].join(".")
28
34
 
29
- @resource = resource_class.where(q, value).first
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
30
42
  end
31
43
 
32
44
  def resource_class(m = nil)
33
- if m
34
- mapping = Devise.mappings[m]
35
- else
36
- mapping = Devise.mappings[resource_name] || Devise.mappings.values.first
37
- end
45
+ mapping = if m
46
+ Devise.mappings[m]
47
+ else
48
+ Devise.mappings[resource_name] || Devise.mappings.values.first
49
+ end
38
50
 
39
51
  mapping.to
40
52
  end
@@ -17,24 +17,11 @@ module DeviseTokenAuth::Concerns::SetUserByToken
17
17
  @used_auth_by_token = true
18
18
 
19
19
  # initialize instance variables
20
- @client_id ||= nil
20
+ @token ||= DeviseTokenAuth::TokenFactory.new
21
21
  @resource ||= nil
22
- @token ||= nil
23
22
  @is_batch_request ||= nil
24
23
  end
25
24
 
26
- def ensure_pristine_resource
27
- if @resource.changed?
28
- # Stash pending changes in the resource before reloading.
29
- changes = @resource.changes
30
- @resource.reload
31
- end
32
- yield
33
- ensure
34
- # Reapply pending changes
35
- @resource.assign_attributes(changes) if changes
36
- end
37
-
38
25
  # user auth
39
26
  def set_user_by_token(mapping = nil)
40
27
  # determine target authentication class
@@ -45,21 +32,37 @@ module DeviseTokenAuth::Concerns::SetUserByToken
45
32
 
46
33
  # gets the headers names, which was set in the initialize file
47
34
  uid_name = DeviseTokenAuth.headers_names[:'uid']
35
+ other_uid_name = DeviseTokenAuth.other_uid && DeviseTokenAuth.headers_names[DeviseTokenAuth.other_uid.to_sym]
48
36
  access_token_name = DeviseTokenAuth.headers_names[:'access-token']
49
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
50
51
 
51
52
  # parse header for values necessary for authentication
52
- uid = request.headers[uid_name] || params[uid_name]
53
- @token ||= request.headers[access_token_name] || params[access_token_name]
54
- @client_id ||= request.headers[client_name] || params[client_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]
55
+ @token = DeviseTokenAuth::TokenFactory.new unless @token
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]
55
58
 
56
- # client_id isn't required, set to 'default' if absent
57
- @client_id ||= 'default'
59
+ # client isn't required, set to 'default' if absent
60
+ @token.client ||= 'default'
58
61
 
59
62
  # check for an existing user, authenticated via warden/devise, if enabled
60
63
  if DeviseTokenAuth.enable_standard_devise_support
61
- devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
62
- if devise_warden_user && devise_warden_user.tokens[@client_id].nil?
64
+ devise_warden_user = warden.user(mapping)
65
+ if devise_warden_user && devise_warden_user.tokens[@token.client].nil?
63
66
  @used_auth_by_token = false
64
67
  @resource = devise_warden_user
65
68
  # REVIEW: The following line _should_ be safe to remove;
@@ -71,53 +74,55 @@ module DeviseTokenAuth::Concerns::SetUserByToken
71
74
  # user has already been found and authenticated
72
75
  return @resource if @resource && @resource.is_a?(rc)
73
76
 
74
- # ensure we clear the client_id
75
- unless @token
76
- @client_id = nil
77
+ # ensure we clear the client
78
+ unless @token.present?
79
+ @token.client = nil
77
80
  return
78
81
  end
79
82
 
80
- return false unless @token
81
-
82
83
  # mitigate timing attacks by finding by uid instead of auth token
83
- user = uid && rc.find_by(uid: uid)
84
+ user = (uid && rc.dta_find_by(uid: uid)) || (other_uid && rc.dta_find_by("#{DeviseTokenAuth.other_uid}": other_uid))
85
+ scope = rc.to_s.underscore.to_sym
84
86
 
85
- if user && user.valid_token?(@token, @client_id)
87
+ if user && user.valid_token?(@token.token, @token.client)
86
88
  # sign_in with bypass: true will be deprecated in the next version of Devise
87
89
  if respond_to?(:bypass_sign_in) && DeviseTokenAuth.bypass_sign_in
88
- bypass_sign_in(user, scope: :user)
90
+ bypass_sign_in(user, scope: scope)
89
91
  else
90
- sign_in(:user, user, store: false, event: :fetch, bypass: DeviseTokenAuth.bypass_sign_in)
92
+ sign_in(scope, user, store: false, event: :fetch, bypass: DeviseTokenAuth.bypass_sign_in)
91
93
  end
92
94
  return @resource = user
93
95
  else
94
96
  # zero all values previously set values
95
- @client_id = nil
97
+ @token.client = nil
96
98
  return @resource = nil
97
99
  end
98
100
  end
99
101
 
100
102
  def update_auth_header
101
103
  # cannot save object if model has invalid params
104
+ return unless @resource && @token.client
102
105
 
103
- return unless @resource && @client_id
104
-
105
- # Generate new client_id with existing authentication
106
- @client_id = nil unless @used_auth_by_token
106
+ # Generate new client with existing authentication
107
+ @token.client = nil unless @used_auth_by_token
107
108
 
108
109
  if @used_auth_by_token && !DeviseTokenAuth.change_headers_on_each_request
109
110
  # should not append auth header if @resource related token was
110
111
  # cleared by sign out in the meantime
111
- return if @resource.reload.tokens[@client_id].nil?
112
+ return if @resource.reload.tokens[@token.client].nil?
112
113
 
113
- auth_header = @resource.build_auth_header(@token, @client_id)
114
+ auth_header = @resource.build_auth_header(@token.token, @token.client)
114
115
 
115
116
  # update the response header
116
117
  response.headers.merge!(auth_header)
117
118
 
119
+ # set a server cookie if configured
120
+ if DeviseTokenAuth.cookie_enabled
121
+ set_cookie(auth_header)
122
+ end
118
123
  else
119
124
  unless @resource.reload.valid?
120
- @resource = resource_class.find(@resource.to_param) # errors remain after reload
125
+ @resource = @resource.class.find(@resource.to_param) # errors remain after reload
121
126
  # if we left the model in a bad state, something is wrong in our app
122
127
  unless @resource.valid?
123
128
  raise DeviseTokenAuth::Errors::InvalidModel, "Cannot set auth token in invalid model. Errors: #{@resource.errors.full_messages}"
@@ -129,38 +134,54 @@ module DeviseTokenAuth::Concerns::SetUserByToken
129
134
 
130
135
  private
131
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
+
132
144
  def refresh_headers
133
- ensure_pristine_resource do
134
- # Lock the user record during any auth_header updates to ensure
135
- # we don't have write contention from multiple threads
136
- @resource.with_lock do
137
- # should not append auth header if @resource related token was
138
- # cleared by sign out in the meantime
139
- return if @used_auth_by_token && @resource.tokens[@client_id].nil?
140
-
141
- # update the response header
142
- response.headers.merge!(auth_header_from_batch_request)
143
- end # end lock
144
- end # end ensure_pristine_resource
145
+ # Lock the user record during any auth_header updates to ensure
146
+ # we don't have write contention from multiple threads
147
+ @resource.with_lock do
148
+ # should not append auth header if @resource related token was
149
+ # cleared by sign out in the meantime
150
+ return if @used_auth_by_token && @resource.tokens[@token.client].nil?
151
+
152
+ _auth_header_from_batch_request = auth_header_from_batch_request
153
+
154
+ # update the response header
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
161
+ end # end lock
162
+ end
163
+
164
+ def set_cookie(auth_header)
165
+ cookies[DeviseTokenAuth.cookie_name] = DeviseTokenAuth.cookie_attributes.merge(value: auth_header.to_json)
145
166
  end
146
167
 
147
- def is_batch_request?(user, client_id)
168
+ def is_batch_request?(user, client)
148
169
  !params[:unbatch] &&
149
- user.tokens[client_id] &&
150
- user.tokens[client_id]['updated_at'] &&
151
- Time.parse(user.tokens[client_id]['updated_at']) > @request_started_at - DeviseTokenAuth.batch_request_buffer_throttle
170
+ user.tokens[client] &&
171
+ user.tokens[client]['updated_at'] &&
172
+ user.tokens[client]['updated_at'].to_time > @request_started_at - DeviseTokenAuth.batch_request_buffer_throttle
152
173
  end
153
174
 
154
175
  def auth_header_from_batch_request
155
176
  # determine batch request status after request processing, in case
156
177
  # another processes has updated it during that processing
157
- @is_batch_request = is_batch_request?(@resource, @client_id)
178
+ @is_batch_request = is_batch_request?(@resource, @token.client)
158
179
 
159
180
  auth_header = {}
160
181
  # extend expiration of batch buffer to account for the duration of
161
182
  # this request
162
183
  if @is_batch_request
163
- auth_header = @resource.extend_batch_buffer(@token, @client_id)
184
+ auth_header = @resource.extend_batch_buffer(@token.token, @token.client)
164
185
 
165
186
  # Do not return token for batch requests to avoid invalidated
166
187
  # tokens returned to the client in case of race conditions.
@@ -171,7 +192,7 @@ module DeviseTokenAuth::Concerns::SetUserByToken
171
192
  auth_header[DeviseTokenAuth.headers_names[:"expiry"]] = ' '
172
193
  else
173
194
  # update Authorization response header with new token
174
- auth_header = @resource.create_new_auth_token(@client_id)
195
+ auth_header = @resource.create_new_auth_token(@token.client)
175
196
  end
176
197
  auth_header
177
198
  end
@@ -2,38 +2,85 @@
2
2
 
3
3
  module DeviseTokenAuth
4
4
  class ConfirmationsController < DeviseTokenAuth::ApplicationController
5
- def show
6
- @resource = resource_class.confirm_by_token(params[:confirmation_token])
7
-
8
- if @resource && @resource.id
9
- expiry = nil
10
- if defined?(@resource.sign_in_count) && @resource.sign_in_count > 0
11
- expiry = (Time.zone.now + 1.second).to_i
12
- end
13
5
 
14
- client_id, token = @resource.create_token expiry: expiry
15
-
16
- sign_in(@resource)
17
- @resource.save!
6
+ def show
7
+ @resource = resource_class.confirm_by_token(resource_params[:confirmation_token])
18
8
 
9
+ if @resource.errors.empty?
19
10
  yield @resource if block_given?
20
11
 
21
12
  redirect_header_options = { account_confirmation_success: true }
22
- redirect_headers = build_redirect_headers(token,
23
- client_id,
24
- redirect_header_options)
25
13
 
26
- # give redirect value from params priority
27
- @redirect_url = params[:redirect_url]
14
+ if signed_in?(resource_name)
15
+ token = signed_in_resource.create_token
16
+ signed_in_resource.save!
28
17
 
29
- # fall back to default value if provided
30
- @redirect_url ||= DeviseTokenAuth.default_confirm_success_url
18
+ redirect_headers = build_redirect_headers(token.token,
19
+ token.client,
20
+ redirect_header_options)
31
21
 
22
+ redirect_to_link = signed_in_resource.build_auth_url(redirect_url, redirect_headers)
23
+ else
24
+ redirect_to_link = DeviseTokenAuth::Url.generate(redirect_url, redirect_header_options)
25
+ end
32
26
 
33
- redirect_to(@resource.build_auth_url(@redirect_url, redirect_headers))
27
+ redirect_to(redirect_to_link)
34
28
  else
35
29
  raise ActionController::RoutingError, 'Not Found'
36
30
  end
37
31
  end
32
+
33
+ def create
34
+ return render_create_error_missing_email if resource_params[:email].blank?
35
+
36
+ @email = get_case_insensitive_field_from_resource_params(:email)
37
+
38
+ @resource = resource_class.dta_find_by(uid: @email, provider: provider)
39
+
40
+ return render_not_found_error unless @resource
41
+
42
+ @resource.send_confirmation_instructions({
43
+ redirect_url: redirect_url,
44
+ client_config: resource_params[:config_name]
45
+ })
46
+
47
+ return render_create_success
48
+ end
49
+
50
+ protected
51
+
52
+ def render_create_error_missing_email
53
+ render_error(401, I18n.t('devise_token_auth.confirmations.missing_email'))
54
+ end
55
+
56
+ def render_create_success
57
+ render json: {
58
+ success: true,
59
+ message: success_message('confirmations', @email)
60
+ }
61
+ end
62
+
63
+ def render_not_found_error
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
69
+ end
70
+
71
+ private
72
+
73
+ def resource_params
74
+ params.permit(:email, :confirmation_token, :config_name)
75
+ end
76
+
77
+ # give redirect value from params priority or fall back to default value if provided
78
+ def redirect_url
79
+ params.fetch(
80
+ :redirect_url,
81
+ DeviseTokenAuth.default_confirm_success_url
82
+ )
83
+ end
84
+
38
85
  end
39
86
  end
@@ -3,6 +3,9 @@
3
3
  module DeviseTokenAuth
4
4
  class OmniauthCallbacksController < DeviseTokenAuth::ApplicationController
5
5
  attr_reader :auth_params
6
+
7
+ before_action :validate_auth_origin_url_param
8
+
6
9
  skip_before_action :set_user_by_token, raise: false
7
10
  skip_after_action :update_auth_header
8
11
 
@@ -12,18 +15,43 @@ module DeviseTokenAuth
12
15
 
13
16
  # derive target redirect route from 'resource_class' param, which was set
14
17
  # before authentication.
15
- devise_mapping = [request.env['omniauth.params']['namespace_name'],
16
- request.env['omniauth.params']['resource_class'].underscore.gsub('/', '_')].compact.join('_')
17
- path = "#{Devise.mappings[devise_mapping.to_sym].fullpath}/#{params[:provider]}/callback"
18
- klass = request.scheme == 'https' ? URI::HTTPS : URI::HTTP
19
- redirect_route = klass.build(host: request.host, port: request.port, path: path).to_s
18
+ devise_mapping = get_devise_mapping
19
+ redirect_route = get_redirect_route(devise_mapping)
20
20
 
21
21
  # preserve omniauth info for success route. ignore 'extra' in twitter
22
22
  # auth response to avoid CookieOverflow.
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
+ end
28
+
29
+ def get_redirect_route(devise_mapping)
30
+ path = "#{Devise.mappings[devise_mapping.to_sym].fullpath}/#{params[:provider]}/callback"
31
+ klass = request.scheme == 'https' ? URI::HTTPS : URI::HTTP
32
+ redirect_route = klass.build(host: request.host, port: request.port, path: path).to_s
33
+ end
34
+
35
+ def get_devise_mapping
36
+ # derive target redirect route from 'resource_class' param, which was set
37
+ # before authentication.
38
+ devise_mapping = [request.env['omniauth.params']['namespace_name'],
39
+ request.env['omniauth.params']['resource_class'].underscore.gsub('/', '_')].compact.join('_')
40
+ rescue NoMethodError => err
41
+ default_devise_mapping
42
+ end
43
+
44
+ # This method will only be called if `get_devise_mapping` cannot
45
+ # find the mapping in `omniauth.params`.
46
+ #
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
49
+ # the devise mapping. If you are in a situation like that, and
50
+ # your app allows for you to determine somehow what the devise
51
+ # mapping should be (because, for example, it is always the same),
52
+ # then you can handle it by overriding this method.
53
+ def default_devise_mapping
54
+ raise NotImplementedError.new('no default_devise_mapping set')
27
55
  end
28
56
 
29
57
  def omniauth_success
@@ -42,6 +70,10 @@ module DeviseTokenAuth
42
70
 
43
71
  yield @resource if block_given?
44
72
 
73
+ if DeviseTokenAuth.cookie_enabled
74
+ set_token_in_cookie(@resource, @token)
75
+ end
76
+
45
77
  render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json)
46
78
  end
47
79
 
@@ -50,6 +82,11 @@ module DeviseTokenAuth
50
82
  render_data_or_redirect('authFailure', error: @error)
51
83
  end
52
84
 
85
+ def validate_auth_origin_url_param
86
+ return render_error_not_allowed_auth_origin_url if auth_origin_url && blacklisted_redirect_url?(auth_origin_url)
87
+ end
88
+
89
+
53
90
  protected
54
91
 
55
92
  # this will be determined differently depending on the action that calls
@@ -79,7 +116,8 @@ module DeviseTokenAuth
79
116
 
80
117
  # break out provider attribute assignment for easy method extension
81
118
  def assign_provider_attrs(user, auth_hash)
82
- attrs = auth_hash['info'].slice(*user.attributes.keys)
119
+ attrs = auth_hash['info'].to_hash
120
+ attrs = attrs.slice(*user.attribute_names)
83
121
  user.assign_attributes(attrs)
84
122
  end
85
123
 
@@ -112,10 +150,18 @@ module DeviseTokenAuth
112
150
  omniauth_params['omniauth_window_type']
113
151
  end
114
152
 
115
- def auth_origin_url
153
+ def unsafe_auth_origin_url
116
154
  omniauth_params['auth_origin_url'] || omniauth_params['origin']
117
155
  end
118
156
 
157
+
158
+ def auth_origin_url
159
+ if unsafe_auth_origin_url && blacklisted_redirect_url?(unsafe_auth_origin_url)
160
+ return nil
161
+ end
162
+ return unsafe_auth_origin_url
163
+ end
164
+
119
165
  # in the success case, omniauth_window_type is in the omniauth_params.
120
166
  # in the failure case, it is in a query param. See monkey patch above
121
167
  def omniauth_window_type
@@ -136,16 +182,6 @@ module DeviseTokenAuth
136
182
  true
137
183
  end
138
184
 
139
- # necessary for access to devise_parameter_sanitizers
140
- def devise_mapping
141
- if omniauth_params
142
- Devise.mappings[[omniauth_params['namespace_name'],
143
- omniauth_params['resource_class'].underscore].compact.join('_').to_sym]
144
- else
145
- request.env['devise.mapping']
146
- end
147
- end
148
-
149
185
  def set_random_password
150
186
  # set crazy password for new oauth users. this is only used to prevent
151
187
  # access via email sign-in.
@@ -156,11 +192,11 @@ module DeviseTokenAuth
156
192
 
157
193
  def create_auth_params
158
194
  @auth_params = {
159
- auth_token: @token,
160
- client_id: @client_id,
161
- uid: @resource.uid,
162
- expiry: @expiry,
163
- config: @config
195
+ auth_token: @token.token,
196
+ client_id: @token.client,
197
+ uid: @resource.uid,
198
+ expiry: @token.expiry,
199
+ config: @config
164
200
  }
165
201
  @auth_params.merge!(oauth_registration: true) if @oauth_registration
166
202
  @auth_params
@@ -168,11 +204,16 @@ module DeviseTokenAuth
168
204
 
169
205
  def set_token_on_resource
170
206
  @config = omniauth_params['config_name']
171
- @client_id, @token, @expiry = @resource.create_token
207
+ @token = @resource.create_token
208
+ end
209
+
210
+ def render_error_not_allowed_auth_origin_url
211
+ message = I18n.t('devise_token_auth.omniauth.not_allowed_redirect_url', redirect_url: unsafe_auth_origin_url)
212
+ render_data_or_redirect('authFailure', error: message)
172
213
  end
173
214
 
174
215
  def render_data(message, data)
175
- @data = data.merge(message: message)
216
+ @data = data.merge(message: ActionController::Base.helpers.sanitize(message))
176
217
  render layout: nil, template: 'devise_token_auth/omniauth_external_window'
177
218
  end
178
219
 
@@ -209,11 +250,20 @@ module DeviseTokenAuth
209
250
  <html>
210
251
  <head></head>
211
252
  <body>
212
- #{text}
253
+ #{ActionController::Base.helpers.sanitize(text)}
213
254
  </body>
214
255
  </html>)
215
256
  end
216
257
 
258
+ def handle_new_resource
259
+ @oauth_registration = true
260
+ set_random_password
261
+ end
262
+
263
+ def assign_whitelisted_params?
264
+ true
265
+ end
266
+
217
267
  def get_resource_from_auth_hash
218
268
  # find or create user by provider and provider uid
219
269
  @resource = resource_class.where(
@@ -222,18 +272,20 @@ module DeviseTokenAuth
222
272
  ).first_or_initialize
223
273
 
224
274
  if @resource.new_record?
225
- @oauth_registration = true
226
- set_random_password
275
+ handle_new_resource
227
276
  end
228
277
 
229
278
  # sync user info with provider, update/generate auth token
230
279
  assign_provider_attrs(@resource, auth_hash)
231
280
 
232
281
  # assign any additional (whitelisted) attributes
233
- extra_params = whitelisted_params
234
- @resource.assign_attributes(extra_params) if extra_params
282
+ if assign_whitelisted_params?
283
+ extra_params = whitelisted_params
284
+ @resource.assign_attributes(extra_params) if extra_params
285
+ end
235
286
 
236
287
  @resource
237
288
  end
238
289
  end
290
+
239
291
  end