devise_token_auth_multi_email 0.9.3 → 0.9.5

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/Rakefile +4 -8
  4. data/app/controllers/devise_token_auth/concerns/resource_finder.rb +2 -9
  5. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +35 -5
  6. data/app/controllers/devise_token_auth/registrations_controller.rb +24 -18
  7. data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +6 -3
  8. data/lib/devise_token_auth/engine.rb +40 -1
  9. data/lib/devise_token_auth/version.rb +1 -1
  10. data/lib/devise_token_auth_multi_email.rb +3 -0
  11. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +4 -4
  12. data/test/controllers/devise_token_auth/multi_email_coexistence_test.rb +130 -0
  13. data/test/controllers/devise_token_auth/multi_email_confirmations_controller_test.rb +210 -0
  14. data/test/controllers/devise_token_auth/multi_email_passwords_controller_test.rb +247 -0
  15. data/test/controllers/devise_token_auth/multi_email_registrations_controller_test.rb +137 -0
  16. data/test/controllers/devise_token_auth/multi_email_sessions_controller_test.rb +191 -0
  17. data/test/controllers/devise_token_auth/multi_email_token_validations_controller_test.rb +140 -0
  18. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +5 -4
  19. data/test/controllers/devise_token_auth/standard_user_registrations_controller_test.rb +165 -0
  20. data/test/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
  21. data/test/coverage/assets/0.13.2/loading.gif +0 -0
  22. data/test/dummy/app/active_record/multi_email_user.rb +45 -0
  23. data/test/dummy/app/active_record/multi_email_user_email.rb +21 -0
  24. data/test/dummy/config/application.rb +6 -1
  25. data/test/dummy/config/initializers/omniauth.rb +15 -1
  26. data/test/dummy/config/routes.rb +8 -0
  27. data/test/dummy/db/migrate/20260401000001_devise_token_auth_create_multi_email_users.rb +49 -0
  28. data/test/dummy/db/migrate/20260401000002_devise_token_auth_create_multi_email_user_emails.rb +29 -0
  29. data/test/dummy/db/schema.rb +81 -41
  30. data/test/dummy/db/test.sqlite3-shm +0 -0
  31. data/test/dummy/tmp/generators/app/controllers/application_controller.rb +6 -0
  32. data/test/dummy/tmp/generators/app/models/{user.rb → azpire/v1/human_resource/user.rb} +1 -1
  33. data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +11 -5
  34. data/test/dummy/tmp/generators/db/migrate/{20210305040222_devise_token_auth_create_users.rb → 20260408021432_devise_token_auth_create_azpire_v1_human_resource_users.rb} +7 -7
  35. data/test/factories/users.rb +1 -0
  36. data/test/lib/devise_token_auth/controllers/helpers_test.rb +402 -0
  37. data/test/lib/devise_token_auth/token_factory_test.rb +18 -18
  38. data/test/lib/generators/devise_token_auth/install_generator_test.rb +60 -0
  39. data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +1 -1
  40. data/test/lib/generators/devise_token_auth/install_mongoid_generator_test.rb +218 -0
  41. data/test/models/multi_email_user_email_test.rb +95 -0
  42. data/test/models/multi_email_user_test.rb +225 -0
  43. data/test/test_helper.rb +21 -11
  44. data/test/validators/devise_token_auth_email_validator_test.rb +114 -0
  45. metadata +59 -27
  46. data/test/dummy/tmp/generators/app/models/mang.rb +0 -9
  47. data/test/dummy/tmp/generators/config/routes.rb +0 -9
  48. data/test/dummy/tmp/generators/db/migrate/20210305040222_devise_token_auth_create_mangs.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97d2fd4e834ebf59bf88453da0029868b9d333bdd9a20b6f7247b64906020224
4
- data.tar.gz: 03ab97bf9fddea5cf9927243bac3c2ef32523dca22319d41e58c216ce8a00bc4
3
+ metadata.gz: d0c66feab8adcbd8779659cfca55d37dafc2f7b597478d249f252e6a8d359fa9
4
+ data.tar.gz: 9e62f19a8c193602b0c8063d2b569a07ed6864a961d9363e95aef335907f74d6
5
5
  SHA512:
6
- metadata.gz: ee478a5fe378cd3d51e8f523e7a08703af2d98047e5abe273190e61356218a597063917c4871a97413df0b36e6e0e60c48df1f8f18bdcc0913d646ab20ba8a1d
7
- data.tar.gz: 76fba5e04fc7528d483ecc03999a2227ebd96d484e6f5732a024cc74dcffb2a9b58a91c48a34a1de9b4108b161c46a64b7cd69513df90ed51c58405a3cafee48
6
+ metadata.gz: 25b3abeb4b9b3b13efca53a386affd1d080673c2815154ed60942aa7190d8860dd9a95bfd0306d34b32fe859150dac52684d6e5c678f3557d2637dcd89418b2b
7
+ data.tar.gz: c722c5166cc96ca5145174b898f5c8490d7230a6b8485ff78439c9dc1dddae87e1b5f05e4fb3164f9030fd500ec19e44765b5ffd92fb11bbb9c65b061d14c87f
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Devise Token Auth Multi Email
2
+ [![Dependabot Updates](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/dependabot/dependabot-updates)
3
+ [![Test](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/test.yml/badge.svg)](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/test.yml)
4
+ [![Coverage Status](https://coveralls.io/repos/github/mgmodell/devise_token_auth_multi_email/badge.svg?branch=master)](https://coveralls.io/github/mgmodell/devise_token_auth_multi_email?branch=master)
2
5
 
3
6
  Simple, multi-client and secure token-based authentication for Rails.
4
7
 
data/Rakefile CHANGED
@@ -21,14 +21,10 @@ load 'rails/tasks/engine.rake'
21
21
 
22
22
  Bundler::GemHelper.install_tasks
23
23
 
24
- require 'rake/testtask'
25
-
26
- Rake::TestTask.new(:test) do |t|
27
- t.libs << 'lib'
28
- t.libs << 'test'
29
- t.pattern = 'test/**/*_test.rb'
30
- t.verbose = false
31
- t.warning = false
24
+ # Custom test task to avoid minitest-rails SIGTRAP issue
25
+ desc 'Run all tests'
26
+ task :test do
27
+ sh 'find test -name "*_test.rb" -exec bundle exec ruby -I lib:test {} \;'
32
28
  end
33
29
 
34
30
  task default: :test
@@ -20,15 +20,8 @@ module DeviseTokenAuth::Concerns::ResourceFinder
20
20
  end
21
21
 
22
22
  def find_resource(field, value)
23
- @@multi_email_field = if( defined? @@multi_email_field )
24
- @@multi_email_field = if( defined?( Devise::MultiEmail.emails_association_name ).nil? )
25
- Devise::MultiEmail.emails_association_name.to_s.singularize.to_sym
26
- else
27
- false
28
- end
29
- end
30
-
31
- @resource = if :email == field && !@@multi_email_field
23
+ @resource = if :email == field && resource_class.respond_to?(:multi_email_association)
24
+ # devise-multi_email adds multi_email_association to parent models only.
32
25
  resource_class.find_by_email value
33
26
  elsif database_adapter&.include?('mysql')
34
27
  # fix for mysql default case insensitivity
@@ -23,7 +23,20 @@ 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, {status: 307}.merge(redirect_options)
26
+ # Also encode omniauth params in the redirect URL so they survive as
27
+ # request params in omniauth_success even when the session cookie is
28
+ # not reliably forwarded through a 307 redirect chain (e.g. Rails 7.2+
29
+ # integration tests where follow_redirect! preserves the POST method).
30
+ omniauth_env_params = request.env['omniauth.params'].presence
31
+ redirect_url = omniauth_env_params ?
32
+ DeviseTokenAuth::Url.generate(redirect_route, omniauth_env_params) :
33
+ redirect_route
34
+
35
+ # Use 302/303 so the callback is performed as a GET and params/session
36
+ # survive reliably across the redirect chain in Rails integration tests.
37
+ # 303 is semantically "see other" after a POST; 302 is also widely used.
38
+ # Suggested by Claude Sonnet 4
39
+ redirect_to redirect_url, { status: 303 }.merge(redirect_options)
27
40
  end
28
41
 
29
42
  def get_redirect_route(devise_mapping)
@@ -102,13 +115,30 @@ module DeviseTokenAuth
102
115
  if request.env['omniauth.params'] && request.env['omniauth.params'].any?
103
116
  @_omniauth_params = request.env['omniauth.params']
104
117
  elsif session['dta.omniauth.params'] && session['dta.omniauth.params'].any?
105
- @_omniauth_params ||= session.delete('dta.omniauth.params')
106
- @_omniauth_params
107
- elsif params['omniauth_window_type']
108
- @_omniauth_params = params.slice('omniauth_window_type', 'auth_origin_url', 'resource_class', 'origin')
118
+ @_omniauth_params = session['dta.omniauth.params']
119
+ elsif params['omniauth_window_type'] || params['auth_origin_url']
120
+ # Fallback: params may arrive as URL query string when the session
121
+ # is not reliably forwarded through a 307 redirect chain (Rails 7.2+).
122
+ # Pre-set @_omniauth_params to {} BEFORE calling params_for_resource to
123
+ # break a potential recursive loop:
124
+ # omniauth_params → params_for_resource → devise_parameter_sanitizer
125
+ # → resource_class → omniauth_params (still computing!) → loop
126
+ # With @_omniauth_params = {} set early, any re-entrant call returns {}
127
+ # and resource_class falls back to params['resource_class'] directly.
128
+ @_omniauth_params = {}
129
+ omniauth_known_keys = %w[omniauth_window_type auth_origin_url resource_class
130
+ origin namespace_name config_name]
131
+ begin
132
+ sign_up_keys = params_for_resource(:sign_up).map(&:to_s)
133
+ rescue NoMethodError, TypeError
134
+ sign_up_keys = []
135
+ end
136
+ @_omniauth_params = params.permit(*omniauth_known_keys, *sign_up_keys)
109
137
  else
110
138
  @_omniauth_params = {}
111
139
  end
140
+ # Always clean up the session key, regardless of which branch was taken.
141
+ session.delete('dta.omniauth.params')
112
142
  end
113
143
  @_omniauth_params
114
144
  end
@@ -40,27 +40,33 @@ module DeviseTokenAuth
40
40
  @resource.skip_confirmation_notification!
41
41
  end
42
42
 
43
- if @resource.save
44
- yield @resource if block_given?
45
-
46
- unless @resource.confirmed?
47
- # user will require email authentication
48
- @resource.send_confirmation_instructions({
49
- client_config: params[:config_name],
50
- redirect_url: @redirect_url
51
- })
52
- end
43
+ begin
44
+ if @resource.save
45
+ yield @resource if block_given?
53
46
 
54
- if active_for_authentication?
55
- # email auth has been bypassed, authenticate user
56
- @token = @resource.create_token
57
- @resource.save!
58
- update_auth_header
47
+ unless @resource.confirmed?
48
+ # user will require email authentication
49
+ @resource.send_confirmation_instructions({
50
+ client_config: params[:config_name],
51
+ redirect_url: @redirect_url
52
+ })
53
+ end
54
+
55
+ if active_for_authentication?
56
+ # email auth has been bypassed, authenticate user
57
+ @token = @resource.create_token
58
+ @resource.save!
59
+ update_auth_header
60
+ end
61
+
62
+ render_create_success
63
+ else
64
+ clean_up_passwords @resource
65
+ render_create_error
59
66
  end
60
-
61
- render_create_success
62
- else
67
+ rescue ActiveRecord::RecordNotUnique
63
68
  clean_up_passwords @resource
69
+ @resource.errors.add(:email, :taken)
64
70
  render_create_error
65
71
  end
66
72
  end
@@ -8,9 +8,12 @@ module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
8
8
  validates :email, :devise_token_auth_email => true, allow_nil: true, allow_blank: true, if: lambda { uid_and_provider_defined? && email_provider? }
9
9
  validates_presence_of :uid, if: lambda { uid_and_provider_defined? && !email_provider? }
10
10
 
11
- # Provide support for devise-multi_email - they implement case_sensitive
12
- # and maintain uniqueness themselves.
13
- unless Gem.loaded_specs[ 'devise-multi_email' ]
11
+ # Only skip the uniqueness validation for models that use devise-multi_email
12
+ # (detected by the presence of the multi_email_association class method, which
13
+ # Devise::MultiEmail::ParentModelExtensions adds via :multi_email_authenticatable).
14
+ # Standard models always validate; multi_email models manage uniqueness via the
15
+ # emails association table instead.
16
+ unless Gem.loaded_specs['devise-multi_email'] && respond_to?(:multi_email_association)
14
17
  # only validate unique emails among email registration users
15
18
  validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create, if: lambda { uid_and_provider_defined? && email_provider? }
16
19
  end
@@ -82,12 +82,51 @@ module DeviseTokenAuth
82
82
 
83
83
  # Omniauth currently removes omniauth.params during mocked requests
84
84
  # see also: https://github.com/intridea/omniauth/pull/812
85
+ #
86
+ # In Rails 7.2+, follow_redirect! preserves the POST method for 307
87
+ # redirects. This means the router's 307 to /omniauth/:provider is
88
+ # followed as a POST, which OmniAuth handles in mock_request_call.
89
+ # However the session cookie written by that Rack response is not
90
+ # reliably forwarded to the subsequent GET /omniauth/:provider/callback
91
+ # request in integration tests, so session['omniauth.params'] is nil
92
+ # when mock_callback_call runs.
93
+ #
94
+ # Fix: also encode the omniauth params as a query string in the
95
+ # callback redirect URL (mock_request_call) so they are available in
96
+ # request.params as a fallback (mock_callback_call).
85
97
  OmniAuth::Strategy.class_eval do
98
+ def mock_request_call
99
+ setup_phase
100
+ @env['omniauth.origin'] = request.params['origin']
101
+ @env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
102
+ omniauth_params = request.params.except('authenticity_token')
103
+ session['omniauth.params'] = omniauth_params
104
+ # Set env now so redirect_to_failure (failure path) and
105
+ # mock_callback_call (success path) both have access to params.
106
+ @env['omniauth.params'] = omniauth_params
107
+ mocked_auth = OmniAuth.mock_auth_for(name.to_s)
108
+ if mocked_auth.is_a?(Symbol)
109
+ fail!(mocked_auth)
110
+ else
111
+ @env['omniauth.auth'] = mocked_auth
112
+ # Encode params in the callback URL so they survive even when the
113
+ # session cookie is not forwarded through the redirect chain.
114
+ redirect_target = omniauth_params.any? ?
115
+ "#{callback_url}?#{omniauth_params.to_query}" :
116
+ callback_url
117
+ redirect redirect_target
118
+ end
119
+ end
120
+
86
121
  def mock_callback_call
87
122
  setup_phase
88
123
  @env['omniauth.origin'] = session.delete('omniauth.origin')
89
124
  @env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
90
- @env['omniauth.params'] = session.delete('omniauth.params') || {}
125
+ # Prefer the session (Rails ≤7.1) but fall back to request params
126
+ # (Rails 7.2+ where the session cookie may not survive the redirect).
127
+ @env['omniauth.params'] = session.delete('omniauth.params').presence ||
128
+ request.params.except('authenticity_token') ||
129
+ {}
91
130
  mocked_auth = OmniAuth.mock_auth_for(name.to_s)
92
131
  if mocked_auth.is_a?(Symbol)
93
132
  fail!(mocked_auth)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeviseTokenAuth
4
- VERSION = '0.9.3'.freeze
4
+ VERSION = '0.9.5'.freeze
5
5
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devise_token_auth'
@@ -62,8 +62,8 @@ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
62
62
  end
63
63
 
64
64
  test 'redirect url includes token params' do
65
- assert @token_params.all? { |param| response.body.include?(param) }
66
- assert response.body.include?('account_confirmation_success')
65
+ assert @token_params.all? { |param| response.location.include?(param) }
66
+ assert response.location.include?('account_confirmation_success')
67
67
  end
68
68
  end
69
69
 
@@ -86,8 +86,8 @@ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
86
86
  end
87
87
 
88
88
  test 'redirect url does not include token params' do
89
- refute @token_params.any? { |param| response.body.include?(param) }
90
- assert response.body.include?('account_confirmation_success')
89
+ refute @token_params.any? { |param| response.location.include?(param) }
90
+ assert response.location.include?('account_confirmation_success')
91
91
  end
92
92
  end
93
93
 
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ # Tests that verify standard models and multi-email models can coexist in the
6
+ # same Rails application without interfering with each other.
7
+ #
8
+ # Specifically:
9
+ # • Standard (User, Mang) and multi-email (MultiEmailUser) routes all work.
10
+ # • The same email address can be used across different model types (they
11
+ # live in different tables so there is no cross-model constraint conflict).
12
+ # • Token authentication works independently for each model type.
13
+ # • Session isolation: signing in on one model doesn't affect another.
14
+ #
15
+ # These tests are ActiveRecord-only — the MultiEmailUser model and route are
16
+ # not set up in Mongoid runs.
17
+ return unless DEVISE_TOKEN_AUTH_ORM == :active_record
18
+
19
+ class MultiEmailCoexistenceTest < ActionDispatch::IntegrationTest
20
+ def standard_params(email: nil)
21
+ {
22
+ email: email || Faker::Internet.unique.email,
23
+ password: 'secret123',
24
+ password_confirmation: 'secret123',
25
+ confirm_success_url: Faker::Internet.url
26
+ }
27
+ end
28
+ alias multi_email_params standard_params
29
+
30
+ # -------------------------------------------------------------------------
31
+ # Both model types can register simultaneously
32
+ # -------------------------------------------------------------------------
33
+ describe 'independent registration' do
34
+ test 'standard user can register at /auth' do
35
+ post '/auth', params: standard_params
36
+ assert_equal 200, response.status
37
+ assert_equal 'User', assigns(:resource).class.name
38
+ end
39
+
40
+ test 'multi-email user can register at /multi_email_auth' do
41
+ post '/multi_email_auth', params: multi_email_params
42
+ assert_equal 200, response.status
43
+ assert_equal 'MultiEmailUser', assigns(:resource).class.name
44
+ end
45
+
46
+ test 'mang (another standard model) can register at /mangs' do
47
+ post '/mangs', params: standard_params
48
+ assert_equal 200, response.status
49
+ assert_equal 'Mang', assigns(:resource).class.name
50
+ end
51
+ end
52
+
53
+ # -------------------------------------------------------------------------
54
+ # Same email address may exist in both the users table and the
55
+ # multi_email_user_emails table simultaneously (different tables / models)
56
+ # -------------------------------------------------------------------------
57
+ describe 'same email across different models' do
58
+ before do
59
+ @shared_email = Faker::Internet.unique.email
60
+ end
61
+
62
+ test 'standard user and multi-email user can share an email address' do
63
+ # Register a standard User with the email
64
+ post '/auth', params: standard_params(email: @shared_email)
65
+ assert_equal 200, response.status,
66
+ "Standard user registration failed: #{response.body}"
67
+
68
+ # Register a MultiEmailUser with the same email — succeeds because the
69
+ # models live in separate tables with no cross-table uniqueness constraint.
70
+ post '/multi_email_auth', params: multi_email_params(email: @shared_email)
71
+ assert_equal 200, response.status,
72
+ "MultiEmailUser registration with shared email failed: #{response.body}"
73
+ end
74
+ end
75
+
76
+ # -------------------------------------------------------------------------
77
+ # Each model type independently rejects duplicate emails
78
+ # -------------------------------------------------------------------------
79
+ describe 'independent validation' do
80
+ test 'duplicate standard user email is rejected at model level' do
81
+ email = Faker::Internet.unique.email
82
+ create(:user, email: email, provider: 'email').confirm
83
+
84
+ post '/auth', params: standard_params(email: email)
85
+ assert_equal 422, response.status
86
+ end
87
+
88
+ test 'duplicate multi-email user email is rejected by the emails table' do
89
+ email = Faker::Internet.unique.email
90
+
91
+ # Register first multi_email user via the endpoint (not factory, since
92
+ # the gem handles email association creation internally on save).
93
+ post '/multi_email_auth', params: multi_email_params(email: email)
94
+ assert_equal 200, response.status, "First registration failed: #{response.body}"
95
+
96
+ # Duplicate registration should be rejected.
97
+ post '/multi_email_auth', params: multi_email_params(email: email)
98
+ assert_equal 422, response.status
99
+ end
100
+ end
101
+
102
+ # -------------------------------------------------------------------------
103
+ # Model-level configuration confirms coexistence setup is correct
104
+ # -------------------------------------------------------------------------
105
+ describe 'model configuration coexistence' do
106
+ test 'standard models have email uniqueness validator from concern' do
107
+ assert User.validators_on(:email).any? { |v|
108
+ v.is_a?(ActiveRecord::Validations::UniquenessValidator)
109
+ }
110
+ assert Mang.validators_on(:email).any? { |v|
111
+ v.is_a?(ActiveRecord::Validations::UniquenessValidator)
112
+ }
113
+ end
114
+
115
+ test 'multi_email model does NOT have concern email uniqueness validator' do
116
+ refute MultiEmailUser.validators_on(:email).any? { |v|
117
+ v.is_a?(ActiveRecord::Validations::UniquenessValidator)
118
+ }
119
+ end
120
+
121
+ test 'multi_email model has multi_email_association class method' do
122
+ assert MultiEmailUser.respond_to?(:multi_email_association)
123
+ end
124
+
125
+ test 'standard models do NOT have multi_email_association class method' do
126
+ refute User.respond_to?(:multi_email_association)
127
+ refute Mang.respond_to?(:multi_email_association)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ # Tests that verify the ConfirmationsController works correctly with a
6
+ # MultiEmailUser — a model that uses :multi_email_confirmable from the
7
+ # devise-multi_email gem.
8
+ #
9
+ # With multi_email_confirmable the confirmation token is stored on the
10
+ # MultiEmailUserEmail record (not the parent model). The confirmations
11
+ # controller calls resource_class.confirm_by_token, which the gem overrides to
12
+ # search the email records, so the standard redirect-based confirmation flow
13
+ # continues to work.
14
+ #
15
+ # These tests are ActiveRecord-only — the MultiEmailUser model and its route
16
+ # are not available in Mongoid runs.
17
+ return unless DEVISE_TOKEN_AUTH_ORM == :active_record
18
+
19
+ class MultiEmailConfirmationsControllerTest < ActionDispatch::IntegrationTest
20
+ describe 'MultiEmailUser confirmations' do
21
+ def registration_params(email: nil)
22
+ {
23
+ email: email || Faker::Internet.unique.email,
24
+ password: 'secret123',
25
+ password_confirmation: 'secret123',
26
+ confirm_success_url: Faker::Internet.url
27
+ }
28
+ end
29
+
30
+ # Parse the confirmation token out of a mailer body.
31
+ def token_from_mail(mail)
32
+ mail.body.match(/confirmation_token=([^&]*)[&"]/)[1]
33
+ end
34
+
35
+ before do
36
+ @redirect_url = Faker::Internet.url
37
+
38
+ # Register a new MultiEmailUser — this triggers the confirmation email.
39
+ @email = Faker::Internet.unique.email
40
+ post '/multi_email_auth', params: registration_params(email: @email)
41
+ assert_equal 200, response.status, "Setup registration failed: #{response.body}"
42
+ @user = assigns(:resource)
43
+ @mail = ActionMailer::Base.deliveries.last
44
+ @token = token_from_mail(@mail)
45
+ end
46
+
47
+ # -----------------------------------------------------------------------
48
+ # Show (GET) — successful confirmation
49
+ # -----------------------------------------------------------------------
50
+ describe 'successful confirmation via token' do
51
+ before do
52
+ get '/multi_email_auth/confirmation',
53
+ params: { confirmation_token: @token, redirect_url: @redirect_url }
54
+ end
55
+
56
+ test 'response redirects to the redirect URL' do
57
+ assert_redirected_to(/^#{Regexp.escape(@redirect_url)}/)
58
+ end
59
+
60
+ test 'redirect URL includes account_confirmation_success' do
61
+ assert response.location.include?('account_confirmation_success')
62
+ end
63
+
64
+ test 'user is confirmed after following the link' do
65
+ @user.reload
66
+ assert @user.confirmed?
67
+ end
68
+ end
69
+
70
+ # -----------------------------------------------------------------------
71
+ # Show (GET) — invalid token
72
+ # -----------------------------------------------------------------------
73
+ describe 'confirmation with invalid token' do
74
+ before do
75
+ get '/multi_email_auth/confirmation',
76
+ params: { confirmation_token: 'invalid-token', redirect_url: @redirect_url }
77
+ end
78
+
79
+ test 'response redirects (to failure URL)' do
80
+ assert_equal 302, response.status
81
+ end
82
+
83
+ test 'redirect URL contains account_confirmation_success=false' do
84
+ assert response.location.include?('account_confirmation_success=false')
85
+ end
86
+
87
+ test 'user is not confirmed' do
88
+ @user.reload
89
+ refute @user.confirmed?
90
+ end
91
+ end
92
+
93
+ # -----------------------------------------------------------------------
94
+ # Create (POST) — resend confirmation email (without paranoid mode)
95
+ # -----------------------------------------------------------------------
96
+ describe 'resend confirmation email — success' do
97
+ before do
98
+ @mail_count = ActionMailer::Base.deliveries.count
99
+
100
+ post '/multi_email_auth/confirmation',
101
+ params: { email: @email, redirect_url: @redirect_url }
102
+ @data = JSON.parse(response.body)
103
+ @resent_mail = ActionMailer::Base.deliveries.last
104
+ end
105
+
106
+ test 'request is successful' do
107
+ assert_equal 200, response.status
108
+ end
109
+
110
+ test 'a confirmation email is sent' do
111
+ assert_equal @mail_count + 1, ActionMailer::Base.deliveries.count
112
+ end
113
+
114
+ test 'email is addressed to the user' do
115
+ assert_equal @email, @resent_mail['to'].to_s
116
+ end
117
+
118
+ test 'response message is returned' do
119
+ assert @data['message']
120
+ end
121
+ end
122
+
123
+ # -----------------------------------------------------------------------
124
+ # Create (POST) — resend confirmation email (with paranoid mode)
125
+ # -----------------------------------------------------------------------
126
+ describe 'resend confirmation email with paranoid mode — success' do
127
+ before do
128
+ swap Devise, paranoid: true do
129
+ post '/multi_email_auth/confirmation',
130
+ params: { email: @email, redirect_url: @redirect_url }
131
+ @data = JSON.parse(response.body)
132
+ end
133
+ end
134
+
135
+ test 'request is successful' do
136
+ assert_equal 200, response.status
137
+ end
138
+
139
+ test 'paranoid message is returned' do
140
+ assert_equal I18n.t('devise_token_auth.confirmations.sended_paranoid',
141
+ email: @email),
142
+ @data['message']
143
+ end
144
+ end
145
+
146
+ # -----------------------------------------------------------------------
147
+ # Create (POST) — missing email param
148
+ # -----------------------------------------------------------------------
149
+ describe 'resend confirmation — missing email' do
150
+ before do
151
+ post '/multi_email_auth/confirmation',
152
+ params: { redirect_url: @redirect_url }
153
+ @data = JSON.parse(response.body)
154
+ end
155
+
156
+ test 'request fails' do
157
+ assert_equal 401, response.status
158
+ end
159
+
160
+ test 'missing email error is returned' do
161
+ assert @data['errors']
162
+ end
163
+ end
164
+
165
+ # -----------------------------------------------------------------------
166
+ # Create (POST) — unknown email (without paranoid mode)
167
+ # -----------------------------------------------------------------------
168
+ describe 'resend confirmation — unknown email' do
169
+ before do
170
+ post '/multi_email_auth/confirmation',
171
+ params: { email: 'nobody@example.com', redirect_url: @redirect_url }
172
+ @data = JSON.parse(response.body)
173
+ end
174
+
175
+ test 'request fails with 404' do
176
+ assert_equal 404, response.status
177
+ end
178
+
179
+ test 'user not found error is returned' do
180
+ assert @data['errors']
181
+ assert_equal [I18n.t('devise_token_auth.confirmations.user_not_found',
182
+ email: 'nobody@example.com')],
183
+ @data['errors']
184
+ end
185
+ end
186
+
187
+ # -----------------------------------------------------------------------
188
+ # Create (POST) — unknown email (with paranoid mode)
189
+ # -----------------------------------------------------------------------
190
+ describe 'resend confirmation — unknown email with paranoid mode' do
191
+ before do
192
+ swap Devise, paranoid: true do
193
+ post '/multi_email_auth/confirmation',
194
+ params: { email: 'nobody@example.com', redirect_url: @redirect_url }
195
+ @data = JSON.parse(response.body)
196
+ end
197
+ end
198
+
199
+ test 'request returns 200 to hide existence' do
200
+ assert_equal 200, response.status
201
+ end
202
+
203
+ test 'paranoid message is returned' do
204
+ assert_equal I18n.t('devise_token_auth.confirmations.sended_paranoid',
205
+ email: 'nobody@example.com'),
206
+ @data['message']
207
+ end
208
+ end
209
+ end
210
+ end