devise_token_auth_multitenancy 1.1.3.alpha1

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 (175) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +103 -0
  4. data/Rakefile +42 -0
  5. data/app/controllers/devise_token_auth/application_controller.rb +79 -0
  6. data/app/controllers/devise_token_auth/concerns/resource_finder.rb +44 -0
  7. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +162 -0
  8. data/app/controllers/devise_token_auth/confirmations_controller.rb +82 -0
  9. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +287 -0
  10. data/app/controllers/devise_token_auth/passwords_controller.rb +206 -0
  11. data/app/controllers/devise_token_auth/registrations_controller.rb +205 -0
  12. data/app/controllers/devise_token_auth/sessions_controller.rb +131 -0
  13. data/app/controllers/devise_token_auth/token_validations_controller.rb +31 -0
  14. data/app/controllers/devise_token_auth/unlocks_controller.rb +89 -0
  15. data/app/models/devise_token_auth/concerns/active_record_support.rb +16 -0
  16. data/app/models/devise_token_auth/concerns/confirmable_support.rb +27 -0
  17. data/app/models/devise_token_auth/concerns/mongoid_support.rb +19 -0
  18. data/app/models/devise_token_auth/concerns/tokens_serialization.rb +19 -0
  19. data/app/models/devise_token_auth/concerns/user.rb +257 -0
  20. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +28 -0
  21. data/app/validators/devise_token_auth_email_validator.rb +23 -0
  22. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  23. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  24. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  25. data/app/views/devise_token_auth/omniauth_external_window.html.erb +38 -0
  26. data/config/locales/da-DK.yml +52 -0
  27. data/config/locales/de.yml +51 -0
  28. data/config/locales/en.yml +57 -0
  29. data/config/locales/es.yml +51 -0
  30. data/config/locales/fr.yml +51 -0
  31. data/config/locales/he.yml +52 -0
  32. data/config/locales/it.yml +48 -0
  33. data/config/locales/ja.yml +48 -0
  34. data/config/locales/ko.yml +51 -0
  35. data/config/locales/nl.yml +32 -0
  36. data/config/locales/pl.yml +51 -0
  37. data/config/locales/pt-BR.yml +48 -0
  38. data/config/locales/pt.yml +51 -0
  39. data/config/locales/ro.yml +48 -0
  40. data/config/locales/ru.yml +52 -0
  41. data/config/locales/sq.yml +48 -0
  42. data/config/locales/sv.yml +52 -0
  43. data/config/locales/uk.yml +61 -0
  44. data/config/locales/vi.yml +52 -0
  45. data/config/locales/zh-CN.yml +48 -0
  46. data/config/locales/zh-HK.yml +50 -0
  47. data/config/locales/zh-TW.yml +50 -0
  48. data/lib/devise_token_auth/blacklist.rb +2 -0
  49. data/lib/devise_token_auth/controllers/helpers.rb +161 -0
  50. data/lib/devise_token_auth/controllers/url_helpers.rb +10 -0
  51. data/lib/devise_token_auth/engine.rb +96 -0
  52. data/lib/devise_token_auth/errors.rb +8 -0
  53. data/lib/devise_token_auth/rails/routes.rb +116 -0
  54. data/lib/devise_token_auth/token_factory.rb +126 -0
  55. data/lib/devise_token_auth/url.rb +44 -0
  56. data/lib/devise_token_auth/version.rb +5 -0
  57. data/lib/devise_token_auth.rb +14 -0
  58. data/lib/generators/devise_token_auth/USAGE +31 -0
  59. data/lib/generators/devise_token_auth/install_generator.rb +91 -0
  60. data/lib/generators/devise_token_auth/install_generator_helpers.rb +98 -0
  61. data/lib/generators/devise_token_auth/install_mongoid_generator.rb +46 -0
  62. data/lib/generators/devise_token_auth/install_views_generator.rb +18 -0
  63. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +60 -0
  64. data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb.erb +49 -0
  65. data/lib/generators/devise_token_auth/templates/user.rb.erb +9 -0
  66. data/lib/generators/devise_token_auth/templates/user_mongoid.rb.erb +56 -0
  67. data/lib/tasks/devise_token_auth_tasks.rake +6 -0
  68. data/test/controllers/custom/custom_confirmations_controller_test.rb +25 -0
  69. data/test/controllers/custom/custom_omniauth_callbacks_controller_test.rb +33 -0
  70. data/test/controllers/custom/custom_passwords_controller_test.rb +79 -0
  71. data/test/controllers/custom/custom_registrations_controller_test.rb +63 -0
  72. data/test/controllers/custom/custom_sessions_controller_test.rb +39 -0
  73. data/test/controllers/custom/custom_token_validations_controller_test.rb +42 -0
  74. data/test/controllers/demo_group_controller_test.rb +151 -0
  75. data/test/controllers/demo_mang_controller_test.rb +284 -0
  76. data/test/controllers/demo_user_controller_test.rb +629 -0
  77. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +191 -0
  78. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +441 -0
  79. data/test/controllers/devise_token_auth/passwords_controller_test.rb +780 -0
  80. data/test/controllers/devise_token_auth/registrations_controller_test.rb +907 -0
  81. data/test/controllers/devise_token_auth/sessions_controller_test.rb +503 -0
  82. data/test/controllers/devise_token_auth/token_validations_controller_test.rb +102 -0
  83. data/test/controllers/devise_token_auth/unlocks_controller_test.rb +196 -0
  84. data/test/controllers/overrides/confirmations_controller_test.rb +47 -0
  85. data/test/controllers/overrides/omniauth_callbacks_controller_test.rb +53 -0
  86. data/test/controllers/overrides/passwords_controller_test.rb +64 -0
  87. data/test/controllers/overrides/registrations_controller_test.rb +46 -0
  88. data/test/controllers/overrides/sessions_controller_test.rb +35 -0
  89. data/test/controllers/overrides/token_validations_controller_test.rb +43 -0
  90. data/test/dummy/README.rdoc +28 -0
  91. data/test/dummy/app/active_record/confirmable_user.rb +11 -0
  92. data/test/dummy/app/active_record/lockable_user.rb +7 -0
  93. data/test/dummy/app/active_record/mang.rb +5 -0
  94. data/test/dummy/app/active_record/only_email_user.rb +7 -0
  95. data/test/dummy/app/active_record/scoped_user.rb +9 -0
  96. data/test/dummy/app/active_record/unconfirmable_user.rb +9 -0
  97. data/test/dummy/app/active_record/unregisterable_user.rb +9 -0
  98. data/test/dummy/app/active_record/user.rb +6 -0
  99. data/test/dummy/app/controllers/application_controller.rb +18 -0
  100. data/test/dummy/app/controllers/auth_origin_controller.rb +7 -0
  101. data/test/dummy/app/controllers/custom/confirmations_controller.rb +13 -0
  102. data/test/dummy/app/controllers/custom/omniauth_callbacks_controller.rb +13 -0
  103. data/test/dummy/app/controllers/custom/passwords_controller.rb +39 -0
  104. data/test/dummy/app/controllers/custom/registrations_controller.rb +39 -0
  105. data/test/dummy/app/controllers/custom/sessions_controller.rb +29 -0
  106. data/test/dummy/app/controllers/custom/token_validations_controller.rb +19 -0
  107. data/test/dummy/app/controllers/demo_group_controller.rb +15 -0
  108. data/test/dummy/app/controllers/demo_mang_controller.rb +14 -0
  109. data/test/dummy/app/controllers/demo_user_controller.rb +27 -0
  110. data/test/dummy/app/controllers/overrides/confirmations_controller.rb +28 -0
  111. data/test/dummy/app/controllers/overrides/omniauth_callbacks_controller.rb +16 -0
  112. data/test/dummy/app/controllers/overrides/passwords_controller.rb +35 -0
  113. data/test/dummy/app/controllers/overrides/registrations_controller.rb +29 -0
  114. data/test/dummy/app/controllers/overrides/sessions_controller.rb +36 -0
  115. data/test/dummy/app/controllers/overrides/token_validations_controller.rb +23 -0
  116. data/test/dummy/app/helpers/application_helper.rb +1058 -0
  117. data/test/dummy/app/models/concerns/favorite_color.rb +19 -0
  118. data/test/dummy/app/mongoid/confirmable_user.rb +52 -0
  119. data/test/dummy/app/mongoid/lockable_user.rb +38 -0
  120. data/test/dummy/app/mongoid/mang.rb +46 -0
  121. data/test/dummy/app/mongoid/only_email_user.rb +33 -0
  122. data/test/dummy/app/mongoid/scoped_user.rb +50 -0
  123. data/test/dummy/app/mongoid/unconfirmable_user.rb +44 -0
  124. data/test/dummy/app/mongoid/unregisterable_user.rb +47 -0
  125. data/test/dummy/app/mongoid/user.rb +49 -0
  126. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  127. data/test/dummy/config/application.rb +48 -0
  128. data/test/dummy/config/application.yml.bk +0 -0
  129. data/test/dummy/config/boot.rb +11 -0
  130. data/test/dummy/config/environment.rb +7 -0
  131. data/test/dummy/config/environments/development.rb +46 -0
  132. data/test/dummy/config/environments/production.rb +84 -0
  133. data/test/dummy/config/environments/test.rb +50 -0
  134. data/test/dummy/config/initializers/assets.rb +10 -0
  135. data/test/dummy/config/initializers/backtrace_silencers.rb +9 -0
  136. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  137. data/test/dummy/config/initializers/devise.rb +290 -0
  138. data/test/dummy/config/initializers/devise_token_auth.rb +55 -0
  139. data/test/dummy/config/initializers/figaro.rb +3 -0
  140. data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  141. data/test/dummy/config/initializers/inflections.rb +18 -0
  142. data/test/dummy/config/initializers/mime_types.rb +6 -0
  143. data/test/dummy/config/initializers/omniauth.rb +11 -0
  144. data/test/dummy/config/initializers/session_store.rb +5 -0
  145. data/test/dummy/config/initializers/wrap_parameters.rb +16 -0
  146. data/test/dummy/config/routes.rb +57 -0
  147. data/test/dummy/config/spring.rb +3 -0
  148. data/test/dummy/config.ru +18 -0
  149. data/test/dummy/db/migrate/20140715061447_devise_token_auth_create_users.rb +58 -0
  150. data/test/dummy/db/migrate/20140715061805_devise_token_auth_create_mangs.rb +57 -0
  151. data/test/dummy/db/migrate/20140829044006_add_operating_thetan_to_user.rb +8 -0
  152. data/test/dummy/db/migrate/20140916224624_add_favorite_color_to_mangs.rb +7 -0
  153. data/test/dummy/db/migrate/20141222035835_devise_token_auth_create_only_email_users.rb +55 -0
  154. data/test/dummy/db/migrate/20141222053502_devise_token_auth_create_unregisterable_users.rb +56 -0
  155. data/test/dummy/db/migrate/20150708104536_devise_token_auth_create_unconfirmable_users.rb +56 -0
  156. data/test/dummy/db/migrate/20160103235141_devise_token_auth_create_scoped_users.rb +56 -0
  157. data/test/dummy/db/migrate/20160629184441_devise_token_auth_create_lockable_users.rb +56 -0
  158. data/test/dummy/db/migrate/20190924101113_devise_token_auth_create_confirmable_users.rb +49 -0
  159. data/test/dummy/db/schema.rb +198 -0
  160. data/test/dummy/lib/migration_database_helper.rb +43 -0
  161. data/test/factories/users.rb +41 -0
  162. data/test/lib/devise_token_auth/blacklist_test.rb +11 -0
  163. data/test/lib/devise_token_auth/token_factory_test.rb +191 -0
  164. data/test/lib/devise_token_auth/url_test.rb +26 -0
  165. data/test/lib/generators/devise_token_auth/install_generator_test.rb +217 -0
  166. data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +222 -0
  167. data/test/lib/generators/devise_token_auth/install_views_generator_test.rb +25 -0
  168. data/test/models/concerns/mongoid_support_test.rb +31 -0
  169. data/test/models/concerns/tokens_serialization_test.rb +70 -0
  170. data/test/models/confirmable_user_test.rb +35 -0
  171. data/test/models/only_email_user_test.rb +29 -0
  172. data/test/models/user_test.rb +108 -0
  173. data/test/support/controllers/routes.rb +43 -0
  174. data/test/test_helper.rb +103 -0
  175. metadata +483 -0
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ # see http://www.emilsoman.com/blog/2013/05/18/building-a-tested/
4
+ module DeviseTokenAuth
5
+ class SessionsController < DeviseTokenAuth::ApplicationController
6
+ before_action :set_user_by_token, only: [:destroy]
7
+ after_action :reset_session, only: [:destroy]
8
+
9
+ def new
10
+ render_new_error
11
+ end
12
+
13
+ def create
14
+ # Check
15
+ field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
16
+
17
+ @resource = nil
18
+ if field
19
+ q_value = get_case_insensitive_field_from_resource_params(field)
20
+
21
+ @resource = find_resource(field, q_value)
22
+ end
23
+
24
+ if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
25
+ valid_password = @resource.valid_password?(resource_params[:password])
26
+ if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
27
+ return render_create_error_bad_credentials
28
+ end
29
+ @token = @resource.create_token
30
+ @resource.save
31
+
32
+ sign_in(:user, @resource, store: false, bypass: false)
33
+
34
+ yield @resource if block_given?
35
+
36
+ render_create_success
37
+ elsif @resource && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
38
+ if @resource.respond_to?(:locked_at) && @resource.locked_at
39
+ render_create_error_account_locked
40
+ else
41
+ render_create_error_not_confirmed
42
+ end
43
+ else
44
+ render_create_error_bad_credentials
45
+ end
46
+ end
47
+
48
+ def destroy
49
+ # remove auth instance variables so that after_action does not run
50
+ user = remove_instance_variable(:@resource) if @resource
51
+ client = @token.client if @token.client
52
+ @token.clear!
53
+
54
+ if user && client && user.tokens[client]
55
+ user.tokens.delete(client)
56
+ user.save!
57
+
58
+ yield user if block_given?
59
+
60
+ render_destroy_success
61
+ else
62
+ render_destroy_error
63
+ end
64
+ end
65
+
66
+ protected
67
+
68
+ def valid_params?(key, val)
69
+ resource_params[:password] && key && val
70
+ end
71
+
72
+ def get_auth_params
73
+ auth_key = nil
74
+ auth_val = nil
75
+
76
+ # iterate thru allowed auth keys, use first found
77
+ resource_class.authentication_keys.each do |k|
78
+ if resource_params[k]
79
+ auth_val = resource_params[k]
80
+ auth_key = k
81
+ break
82
+ end
83
+ end
84
+
85
+ # honor devise configuration for case_insensitive_keys
86
+ if resource_class.case_insensitive_keys.include?(auth_key)
87
+ auth_val.downcase!
88
+ end
89
+
90
+ { key: auth_key, val: auth_val }
91
+ end
92
+
93
+ def render_new_error
94
+ render_error(405, I18n.t('devise_token_auth.sessions.not_supported'))
95
+ end
96
+
97
+ def render_create_success
98
+ render json: {
99
+ data: resource_data(resource_json: @resource.token_validation_response)
100
+ }
101
+ end
102
+
103
+ def render_create_error_not_confirmed
104
+ render_error(401, I18n.t('devise_token_auth.sessions.not_confirmed', email: @resource.email))
105
+ end
106
+
107
+ def render_create_error_account_locked
108
+ render_error(401, I18n.t('devise.mailer.unlock_instructions.account_lock_msg'))
109
+ end
110
+
111
+ def render_create_error_bad_credentials
112
+ render_error(401, I18n.t('devise_token_auth.sessions.bad_credentials'))
113
+ end
114
+
115
+ def render_destroy_success
116
+ render json: {
117
+ success:true
118
+ }, status: 200
119
+ end
120
+
121
+ def render_destroy_error
122
+ render_error(404, I18n.t('devise_token_auth.sessions.user_not_found'))
123
+ end
124
+
125
+ private
126
+
127
+ def resource_params
128
+ params.permit(*params_for_resource(:sign_in))
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeviseTokenAuth
4
+ class TokenValidationsController < DeviseTokenAuth::ApplicationController
5
+ skip_before_action :assert_is_devise_resource!, only: [:validate_token]
6
+ before_action :set_user_by_token, only: [:validate_token]
7
+
8
+ def validate_token
9
+ # @resource will have been set by set_user_by_token concern
10
+ if @resource
11
+ yield @resource if block_given?
12
+ render_validate_token_success
13
+ else
14
+ render_validate_token_error
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def render_validate_token_success
21
+ render json: {
22
+ success: true,
23
+ data: resource_data(resource_json: @resource.token_validation_response)
24
+ }
25
+ end
26
+
27
+ def render_validate_token_error
28
+ render_error(401, I18n.t('devise_token_auth.token_validations.invalid'))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeviseTokenAuth
4
+ class UnlocksController < DeviseTokenAuth::ApplicationController
5
+ skip_after_action :update_auth_header, only: [:create, :show]
6
+
7
+ # this action is responsible for generating unlock tokens and
8
+ # sending emails
9
+ def create
10
+ return render_create_error_missing_email unless resource_params[:email]
11
+
12
+ @email = get_case_insensitive_field_from_resource_params(:email)
13
+ @resource = find_resource(:email, @email)
14
+
15
+ if @resource
16
+ yield @resource if block_given?
17
+
18
+ @resource.send_unlock_instructions(
19
+ email: @email,
20
+ provider: 'email',
21
+ client_config: params[:config_name]
22
+ )
23
+
24
+ if @resource.errors.empty?
25
+ return render_create_success
26
+ else
27
+ render_create_error @resource.errors
28
+ end
29
+ else
30
+ render_not_found_error
31
+ end
32
+ end
33
+
34
+ def show
35
+ @resource = resource_class.unlock_access_by_token(params[:unlock_token])
36
+
37
+ if @resource.persisted?
38
+ token = @resource.create_token
39
+ @resource.save!
40
+ yield @resource if block_given?
41
+
42
+ redirect_header_options = { unlock: true }
43
+ redirect_headers = build_redirect_headers(token.token,
44
+ token.client,
45
+ redirect_header_options)
46
+ redirect_to(@resource.build_auth_url(after_unlock_path_for(@resource),
47
+ redirect_headers))
48
+ else
49
+ render_show_error
50
+ end
51
+ end
52
+
53
+ private
54
+ def after_unlock_path_for(resource)
55
+ #TODO: This should probably be a configuration option at the very least.
56
+ '/'
57
+ end
58
+
59
+ def render_create_error_missing_email
60
+ render_error(401, I18n.t('devise_token_auth.unlocks.missing_email'))
61
+ end
62
+
63
+ def render_create_success
64
+ render json: {
65
+ success: true,
66
+ message: I18n.t('devise_token_auth.unlocks.sended', email: @email)
67
+ }
68
+ end
69
+
70
+ def render_create_error(errors)
71
+ render json: {
72
+ success: false,
73
+ errors: errors
74
+ }, status: 400
75
+ end
76
+
77
+ def render_show_error
78
+ raise ActionController::RoutingError, 'Not Found'
79
+ end
80
+
81
+ def render_not_found_error
82
+ render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
83
+ end
84
+
85
+ def resource_params
86
+ params.permit(:email, :unlock_token, :config)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'tokens_serialization'
2
+
3
+ module DeviseTokenAuth::Concerns::ActiveRecordSupport
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ serialize :tokens, DeviseTokenAuth::Concerns::TokensSerialization
8
+ end
9
+
10
+ class_methods do
11
+ # It's abstract replacement .find_by
12
+ def dta_find_by(attrs = {})
13
+ find_by(attrs)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module DeviseTokenAuth::Concerns::ConfirmableSupport
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Override standard devise `postpone_email_change?` method
6
+ # for not to use `will_save_change_to_email?` & `email_changed?` methods.
7
+ def postpone_email_change?
8
+ postpone = self.class.reconfirmable &&
9
+ email_value_in_database != email &&
10
+ !@bypass_confirmation_postpone &&
11
+ self.email.present? &&
12
+ (!@skip_reconfirmation_in_callback || !email_value_in_database.nil?)
13
+ @bypass_confirmation_postpone = false
14
+ postpone
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def email_value_in_database
21
+ if Devise.rails51? && respond_to?(:email_in_database)
22
+ email_in_database
23
+ else
24
+ email_was
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module DeviseTokenAuth::Concerns::MongoidSupport
2
+ extend ActiveSupport::Concern
3
+
4
+ def as_json(options = {})
5
+ options[:except] = (options[:except] || []) + [:_id]
6
+ hash = super(options)
7
+ hash['id'] = to_param
8
+ hash
9
+ end
10
+
11
+ class_methods do
12
+ # It's abstract replacement .find_by
13
+ def dta_find_by(attrs = {})
14
+ find_by(attrs)
15
+ rescue Mongoid::Errors::DocumentNotFound
16
+ nil
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module DeviseTokenAuth::Concerns::TokensSerialization
2
+ # Serialization hash to json
3
+ def self.dump(object)
4
+ object.each_value(&:compact!) unless object.nil?
5
+ JSON.generate(object)
6
+ end
7
+
8
+ # Deserialization json to hash
9
+ def self.load(json)
10
+ case json
11
+ when String
12
+ JSON.parse(json)
13
+ when NilClass
14
+ {}
15
+ else
16
+ json
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeviseTokenAuth::Concerns::User
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.tokens_match?(token_hash, token)
7
+ @token_equality_cache ||= {}
8
+
9
+ key = "#{token_hash}/#{token}"
10
+ result = @token_equality_cache[key] ||= DeviseTokenAuth::TokenFactory.token_hash_is_token?(token_hash, token)
11
+ @token_equality_cache = {} if @token_equality_cache.size > 10000
12
+ result
13
+ end
14
+
15
+ included do
16
+ # Hack to check if devise is already enabled
17
+ if method_defined?(:devise_modules)
18
+ devise_modules.delete(:omniauthable)
19
+ else
20
+ devise :database_authenticatable, :registerable,
21
+ :recoverable, :validatable, :confirmable
22
+ end
23
+
24
+ if const_defined?('ActiveRecord') && ancestors.include?(ActiveRecord::Base)
25
+ include DeviseTokenAuth::Concerns::ActiveRecordSupport
26
+ end
27
+
28
+ if const_defined?('Mongoid') && ancestors.include?(Mongoid::Document)
29
+ include DeviseTokenAuth::Concerns::MongoidSupport
30
+ end
31
+
32
+ if DeviseTokenAuth.default_callbacks
33
+ include DeviseTokenAuth::Concerns::UserOmniauthCallbacks
34
+ end
35
+
36
+ # get rid of dead tokens
37
+ before_save :destroy_expired_tokens
38
+
39
+ # remove old tokens if password has changed
40
+ before_save :remove_tokens_after_password_reset
41
+
42
+ # don't use default devise email validation
43
+ def email_required?; false; end
44
+ def email_changed?; false; end
45
+ def will_save_change_to_email?; false; end
46
+
47
+ if DeviseTokenAuth.send_confirmation_email && devise_modules.include?(:confirmable)
48
+ include DeviseTokenAuth::Concerns::ConfirmableSupport
49
+ end
50
+
51
+ def password_required?
52
+ return false unless provider == 'email'
53
+ super
54
+ end
55
+
56
+ # override devise method to include additional info as opts hash
57
+ def send_confirmation_instructions(opts = {})
58
+ generate_confirmation_token! unless @raw_confirmation_token
59
+
60
+ # fall back to "default" config name
61
+ opts[:client_config] ||= 'default'
62
+ opts[:to] = unconfirmed_email if pending_reconfirmation?
63
+ opts[:redirect_url] ||= DeviseTokenAuth.default_confirm_success_url
64
+
65
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
66
+ end
67
+
68
+ # override devise method to include additional info as opts hash
69
+ def send_reset_password_instructions(opts = {})
70
+ token = set_reset_password_token
71
+
72
+ # fall back to "default" config name
73
+ opts[:client_config] ||= 'default'
74
+
75
+ send_devise_notification(:reset_password_instructions, token, opts)
76
+ token
77
+ end
78
+
79
+ # override devise method to include additional info as opts hash
80
+ def send_unlock_instructions(opts = {})
81
+ raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
82
+ self.unlock_token = enc
83
+ save(validate: false)
84
+
85
+ # fall back to "default" config name
86
+ opts[:client_config] ||= 'default'
87
+
88
+ send_devise_notification(:unlock_instructions, raw, opts)
89
+ raw
90
+ end
91
+
92
+ def create_token(client: nil, lifespan: nil, cost: nil, **token_extras)
93
+ token = DeviseTokenAuth::TokenFactory.create(client: client, lifespan: lifespan, cost: cost)
94
+
95
+ tokens[token.client] = {
96
+ token: token.token_hash,
97
+ expiry: token.expiry
98
+ }.merge!(token_extras)
99
+
100
+ clean_old_tokens
101
+
102
+ token
103
+ end
104
+ end
105
+
106
+ def valid_token?(token, client = 'default')
107
+ return false unless tokens[client]
108
+ return true if token_is_current?(token, client)
109
+ return true if token_can_be_reused?(token, client)
110
+
111
+ # return false if none of the above conditions are met
112
+ false
113
+ end
114
+
115
+ # this must be done from the controller so that additional params
116
+ # can be passed on from the client
117
+ def send_confirmation_notification?; false; end
118
+
119
+ def token_is_current?(token, client)
120
+ # ghetto HashWithIndifferentAccess
121
+ expiry = tokens[client]['expiry'] || tokens[client][:expiry]
122
+ token_hash = tokens[client]['token'] || tokens[client][:token]
123
+
124
+ return true if (
125
+ # ensure that expiry and token are set
126
+ expiry && token &&
127
+
128
+ # ensure that the token has not yet expired
129
+ DateTime.strptime(expiry.to_s, '%s') > Time.zone.now &&
130
+
131
+ # ensure that the token is valid
132
+ DeviseTokenAuth::Concerns::User.tokens_match?(token_hash, token)
133
+ )
134
+ end
135
+
136
+ # allow batch requests to use the previous token
137
+ def token_can_be_reused?(token, client)
138
+ # ghetto HashWithIndifferentAccess
139
+ updated_at = tokens[client]['updated_at'] || tokens[client][:updated_at]
140
+ last_token_hash = tokens[client]['last_token'] || tokens[client][:last_token]
141
+
142
+ return true if (
143
+ # ensure that the last token and its creation time exist
144
+ updated_at && last_token_hash &&
145
+
146
+ # ensure that previous token falls within the batch buffer throttle time of the last request
147
+ updated_at.to_time > Time.zone.now - DeviseTokenAuth.batch_request_buffer_throttle &&
148
+
149
+ # ensure that the token is valid
150
+ DeviseTokenAuth::TokenFactory.token_hash_is_token?(last_token_hash, token)
151
+ )
152
+ end
153
+
154
+ # update user's auth token (should happen on each request)
155
+ def create_new_auth_token(client = nil)
156
+ now = Time.zone.now
157
+
158
+ token = create_token(
159
+ client: client,
160
+ last_token: tokens.fetch(client, {})['token'],
161
+ updated_at: now.to_s(:rfc822)
162
+ )
163
+
164
+ update_auth_header(token.token, token.client)
165
+ end
166
+
167
+ def build_auth_header(token, client = 'default')
168
+ # client may use expiry to prevent validation request if expired
169
+ # must be cast as string or headers will break
170
+ expiry = tokens[client]['expiry'] || tokens[client][:expiry]
171
+
172
+ {
173
+ DeviseTokenAuth.headers_names[:"access-token"] => token,
174
+ DeviseTokenAuth.headers_names[:"token-type"] => 'Bearer',
175
+ DeviseTokenAuth.headers_names[:"client"] => client,
176
+ DeviseTokenAuth.headers_names[:"expiry"] => expiry.to_s,
177
+ DeviseTokenAuth.headers_names[:"uid"] => uid
178
+ }
179
+ end
180
+
181
+ def update_auth_header(token, client = 'default')
182
+ headers = build_auth_header(token, client)
183
+ clean_old_tokens
184
+ save!
185
+
186
+ headers
187
+ end
188
+
189
+ def build_auth_url(base_url, args)
190
+ args[:uid] = uid
191
+ args[:expiry] = tokens[args[:client_id]]['expiry']
192
+
193
+ DeviseTokenAuth::Url.generate(base_url, args)
194
+ end
195
+
196
+ def extend_batch_buffer(token, client)
197
+ tokens[client]['updated_at'] = Time.zone.now.to_s(:rfc822)
198
+ update_auth_header(token, client)
199
+ end
200
+
201
+ def confirmed?
202
+ devise_modules.exclude?(:confirmable) || super
203
+ end
204
+
205
+ def token_validation_response
206
+ as_json(except: %i[tokens created_at updated_at])
207
+ end
208
+
209
+ protected
210
+
211
+ def destroy_expired_tokens
212
+ if tokens
213
+ tokens.delete_if do |cid, v|
214
+ expiry = v[:expiry] || v['expiry']
215
+ DateTime.strptime(expiry.to_s, '%s') < Time.zone.now
216
+ end
217
+ end
218
+ end
219
+
220
+ 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
228
+ end
229
+
230
+ def remove_tokens_after_password_reset
231
+ return unless should_remove_tokens_after_password_reset?
232
+
233
+ if tokens.present? && tokens.many?
234
+ client, token_data = tokens.max_by { |cid, v| v[:expiry] || v['expiry'] }
235
+ self.tokens = { client => token_data }
236
+ end
237
+ end
238
+
239
+ def max_client_tokens_exceeded?
240
+ tokens.length > DeviseTokenAuth.max_number_of_devices
241
+ end
242
+
243
+ def clean_old_tokens
244
+ if tokens.present? && max_client_tokens_exceeded?
245
+ # Using Enumerable#sort_by on a Hash will typecast it into an associative
246
+ # Array (i.e. an Array of key-value Array pairs). However, since Hashes
247
+ # have an internal order in Ruby 1.9+, the resulting sorted associative
248
+ # Array can be converted back into a Hash, while maintaining the sorted
249
+ # order.
250
+ self.tokens = tokens.sort_by { |_cid, v| v[:expiry] || v['expiry'] }.to_h
251
+
252
+ # Since the tokens are sorted by expiry, shift the oldest client token
253
+ # off the Hash until it no longer exceeds the maximum number of clients
254
+ tokens.shift while max_client_tokens_exceeded?
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
4
+ extend ActiveSupport::Concern
5
+
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?
10
+
11
+ # only validate unique emails among email registration users
12
+ validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create, if: :email_provider?
13
+
14
+ # keep uid in sync with email
15
+ before_save :sync_uid
16
+ before_create :sync_uid
17
+ end
18
+
19
+ protected
20
+
21
+ def email_provider?
22
+ provider == 'email'
23
+ end
24
+
25
+ def sync_uid
26
+ self.uid = email if email_provider?
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DeviseTokenAuthEmailValidator < ActiveModel::EachValidator
4
+ 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
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def email_invalid_message
13
+ # Try strictly set message:
14
+ message = options[:message]
15
+
16
+ if message.nil?
17
+ # Try DeviceTokenAuth translations or fallback to ActiveModel translations
18
+ message = I18n.t(:'errors.messages.not_email', default: :'errors.messages.invalid')
19
+ end
20
+
21
+ message
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ <p><%= t(:welcome).capitalize + ' ' + @email %>!</p>
2
+
3
+ <p><%= t '.confirm_link_msg' %> </p>
4
+
5
+ <p><%= link_to t('.confirm_account_link'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %></p>
@@ -0,0 +1,8 @@
1
+ <p><%= t(:hello).capitalize %> <%= @resource.email %>!</p>
2
+
3
+ <p><%= t '.request_reset_link_msg' %></p>
4
+
5
+ <p><%= link_to t('.password_change_link'), edit_password_url(@resource, reset_password_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s).html_safe %></p>
6
+
7
+ <p><%= t '.ignore_mail_msg' %></p>
8
+ <p><%= t '.no_changes_msg' %></p>
@@ -0,0 +1,7 @@
1
+ <p><%= t :hello %> <%= @resource.email %>!</p>
2
+
3
+ <p><%= t '.account_lock_msg' %></p>
4
+
5
+ <p><%= t '.unlock_link_msg' %></p>
6
+
7
+ <p><%= link_to t('.unlock_link'), unlock_url(@resource, unlock_token: @token, config: message['client-config'].to_s) %></p>