devise_token_auth_multi_email 0.9.4 → 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.
- checksums.yaml +4 -4
- data/README.md +3 -0
- data/Rakefile +4 -8
- data/app/controllers/devise_token_auth/concerns/resource_finder.rb +2 -9
- data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +35 -5
- data/app/controllers/devise_token_auth/registrations_controller.rb +24 -18
- data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +6 -3
- data/lib/devise_token_auth/engine.rb +40 -1
- data/lib/devise_token_auth/version.rb +1 -1
- data/test/controllers/devise_token_auth/confirmations_controller_test.rb +4 -4
- data/test/controllers/devise_token_auth/multi_email_coexistence_test.rb +130 -0
- data/test/controllers/devise_token_auth/multi_email_confirmations_controller_test.rb +210 -0
- data/test/controllers/devise_token_auth/multi_email_passwords_controller_test.rb +247 -0
- data/test/controllers/devise_token_auth/multi_email_registrations_controller_test.rb +137 -0
- data/test/controllers/devise_token_auth/multi_email_sessions_controller_test.rb +191 -0
- data/test/controllers/devise_token_auth/multi_email_token_validations_controller_test.rb +140 -0
- data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +5 -4
- data/test/controllers/devise_token_auth/standard_user_registrations_controller_test.rb +165 -0
- data/test/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
- data/test/coverage/assets/0.13.2/loading.gif +0 -0
- data/test/dummy/app/active_record/multi_email_user.rb +45 -0
- data/test/dummy/app/active_record/multi_email_user_email.rb +21 -0
- data/test/dummy/config/application.rb +6 -1
- data/test/dummy/config/initializers/omniauth.rb +15 -1
- data/test/dummy/config/routes.rb +8 -0
- data/test/dummy/db/migrate/20260401000001_devise_token_auth_create_multi_email_users.rb +49 -0
- data/test/dummy/db/migrate/20260401000002_devise_token_auth_create_multi_email_user_emails.rb +29 -0
- data/test/dummy/db/schema.rb +81 -41
- data/test/dummy/db/test.sqlite3-shm +0 -0
- data/test/dummy/tmp/generators/app/controllers/application_controller.rb +6 -0
- data/test/dummy/tmp/generators/app/models/{user.rb → azpire/v1/human_resource/user.rb} +1 -1
- data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +11 -5
- 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
- data/test/factories/users.rb +1 -0
- data/test/lib/devise_token_auth/controllers/helpers_test.rb +402 -0
- data/test/lib/devise_token_auth/token_factory_test.rb +18 -18
- data/test/lib/generators/devise_token_auth/install_generator_test.rb +60 -0
- data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +1 -1
- data/test/lib/generators/devise_token_auth/install_mongoid_generator_test.rb +218 -0
- data/test/models/multi_email_user_email_test.rb +95 -0
- data/test/models/multi_email_user_test.rb +225 -0
- data/test/test_helper.rb +21 -11
- data/test/validators/devise_token_auth_email_validator_test.rb +114 -0
- metadata +58 -27
- data/test/dummy/tmp/generators/app/models/mang.rb +0 -9
- data/test/dummy/tmp/generators/config/routes.rb +0 -9
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0c66feab8adcbd8779659cfca55d37dafc2f7b597478d249f252e6a8d359fa9
|
|
4
|
+
data.tar.gz: 9e62f19a8c193602b0c8063d2b569a07ed6864a961d9363e95aef335907f74d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+
[](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/dependabot/dependabot-updates)
|
|
3
|
+
[](https://github.com/mgmodell/devise_token_auth_multi_email/actions/workflows/test.yml)
|
|
4
|
+
[](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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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.
|
|
66
|
-
assert response.
|
|
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.
|
|
90
|
-
assert response.
|
|
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
|