devise_token_auth_multi_email 0.9.5 → 0.9.9
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/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +12 -9
- data/lib/devise_token_auth/version.rb +1 -1
- data/test/controllers/devise_token_auth/concerns/resource_finder_test.rb +109 -0
- data/test/controllers/devise_token_auth/concerns/set_user_by_token_test.rb +199 -0
- data/test/controllers/devise_token_auth/multi_email_registrations_controller_test.rb +162 -5
- data/test/models/confirmable_support_concern_test.rb +97 -0
- data/test/models/user_concern_methods_test.rb +326 -0
- data/test/models/user_omniauth_callbacks_concern_test.rb +123 -0
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43896725154b8afab36fcca00754a5bd5743cdbe08bf44401f0be917ec3cdb59
|
|
4
|
+
data.tar.gz: 4ddd80d829695020b27863ebe5891d9f3037f7070cd857658302e499eed3a711
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7429dbc8c2df36d2df63333d5e34de27b48de8313742942578c920a19de01c5c4fa16b02a702acabfa28a3c9c8a0c45eed728f11b5e9d006886d04da7190240c
|
|
7
|
+
data.tar.gz: c1d928e209ff6e3341c6b33233c1161b43ccae48bc208c4de62548164ad54d4e18f369b3325d3f2d7dbeda49b275a952d6876366cccc54c0009f760e45b6ebff
|
|
@@ -8,15 +8,18 @@ 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
|
-
# Only
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
# Only validate email uniqueness for models that do NOT use devise-multi_email.
|
|
12
|
+
# Multi-email models manage uniqueness via the emails association table instead.
|
|
13
|
+
#
|
|
14
|
+
# The check is done at runtime (inside the lambda) rather than at class-load
|
|
15
|
+
# time, so that it works correctly regardless of the order in which modules are
|
|
16
|
+
# included. If the class responds to :multi_email_association at the point the
|
|
17
|
+
# validation is about to run, we know this is a multi-email model and skip the
|
|
18
|
+
# check. This also avoids calling column_for_attribute(:email) on a model that
|
|
19
|
+
# has no email column, which would otherwise cause MySQL to call
|
|
20
|
+
# nil.case_sensitive? and raise a NoMethodError.
|
|
21
|
+
validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create,
|
|
22
|
+
if: lambda { uid_and_provider_defined? && email_provider? && !self.class.respond_to?(:multi_email_association) }
|
|
20
23
|
|
|
21
24
|
# keep uid in sync with email
|
|
22
25
|
before_save :sync_uid
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
# Tests targeting DeviseTokenAuth::Concerns::ResourceFinder in isolation.
|
|
6
|
+
# The concern is exercised via DeviseTokenAuth::SessionsController, which
|
|
7
|
+
# includes it transitively through SetUserByToken.
|
|
8
|
+
class DeviseTokenAuth::Concerns::ResourceFinderTest < ActionController::TestCase
|
|
9
|
+
tests DeviseTokenAuth::SessionsController
|
|
10
|
+
|
|
11
|
+
describe DeviseTokenAuth::Concerns::ResourceFinder do
|
|
12
|
+
describe '#provider' do
|
|
13
|
+
it 'returns "email"' do
|
|
14
|
+
assert_equal 'email', @controller.provider
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#resource_class' do
|
|
19
|
+
it 'returns User when called without arguments (default devise mapping)' do
|
|
20
|
+
assert_equal User, @controller.resource_class
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'returns the correct class for an explicit mapping argument' do
|
|
24
|
+
assert_equal Mang, @controller.resource_class(:mang)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
29
|
+
describe '#database_adapter' do
|
|
30
|
+
it 'returns a String identifying the configured database adapter' do
|
|
31
|
+
assert_kind_of String, @controller.database_adapter
|
|
32
|
+
refute_empty @controller.database_adapter
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#find_resource' do
|
|
37
|
+
describe 'standard user' do
|
|
38
|
+
before do
|
|
39
|
+
@existing_user = create(:user, :confirmed)
|
|
40
|
+
post :create, params: { email: @existing_user.email,
|
|
41
|
+
password: @existing_user.password }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'assigns the matching resource' do
|
|
45
|
+
assert_equal @existing_user, assigns(:resource)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe 'non-existent email' do
|
|
50
|
+
before do
|
|
51
|
+
post :create, params: { email: 'nobody@example.com',
|
|
52
|
+
password: 'wrongpassword' }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'returns 401' do
|
|
56
|
+
assert_equal 401, response.status
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'does not assign a resource' do
|
|
60
|
+
assert_nil assigns(:resource)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
# Mongoid: connection_db_config is not available; database_adapter returns nil.
|
|
66
|
+
describe '#database_adapter' do
|
|
67
|
+
it 'returns nil for Mongoid (no SQL connection)' do
|
|
68
|
+
assert_nil @controller.database_adapter
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Tests for the multi_email branch of find_resource require ActiveRecord and
|
|
76
|
+
# the /multi_email_auth routes. They are placed in a separate integration test
|
|
77
|
+
# class to avoid forcing a route/mapping override on the unit tests above.
|
|
78
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
79
|
+
class DeviseTokenAuth::Concerns::ResourceFinderMultiEmailTest < ActionDispatch::IntegrationTest
|
|
80
|
+
SIGN_IN_PASSWORD = 'secret123'
|
|
81
|
+
|
|
82
|
+
describe 'ResourceFinder#find_resource with a multi_email user' do
|
|
83
|
+
before do
|
|
84
|
+
# Register and immediately confirm a MultiEmailUser.
|
|
85
|
+
@email = Faker::Internet.unique.email
|
|
86
|
+
post '/multi_email_auth',
|
|
87
|
+
params: { email: @email, password: SIGN_IN_PASSWORD,
|
|
88
|
+
password_confirmation: SIGN_IN_PASSWORD,
|
|
89
|
+
confirm_success_url: Faker::Internet.url }
|
|
90
|
+
assert_equal 200, response.status, "Setup failed: #{response.body}"
|
|
91
|
+
@user = assigns(:resource)
|
|
92
|
+
@user.confirm
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'finds the multi_email user via the emails association' do
|
|
96
|
+
post '/multi_email_auth/sign_in',
|
|
97
|
+
params: { email: @email, password: SIGN_IN_PASSWORD }
|
|
98
|
+
assert_equal 200, response.status
|
|
99
|
+
assert_equal @user, assigns(:resource)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'returns 401 when email is unknown' do
|
|
103
|
+
post '/multi_email_auth/sign_in',
|
|
104
|
+
params: { email: 'nobody@example.com', password: SIGN_IN_PASSWORD }
|
|
105
|
+
assert_equal 401, response.status
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
# Tests targeting DeviseTokenAuth::Concerns::SetUserByToken behaviors that are
|
|
6
|
+
# not already covered by demo_user_controller_test.rb or
|
|
7
|
+
# token_validations_controller_test.rb.
|
|
8
|
+
#
|
|
9
|
+
# The DemoUserController at /demo/members_only is used as the test target
|
|
10
|
+
# because it runs set_user_by_token via the before_action inherited from
|
|
11
|
+
# ApplicationController (which includes SetUserByToken).
|
|
12
|
+
class DeviseTokenAuth::Concerns::SetUserByTokenTest < ActionDispatch::IntegrationTest
|
|
13
|
+
include Warden::Test::Helpers
|
|
14
|
+
|
|
15
|
+
describe DeviseTokenAuth::Concerns::SetUserByToken do
|
|
16
|
+
before do
|
|
17
|
+
@resource = create(:user, :confirmed)
|
|
18
|
+
@auth_headers = @resource.create_new_auth_token
|
|
19
|
+
@token = @auth_headers['access-token']
|
|
20
|
+
@client_id = @auth_headers['client']
|
|
21
|
+
@uid = @auth_headers['uid']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# -------------------------------------------------------------------------
|
|
25
|
+
# #set_request_start
|
|
26
|
+
# -------------------------------------------------------------------------
|
|
27
|
+
describe '#set_request_start' do
|
|
28
|
+
before do
|
|
29
|
+
age_token(@resource, @client_id)
|
|
30
|
+
get '/demo/members_only', params: {}, headers: @auth_headers
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'sets @request_started_at to the current time' do
|
|
34
|
+
assert_kind_of Time, assigns(:request_started_at)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'initializes @resource' do
|
|
38
|
+
refute_nil assigns(:resource)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'initializes @token' do
|
|
42
|
+
refute_nil assigns(:token)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# -------------------------------------------------------------------------
|
|
47
|
+
# #set_user_by_token – authentication via query parameters
|
|
48
|
+
# -------------------------------------------------------------------------
|
|
49
|
+
describe 'token auth via query params' do
|
|
50
|
+
before do
|
|
51
|
+
age_token(@resource, @client_id)
|
|
52
|
+
# Pass the three auth values as query-string parameters, not headers.
|
|
53
|
+
get '/demo/members_only',
|
|
54
|
+
params: {
|
|
55
|
+
'access-token' => @token,
|
|
56
|
+
'client' => @client_id,
|
|
57
|
+
'uid' => @uid
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'returns 200' do
|
|
62
|
+
assert_equal 200, response.status
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'authenticates the correct user' do
|
|
66
|
+
assert_equal @resource, assigns(:resource)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'returns a new access token in the response headers' do
|
|
70
|
+
assert response.headers['access-token'].present?
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# -------------------------------------------------------------------------
|
|
75
|
+
# #set_user_by_token – authentication via auth cookie
|
|
76
|
+
# -------------------------------------------------------------------------
|
|
77
|
+
describe 'token auth via auth cookie' do
|
|
78
|
+
before do
|
|
79
|
+
DeviseTokenAuth.cookie_enabled = true
|
|
80
|
+
|
|
81
|
+
# Sign in to receive the auth cookie from the server.
|
|
82
|
+
post '/auth/sign_in',
|
|
83
|
+
params: { email: @resource.email, password: @resource.password }
|
|
84
|
+
assert_equal 200, response.status, "Sign-in failed: #{response.body}"
|
|
85
|
+
|
|
86
|
+
# The integration session retains the Set-Cookie from sign-in.
|
|
87
|
+
# Make a protected request without explicit auth headers; the cookie
|
|
88
|
+
# should carry the credentials for set_user_by_token to use.
|
|
89
|
+
get '/demo/members_only', params: {}, headers: {}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
after do
|
|
93
|
+
DeviseTokenAuth.cookie_enabled = false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns 200' do
|
|
97
|
+
assert_equal 200, response.status
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'authenticates the correct user' do
|
|
101
|
+
assert_equal @resource, assigns(:resource)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# -------------------------------------------------------------------------
|
|
106
|
+
# #decode_bearer_token – tested indirectly via /auth/validate_token
|
|
107
|
+
# -------------------------------------------------------------------------
|
|
108
|
+
describe '#decode_bearer_token' do
|
|
109
|
+
before do
|
|
110
|
+
age_token(@resource, @client_id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe 'with a blank Authorization header' do
|
|
114
|
+
before do
|
|
115
|
+
get '/auth/validate_token', params: {}, headers: {}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'returns 401 (no credentials provided)' do
|
|
119
|
+
assert_equal 401, response.status
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe 'with an invalid base64 Bearer token' do
|
|
124
|
+
before do
|
|
125
|
+
get '/auth/validate_token', params: {},
|
|
126
|
+
headers: { 'Authorization' => 'Bearer not-valid-base64!!!' }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'returns 401 (decoded token treated as empty hash)' do
|
|
130
|
+
assert_equal 401, response.status
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe 'with valid base64 but non-JSON payload' do
|
|
135
|
+
before do
|
|
136
|
+
non_json_token = Base64.strict_encode64('this is not json')
|
|
137
|
+
get '/auth/validate_token', params: {},
|
|
138
|
+
headers: { 'Authorization' => "Bearer #{non_json_token}" }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'returns 401 (JSON parse error rescued to empty hash)' do
|
|
142
|
+
assert_equal 401, response.status
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe 'with a valid Bearer token (correct uid + access-token + client)' do
|
|
147
|
+
before do
|
|
148
|
+
encoded = Base64.strict_encode64(@auth_headers.to_json)
|
|
149
|
+
get '/auth/validate_token', params: {},
|
|
150
|
+
headers: { 'Authorization' => "Bearer #{encoded}" }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'returns 200' do
|
|
154
|
+
assert_equal 200, response.status
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'authenticates the correct user' do
|
|
158
|
+
assert_equal @resource, assigns(:resource)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# -------------------------------------------------------------------------
|
|
164
|
+
# #set_user_by_token – no credentials at all → resource is nil
|
|
165
|
+
# -------------------------------------------------------------------------
|
|
166
|
+
describe 'when no auth credentials are provided' do
|
|
167
|
+
before do
|
|
168
|
+
get '/demo/members_only', params: {}, headers: {}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'returns 401' do
|
|
172
|
+
assert_equal 401, response.status
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'does not authenticate a resource' do
|
|
176
|
+
assert_nil assigns(:resource)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# -------------------------------------------------------------------------
|
|
181
|
+
# #update_auth_header – cookie updated on each request when cookie_enabled
|
|
182
|
+
# -------------------------------------------------------------------------
|
|
183
|
+
describe 'update_auth_header with cookie_enabled' do
|
|
184
|
+
before do
|
|
185
|
+
DeviseTokenAuth.cookie_enabled = true
|
|
186
|
+
age_token(@resource, @client_id)
|
|
187
|
+
get '/demo/members_only', params: {}, headers: @auth_headers
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
after do
|
|
191
|
+
DeviseTokenAuth.cookie_enabled = false
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'sets the auth cookie on the response' do
|
|
195
|
+
assert response.cookies[DeviseTokenAuth.cookie_name].present?
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -46,11 +46,26 @@ class MultiEmailRegistrationsControllerTest < ActionDispatch::IntegrationTest
|
|
|
46
46
|
assert MultiEmailUser.respond_to?(:find_by_email)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
test '
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
test 'concern uniqueness validator never fires for MultiEmailUser' do
|
|
50
|
+
# The concern adds a uniqueness validator with a runtime :if guard that
|
|
51
|
+
# skips it whenever the model responds to :multi_email_association.
|
|
52
|
+
# This ensures the validator is never exercised on multi-email models
|
|
53
|
+
# (which have no email column and would otherwise crash on MySQL via
|
|
54
|
+
# nil.case_sensitive?).
|
|
55
|
+
#
|
|
56
|
+
# The :if lambda is called via instance_exec on the record (arity 0),
|
|
57
|
+
# so self inside the lambda is the user instance.
|
|
58
|
+
user = MultiEmailUser.new(provider: 'email', uid: 'test@example.com')
|
|
59
|
+
MultiEmailUser.validators_on(:email).select { |v|
|
|
60
|
+
v.is_a?(ActiveRecord::Validations::UniquenessValidator) &&
|
|
61
|
+
Array(v.options[:scope]).include?(:provider)
|
|
62
|
+
}.each do |validator|
|
|
63
|
+
if_procs = Array(validator.options[:if]).select { |c| c.respond_to?(:call) }
|
|
64
|
+
refute if_procs.empty?,
|
|
65
|
+
'DTA concern uniqueness validator must have a runtime :if guard'
|
|
66
|
+
assert if_procs.none? { |c| user.instance_exec(&c) },
|
|
67
|
+
'DTA concern uniqueness validator should not fire for multi-email models'
|
|
68
|
+
end
|
|
54
69
|
end
|
|
55
70
|
|
|
56
71
|
test 'MultiEmailUserEmail has email uniqueness validator from EmailValidatable' do
|
|
@@ -133,5 +148,147 @@ class MultiEmailRegistrationsControllerTest < ActionDispatch::IntegrationTest
|
|
|
133
148
|
assert_not_empty @data['errors']
|
|
134
149
|
end
|
|
135
150
|
end
|
|
151
|
+
|
|
152
|
+
# -----------------------------------------------------------------------
|
|
153
|
+
# Shared helper: register, confirm, and sign in a MultiEmailUser.
|
|
154
|
+
# Returns [user, auth_headers].
|
|
155
|
+
# -----------------------------------------------------------------------
|
|
156
|
+
def sign_in_confirmed_user(email: nil)
|
|
157
|
+
email ||= Faker::Internet.unique.email
|
|
158
|
+
post '/multi_email_auth', params: registration_params(email: email)
|
|
159
|
+
assert_equal 200, response.status, "Setup registration failed: #{response.body}"
|
|
160
|
+
user = assigns(:resource)
|
|
161
|
+
user.confirm
|
|
162
|
+
|
|
163
|
+
post '/multi_email_auth/sign_in',
|
|
164
|
+
params: { email: email, password: 'secret123' }
|
|
165
|
+
assert_equal 200, response.status, "Sign-in failed: #{response.body}"
|
|
166
|
+
|
|
167
|
+
auth_headers = {
|
|
168
|
+
'access-token' => response.headers['access-token'],
|
|
169
|
+
'client' => response.headers['client'],
|
|
170
|
+
'uid' => response.headers['uid'],
|
|
171
|
+
'token-type' => response.headers['token-type']
|
|
172
|
+
}
|
|
173
|
+
[user, auth_headers]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# -----------------------------------------------------------------------
|
|
177
|
+
# Update account (PUT /multi_email_auth)
|
|
178
|
+
# -----------------------------------------------------------------------
|
|
179
|
+
describe 'account update' do
|
|
180
|
+
describe 'successful account update (name field)' do
|
|
181
|
+
before do
|
|
182
|
+
@user, @auth_headers = sign_in_confirmed_user
|
|
183
|
+
age_token(@user, @auth_headers['client'])
|
|
184
|
+
|
|
185
|
+
put '/multi_email_auth',
|
|
186
|
+
params: { name: 'Updated Name' },
|
|
187
|
+
headers: @auth_headers
|
|
188
|
+
@data = JSON.parse(response.body)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
test 'request is successful' do
|
|
192
|
+
assert_equal 200, response.status
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
test 'response status is success' do
|
|
196
|
+
assert_equal 'success', @data['status']
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
test 'updated name is reflected in the response' do
|
|
200
|
+
assert_equal 'Updated Name', @data['data']['name']
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
test 'name is persisted to the database' do
|
|
204
|
+
@user.reload
|
|
205
|
+
assert_equal 'Updated Name', @user.name
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
describe 'account update without authentication' do
|
|
210
|
+
before do
|
|
211
|
+
put '/multi_email_auth',
|
|
212
|
+
params: { name: 'Unauthenticated Update' }
|
|
213
|
+
@data = JSON.parse(response.body)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
test 'request fails with 404' do
|
|
217
|
+
assert_equal 404, response.status
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
test 'user not found error is returned' do
|
|
221
|
+
assert @data['errors']
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe 'account update with empty body' do
|
|
226
|
+
before do
|
|
227
|
+
@user, @auth_headers = sign_in_confirmed_user
|
|
228
|
+
age_token(@user, @auth_headers['client'])
|
|
229
|
+
|
|
230
|
+
put '/multi_email_auth',
|
|
231
|
+
params: {},
|
|
232
|
+
headers: @auth_headers
|
|
233
|
+
@data = JSON.parse(response.body)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
test 'request fails with 422' do
|
|
237
|
+
assert_equal 422, response.status
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# -----------------------------------------------------------------------
|
|
243
|
+
# Destroy account (DELETE /multi_email_auth)
|
|
244
|
+
# -----------------------------------------------------------------------
|
|
245
|
+
describe 'account destroy' do
|
|
246
|
+
describe 'successful account deletion' do
|
|
247
|
+
before do
|
|
248
|
+
@user, @auth_headers = sign_in_confirmed_user
|
|
249
|
+
@user_id = @user.id
|
|
250
|
+
age_token(@user, @auth_headers['client'])
|
|
251
|
+
|
|
252
|
+
delete '/multi_email_auth', headers: @auth_headers
|
|
253
|
+
@data = JSON.parse(response.body)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
test 'request is successful' do
|
|
257
|
+
assert_equal 200, response.status
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
test 'success status is returned' do
|
|
261
|
+
assert_equal 'success', @data['status']
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
test 'user is removed from the database' do
|
|
265
|
+
refute MultiEmailUser.where(id: @user_id).exists?,
|
|
266
|
+
'MultiEmailUser should be deleted after destroy'
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
test 'associated email records are also deleted (dependent: :destroy)' do
|
|
270
|
+
refute MultiEmailUserEmail.where(multi_email_user_id: @user_id).exists?,
|
|
271
|
+
'Email records should be destroyed with the user'
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
describe 'account deletion without authentication' do
|
|
276
|
+
before do
|
|
277
|
+
delete '/multi_email_auth'
|
|
278
|
+
@data = JSON.parse(response.body)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
test 'request fails with 404' do
|
|
282
|
+
assert_equal 404, response.status
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
test 'error message is returned' do
|
|
286
|
+
assert @data['errors']
|
|
287
|
+
assert @data['errors'].include?(
|
|
288
|
+
I18n.t('devise_token_auth.registrations.account_to_destroy_not_found')
|
|
289
|
+
)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
136
293
|
end
|
|
137
294
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
# Unit tests for DeviseTokenAuth::Concerns::ConfirmableSupport.
|
|
6
|
+
#
|
|
7
|
+
# This concern is included into models that have DeviseTokenAuth's
|
|
8
|
+
# send_confirmation_email setting enabled along with Devise's :confirmable
|
|
9
|
+
# module. It overrides postpone_email_change? to avoid relying on
|
|
10
|
+
# devise_will_save_change_to_email?, and exposes email_value_in_database
|
|
11
|
+
# as a Rails-version-safe helper.
|
|
12
|
+
#
|
|
13
|
+
# These tests use ConfirmableUser, which is the only model in the test app
|
|
14
|
+
# that has :confirmable and triggers ConfirmableSupport inclusion.
|
|
15
|
+
return unless DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
16
|
+
|
|
17
|
+
class ConfirmableSupportConcernTest < ActiveSupport::TestCase
|
|
18
|
+
describe DeviseTokenAuth::Concerns::ConfirmableSupport do
|
|
19
|
+
# ConfirmableUser includes ConfirmableSupport via the
|
|
20
|
+
# DeviseTokenAuth.send_confirmation_email + :confirmable check in user.rb.
|
|
21
|
+
test 'ConfirmableUser includes ConfirmableSupport' do
|
|
22
|
+
assert ConfirmableUser.ancestors.include?(DeviseTokenAuth::Concerns::ConfirmableSupport),
|
|
23
|
+
'Expected ConfirmableUser to include ConfirmableSupport'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# -------------------------------------------------------------------------
|
|
27
|
+
# email_value_in_database (protected)
|
|
28
|
+
# -------------------------------------------------------------------------
|
|
29
|
+
describe '#email_value_in_database' do
|
|
30
|
+
test 'returns the persisted email (not an in-memory change)' do
|
|
31
|
+
original_email = 'original@example.com'
|
|
32
|
+
resource = create(:confirmable_user, email: original_email)
|
|
33
|
+
|
|
34
|
+
# Change email in memory only — do not save
|
|
35
|
+
resource.email = 'inmemory@example.com'
|
|
36
|
+
|
|
37
|
+
persisted = resource.send(:email_value_in_database)
|
|
38
|
+
assert_equal original_email, persisted,
|
|
39
|
+
'email_value_in_database should return the DB value, not the in-memory change'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test 'returns nil for a brand-new (unsaved) record' do
|
|
43
|
+
resource = ConfirmableUser.new(email: 'new@example.com')
|
|
44
|
+
result = resource.send(:email_value_in_database)
|
|
45
|
+
# A new record has no persisted value — expect nil or blank string
|
|
46
|
+
assert result.blank?,
|
|
47
|
+
"Expected blank value for unsaved record, got: #{result.inspect}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# -------------------------------------------------------------------------
|
|
52
|
+
# postpone_email_change? (public via override)
|
|
53
|
+
# -------------------------------------------------------------------------
|
|
54
|
+
describe '#postpone_email_change?' do
|
|
55
|
+
test 'returns true when reconfirmable is enabled and email has changed' do
|
|
56
|
+
resource = create(:confirmable_user, email: 'before@example.com')
|
|
57
|
+
|
|
58
|
+
# Change email but do not save — postpone_email_change? inspects pending
|
|
59
|
+
# changes before the save happens.
|
|
60
|
+
resource.email = 'after@example.com'
|
|
61
|
+
|
|
62
|
+
assert resource.postpone_email_change?,
|
|
63
|
+
'Expected postpone_email_change? to return true when email changed and reconfirmable is on'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test 'returns false when reconfirmable is disabled' do
|
|
67
|
+
swap ConfirmableUser, reconfirmable: false do
|
|
68
|
+
resource = create(:confirmable_user, email: 'reconf@example.com')
|
|
69
|
+
resource.email = 'reconf_new@example.com'
|
|
70
|
+
refute resource.postpone_email_change?,
|
|
71
|
+
'Expected postpone_email_change? to return false when reconfirmable is off'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
test 'returns false when email has not changed' do
|
|
76
|
+
resource = create(:confirmable_user, email: 'same@example.com')
|
|
77
|
+
# No change to email
|
|
78
|
+
refute resource.postpone_email_change?,
|
|
79
|
+
'Expected postpone_email_change? to return false when email is unchanged'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
test 'resets @bypass_confirmation_postpone after the check' do
|
|
83
|
+
resource = create(:confirmable_user, email: 'reset_test@example.com')
|
|
84
|
+
resource.instance_variable_set(:@bypass_confirmation_postpone, true)
|
|
85
|
+
resource.email = 'reset_new@example.com'
|
|
86
|
+
|
|
87
|
+
# The flag bypasses the postpone on the first call
|
|
88
|
+
refute resource.postpone_email_change?,
|
|
89
|
+
'Expected postpone_email_change? to return false when bypass flag is set'
|
|
90
|
+
|
|
91
|
+
# After the first call the flag should be cleared
|
|
92
|
+
refute resource.instance_variable_get(:@bypass_confirmation_postpone),
|
|
93
|
+
'@bypass_confirmation_postpone should be reset to false after the call'
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
# Unit tests for methods defined in DeviseTokenAuth::Concerns::User that are
|
|
6
|
+
# not already exercised by user_test.rb or the controller-level tests.
|
|
7
|
+
#
|
|
8
|
+
# Covered here:
|
|
9
|
+
# - build_auth_headers / build_bearer_token
|
|
10
|
+
# - confirmed?
|
|
11
|
+
# - token_validation_response
|
|
12
|
+
# - extend_batch_buffer
|
|
13
|
+
# - remove_tokens_after_password_reset / should_remove_tokens_after_password_reset?
|
|
14
|
+
# - does_token_match?
|
|
15
|
+
# - tokens_match? (class-level cache)
|
|
16
|
+
class UserConcernMethodsTest < ActiveSupport::TestCase
|
|
17
|
+
describe DeviseTokenAuth::Concerns::User do
|
|
18
|
+
# -------------------------------------------------------------------------
|
|
19
|
+
# Setup helpers
|
|
20
|
+
# -------------------------------------------------------------------------
|
|
21
|
+
def build_confirmed_user
|
|
22
|
+
create(:user, :confirmed)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# -------------------------------------------------------------------------
|
|
26
|
+
# build_auth_headers / build_bearer_token
|
|
27
|
+
# -------------------------------------------------------------------------
|
|
28
|
+
describe '#build_auth_headers' do
|
|
29
|
+
before do
|
|
30
|
+
@resource = build_confirmed_user
|
|
31
|
+
@auth = @resource.create_new_auth_token
|
|
32
|
+
@client_id = @auth['client']
|
|
33
|
+
@token = @auth['access-token']
|
|
34
|
+
@headers = @resource.build_auth_headers(@token, @client_id)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test 'includes access-token key' do
|
|
38
|
+
assert @headers.key?(DeviseTokenAuth.headers_names[:'access-token'])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
test 'includes token-type key' do
|
|
42
|
+
assert @headers.key?(DeviseTokenAuth.headers_names[:'token-type'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
test 'includes client key' do
|
|
46
|
+
assert @headers.key?(DeviseTokenAuth.headers_names[:'client'])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test 'includes expiry key' do
|
|
50
|
+
assert @headers.key?(DeviseTokenAuth.headers_names[:'expiry'])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
test 'includes uid key' do
|
|
54
|
+
assert @headers.key?(DeviseTokenAuth.headers_names[:'uid'])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
test 'uid value matches resource uid' do
|
|
58
|
+
assert_equal @resource.uid, @headers[DeviseTokenAuth.headers_names[:'uid']]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test 'access-token value matches token' do
|
|
62
|
+
assert_equal @token, @headers[DeviseTokenAuth.headers_names[:'access-token']]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
test 'token-type is Bearer' do
|
|
66
|
+
assert_equal 'Bearer', @headers[DeviseTokenAuth.headers_names[:'token-type']]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '#build_bearer_token' do
|
|
71
|
+
before do
|
|
72
|
+
@resource = build_confirmed_user
|
|
73
|
+
@auth = @resource.create_new_auth_token
|
|
74
|
+
@client_id = @auth['client']
|
|
75
|
+
@token = @auth['access-token']
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
test 'returns a hash with an Authorization key when cookies are disabled' do
|
|
79
|
+
DeviseTokenAuth.cookie_enabled = false
|
|
80
|
+
auth_payload = { 'uid' => @resource.uid }
|
|
81
|
+
result = @resource.build_bearer_token(auth_payload)
|
|
82
|
+
assert result.key?(DeviseTokenAuth.headers_names[:'authorization'])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
test 'Authorization value starts with "Bearer "' do
|
|
86
|
+
DeviseTokenAuth.cookie_enabled = false
|
|
87
|
+
auth_payload = { 'uid' => @resource.uid }
|
|
88
|
+
result = @resource.build_bearer_token(auth_payload)
|
|
89
|
+
assert result[DeviseTokenAuth.headers_names[:'authorization']].start_with?('Bearer ')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test 'returns an empty hash when cookie_enabled is true' do
|
|
93
|
+
DeviseTokenAuth.cookie_enabled = true
|
|
94
|
+
auth_payload = { 'uid' => @resource.uid }
|
|
95
|
+
result = @resource.build_bearer_token(auth_payload)
|
|
96
|
+
assert_empty result
|
|
97
|
+
ensure
|
|
98
|
+
DeviseTokenAuth.cookie_enabled = false
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# -------------------------------------------------------------------------
|
|
103
|
+
# confirmed?
|
|
104
|
+
# -------------------------------------------------------------------------
|
|
105
|
+
describe '#confirmed?' do
|
|
106
|
+
test 'returns true for a user without :confirmable devise module' do
|
|
107
|
+
# User does not include :confirmable, so confirmed? must return true.
|
|
108
|
+
resource = build_confirmed_user
|
|
109
|
+
refute resource.devise_modules.include?(:confirmable)
|
|
110
|
+
assert resource.confirmed?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
114
|
+
test 'returns true for a ConfirmableUser that has been confirmed' do
|
|
115
|
+
resource = create(:confirmable_user)
|
|
116
|
+
resource.confirm
|
|
117
|
+
assert resource.confirmed?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
test 'returns false for a ConfirmableUser that has not been confirmed' do
|
|
121
|
+
resource = create(:confirmable_user)
|
|
122
|
+
# Reload to drop any in-memory state set during creation
|
|
123
|
+
resource.reload
|
|
124
|
+
refute resource.confirmed?
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# -------------------------------------------------------------------------
|
|
130
|
+
# token_validation_response
|
|
131
|
+
# -------------------------------------------------------------------------
|
|
132
|
+
describe '#token_validation_response' do
|
|
133
|
+
before do
|
|
134
|
+
@resource = build_confirmed_user
|
|
135
|
+
@response = @resource.token_validation_response
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
test 'returns a hash' do
|
|
139
|
+
assert_kind_of Hash, @response
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
test 'does not include :tokens key' do
|
|
143
|
+
refute @response.key?('tokens'), 'tokens should be excluded from token_validation_response'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
test 'does not include :created_at key' do
|
|
147
|
+
refute @response.key?('created_at'), 'created_at should be excluded'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
test 'does not include :updated_at key' do
|
|
151
|
+
refute @response.key?('updated_at'), 'updated_at should be excluded'
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
test 'includes the email key' do
|
|
155
|
+
assert @response.key?('email')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
test 'includes the uid key' do
|
|
159
|
+
assert @response.key?('uid')
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# -------------------------------------------------------------------------
|
|
164
|
+
# extend_batch_buffer
|
|
165
|
+
# -------------------------------------------------------------------------
|
|
166
|
+
describe '#extend_batch_buffer' do
|
|
167
|
+
before do
|
|
168
|
+
@resource = build_confirmed_user
|
|
169
|
+
@auth = @resource.create_new_auth_token
|
|
170
|
+
@client_id = @auth['client']
|
|
171
|
+
@token = @auth['access-token']
|
|
172
|
+
age_token(@resource, @client_id)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
test 'returns a hash with auth header keys' do
|
|
176
|
+
result = @resource.extend_batch_buffer(@token, @client_id)
|
|
177
|
+
assert result.key?(DeviseTokenAuth.headers_names[:'access-token'])
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
test 'updates the updated_at timestamp for the client token' do
|
|
181
|
+
# age_token set updated_at to the past; extend_batch_buffer should
|
|
182
|
+
# refresh it to approximately now.
|
|
183
|
+
@resource.extend_batch_buffer(@token, @client_id)
|
|
184
|
+
updated_at = @resource.tokens[@client_id]['updated_at']
|
|
185
|
+
# updated_at should now be within the last 5 seconds
|
|
186
|
+
assert updated_at.to_time >= Time.zone.now - 5.seconds,
|
|
187
|
+
'updated_at should be refreshed to approximately now by extend_batch_buffer'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
test 'persists the token record to the database' do
|
|
191
|
+
@resource.extend_batch_buffer(@token, @client_id)
|
|
192
|
+
reloaded = @resource.class.find(@resource.id)
|
|
193
|
+
assert reloaded.tokens[@client_id]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# -------------------------------------------------------------------------
|
|
198
|
+
# does_token_match?
|
|
199
|
+
# -------------------------------------------------------------------------
|
|
200
|
+
describe '#does_token_match?' do
|
|
201
|
+
before do
|
|
202
|
+
@resource = build_confirmed_user
|
|
203
|
+
@auth = @resource.create_new_auth_token
|
|
204
|
+
@client_id = @auth['client']
|
|
205
|
+
@token = @auth['access-token']
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
test 'returns false when token_hash is nil' do
|
|
209
|
+
refute @resource.does_token_match?(nil, @token)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
test 'returns false when token does not match' do
|
|
213
|
+
token_hash = @resource.tokens[@client_id]['token']
|
|
214
|
+
refute @resource.does_token_match?(token_hash, 'wrong_token')
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
test 'returns true when token matches its hash' do
|
|
218
|
+
# Obtain the raw token before it gets rotated; use the original
|
|
219
|
+
# auth headers directly since create_new_auth_token returns the
|
|
220
|
+
# plaintext token in the headers hash.
|
|
221
|
+
token_hash = @resource.tokens[@client_id]['token']
|
|
222
|
+
assert @resource.does_token_match?(token_hash, @token)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# -------------------------------------------------------------------------
|
|
227
|
+
# DeviseTokenAuth::Concerns::User.tokens_match? (class-level)
|
|
228
|
+
# -------------------------------------------------------------------------
|
|
229
|
+
describe '.tokens_match?' do
|
|
230
|
+
test 'returns truthy when hash matches token' do
|
|
231
|
+
raw = DeviseTokenAuth::TokenFactory.create
|
|
232
|
+
assert DeviseTokenAuth::Concerns::User.tokens_match?(raw.token_hash, raw.token)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
test 'returns falsy when hash does not match token' do
|
|
236
|
+
raw = DeviseTokenAuth::TokenFactory.create
|
|
237
|
+
refute DeviseTokenAuth::Concerns::User.tokens_match?(raw.token_hash, 'wrong')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
test 'populates and reuses the equality cache' do
|
|
241
|
+
raw = DeviseTokenAuth::TokenFactory.create
|
|
242
|
+
# Reset the cache to isolate this test
|
|
243
|
+
DeviseTokenAuth::Concerns::User.instance_variable_set(:@token_equality_cache, nil)
|
|
244
|
+
|
|
245
|
+
# First call — cache is empty, result computed from BCrypt
|
|
246
|
+
first = DeviseTokenAuth::Concerns::User.tokens_match?(raw.token_hash, raw.token)
|
|
247
|
+
cache_after_first = DeviseTokenAuth::Concerns::User.instance_variable_get(:@token_equality_cache)
|
|
248
|
+
|
|
249
|
+
assert first, 'Expected tokens_match? to return truthy for a matching pair'
|
|
250
|
+
assert_equal 1, cache_after_first.size, 'Cache should contain one entry after first call'
|
|
251
|
+
|
|
252
|
+
# Second call — same inputs, result served from cache (no BCrypt re-computation)
|
|
253
|
+
second = DeviseTokenAuth::Concerns::User.tokens_match?(raw.token_hash, raw.token)
|
|
254
|
+
cache_after_second = DeviseTokenAuth::Concerns::User.instance_variable_get(:@token_equality_cache)
|
|
255
|
+
|
|
256
|
+
assert second, 'Cached result should also be truthy'
|
|
257
|
+
assert_equal 1, cache_after_second.size, 'Cache size should remain 1 (no duplicate entry)'
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# -------------------------------------------------------------------------
|
|
262
|
+
# should_remove_tokens_after_password_reset? / remove_tokens_after_password_reset
|
|
263
|
+
# -------------------------------------------------------------------------
|
|
264
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
265
|
+
describe '#should_remove_tokens_after_password_reset?' do
|
|
266
|
+
before do
|
|
267
|
+
@resource = build_confirmed_user
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
test 'returns false when remove_tokens_after_password_reset is false' do
|
|
271
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = false
|
|
272
|
+
refute @resource.send(:should_remove_tokens_after_password_reset?)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
test 'returns false when remove_tokens_after_password_reset is true but password has not changed' do
|
|
276
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = true
|
|
277
|
+
# Reload so there is no pending change
|
|
278
|
+
@resource.reload
|
|
279
|
+
refute @resource.send(:should_remove_tokens_after_password_reset?)
|
|
280
|
+
ensure
|
|
281
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = false
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
test 'returns true when remove_tokens_after_password_reset is true and password changed' do
|
|
285
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = true
|
|
286
|
+
@resource.password = @resource.password_confirmation = 'NewSecret999!'
|
|
287
|
+
assert @resource.send(:should_remove_tokens_after_password_reset?)
|
|
288
|
+
ensure
|
|
289
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = false
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
describe '#remove_tokens_after_password_reset' do
|
|
294
|
+
before do
|
|
295
|
+
@resource = build_confirmed_user
|
|
296
|
+
# Create multiple tokens on multiple clients
|
|
297
|
+
3.times { @resource.create_new_auth_token }
|
|
298
|
+
@resource.save!
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
test 'keeps only the most recent token when password changes and setting is enabled' do
|
|
302
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = true
|
|
303
|
+
|
|
304
|
+
# Simulate a password change
|
|
305
|
+
@resource.password = @resource.password_confirmation = 'NewSecret999!'
|
|
306
|
+
@resource.save!
|
|
307
|
+
|
|
308
|
+
assert_equal 1, @resource.tokens.count,
|
|
309
|
+
'All but the most-recent token should be removed after password reset'
|
|
310
|
+
ensure
|
|
311
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = false
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
test 'does not alter tokens when setting is disabled' do
|
|
315
|
+
DeviseTokenAuth.remove_tokens_after_password_reset = false
|
|
316
|
+
count_before = @resource.tokens.count
|
|
317
|
+
|
|
318
|
+
@resource.password = @resource.password_confirmation = 'NewSecret999!'
|
|
319
|
+
@resource.save!
|
|
320
|
+
|
|
321
|
+
assert_equal count_before, @resource.tokens.count
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
# Unit tests for DeviseTokenAuth::Concerns::UserOmniauthCallbacks.
|
|
6
|
+
#
|
|
7
|
+
# The concern is included in every model that includes
|
|
8
|
+
# DeviseTokenAuth::Concerns::User. It is responsible for:
|
|
9
|
+
# - Keeping uid in sync with email for email-provider users (sync_uid).
|
|
10
|
+
# - Validating uid presence for OAuth providers.
|
|
11
|
+
# - Providing the email_provider? and uid_and_provider_defined? helpers.
|
|
12
|
+
class UserOmniauthCallbacksConcernTest < ActiveSupport::TestCase
|
|
13
|
+
describe DeviseTokenAuth::Concerns::UserOmniauthCallbacks do
|
|
14
|
+
# -------------------------------------------------------------------------
|
|
15
|
+
# #uid_and_provider_defined?
|
|
16
|
+
# -------------------------------------------------------------------------
|
|
17
|
+
describe '#uid_and_provider_defined?' do
|
|
18
|
+
test 'returns true for User (which has provider and uid columns)' do
|
|
19
|
+
resource = User.new
|
|
20
|
+
assert resource.send(:uid_and_provider_defined?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
24
|
+
test 'returns true for MultiEmailUser' do
|
|
25
|
+
resource = MultiEmailUser.new
|
|
26
|
+
assert resource.send(:uid_and_provider_defined?)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# -------------------------------------------------------------------------
|
|
32
|
+
# #email_provider?
|
|
33
|
+
# -------------------------------------------------------------------------
|
|
34
|
+
describe '#email_provider?' do
|
|
35
|
+
test 'returns true when provider is "email"' do
|
|
36
|
+
resource = User.new(provider: 'email')
|
|
37
|
+
assert resource.send(:email_provider?)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test 'returns false when provider is not "email"' do
|
|
41
|
+
resource = User.new(provider: 'facebook')
|
|
42
|
+
refute resource.send(:email_provider?)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
test 'returns false when provider is nil' do
|
|
46
|
+
resource = User.new(provider: nil)
|
|
47
|
+
refute resource.send(:email_provider?)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# -------------------------------------------------------------------------
|
|
52
|
+
# sync_uid (via before_save / before_create callbacks)
|
|
53
|
+
# -------------------------------------------------------------------------
|
|
54
|
+
describe 'sync_uid' do
|
|
55
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
56
|
+
describe 'email-provider user' do
|
|
57
|
+
test 'uid is set to email when creating a new email-provider user' do
|
|
58
|
+
email = Faker::Internet.unique.email
|
|
59
|
+
resource = create(:user, email: email, provider: 'email')
|
|
60
|
+
assert_equal email, resource.uid,
|
|
61
|
+
'uid should be synced from email on creation'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
test 'uid is updated when email changes on an existing user' do
|
|
65
|
+
resource = create(:user, :confirmed)
|
|
66
|
+
new_email = Faker::Internet.unique.email
|
|
67
|
+
resource.email = new_email
|
|
68
|
+
resource.save(validate: false)
|
|
69
|
+
assert_equal new_email, resource.uid,
|
|
70
|
+
'uid should stay in sync with email after update'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'OAuth provider user' do
|
|
75
|
+
test 'uid is NOT overwritten with email for non-email providers' do
|
|
76
|
+
uid = '12345'
|
|
77
|
+
resource = build(:user, :facebook, uid: uid)
|
|
78
|
+
resource.save(validate: false)
|
|
79
|
+
assert_equal uid, resource.uid,
|
|
80
|
+
'uid should remain unchanged for OAuth providers'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
# Validation: email presence (email provider)
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
describe 'email presence validation' do
|
|
90
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
91
|
+
test 'fails to save email-provider user without email' do
|
|
92
|
+
resource = User.new(provider: 'email', uid: '', password: 'secret123',
|
|
93
|
+
password_confirmation: 'secret123')
|
|
94
|
+
refute resource.save
|
|
95
|
+
assert resource.errors[:email].any?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test 'saves OAuth user without email' do
|
|
99
|
+
resource = User.new(provider: 'facebook', uid: '999', email: nil)
|
|
100
|
+
# skip uniqueness / other validations to test only email presence
|
|
101
|
+
resource.password = resource.password_confirmation = 'secret123'
|
|
102
|
+
result = resource.save(validate: false)
|
|
103
|
+
assert result
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# -------------------------------------------------------------------------
|
|
109
|
+
# Validation: uid presence (OAuth provider)
|
|
110
|
+
# -------------------------------------------------------------------------
|
|
111
|
+
describe 'uid presence validation for OAuth users' do
|
|
112
|
+
if DEVISE_TOKEN_AUTH_ORM == :active_record
|
|
113
|
+
test 'fails to save OAuth user without uid' do
|
|
114
|
+
resource = User.new(provider: 'facebook', uid: nil)
|
|
115
|
+
resource.password = resource.password_confirmation = 'secret123'
|
|
116
|
+
resource.save
|
|
117
|
+
assert resource.errors[:uid].any?,
|
|
118
|
+
'Expected a validation error on :uid for OAuth users with nil uid'
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise_token_auth_multi_email
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lynn Hurley
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-04-
|
|
12
|
+
date: 2026-04-09 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rails
|
|
@@ -275,6 +275,8 @@ files:
|
|
|
275
275
|
- test/controllers/demo_group_controller_test.rb
|
|
276
276
|
- test/controllers/demo_mang_controller_test.rb
|
|
277
277
|
- test/controllers/demo_user_controller_test.rb
|
|
278
|
+
- test/controllers/devise_token_auth/concerns/resource_finder_test.rb
|
|
279
|
+
- test/controllers/devise_token_auth/concerns/set_user_by_token_test.rb
|
|
278
280
|
- test/controllers/devise_token_auth/confirmations_controller_test.rb
|
|
279
281
|
- test/controllers/devise_token_auth/multi_email_coexistence_test.rb
|
|
280
282
|
- test/controllers/devise_token_auth/multi_email_confirmations_controller_test.rb
|
|
@@ -389,10 +391,13 @@ files:
|
|
|
389
391
|
- test/lib/generators/devise_token_auth/install_views_generator_test.rb
|
|
390
392
|
- test/models/concerns/mongoid_support_test.rb
|
|
391
393
|
- test/models/concerns/tokens_serialization_test.rb
|
|
394
|
+
- test/models/confirmable_support_concern_test.rb
|
|
392
395
|
- test/models/confirmable_user_test.rb
|
|
393
396
|
- test/models/multi_email_user_email_test.rb
|
|
394
397
|
- test/models/multi_email_user_test.rb
|
|
395
398
|
- test/models/only_email_user_test.rb
|
|
399
|
+
- test/models/user_concern_methods_test.rb
|
|
400
|
+
- test/models/user_omniauth_callbacks_concern_test.rb
|
|
396
401
|
- test/models/user_test.rb
|
|
397
402
|
- test/support/controllers/routes.rb
|
|
398
403
|
- test/test_helper.rb
|
|
@@ -430,6 +435,8 @@ test_files:
|
|
|
430
435
|
- test/controllers/demo_group_controller_test.rb
|
|
431
436
|
- test/controllers/demo_mang_controller_test.rb
|
|
432
437
|
- test/controllers/demo_user_controller_test.rb
|
|
438
|
+
- test/controllers/devise_token_auth/concerns/resource_finder_test.rb
|
|
439
|
+
- test/controllers/devise_token_auth/concerns/set_user_by_token_test.rb
|
|
433
440
|
- test/controllers/devise_token_auth/confirmations_controller_test.rb
|
|
434
441
|
- test/controllers/devise_token_auth/multi_email_coexistence_test.rb
|
|
435
442
|
- test/controllers/devise_token_auth/multi_email_confirmations_controller_test.rb
|
|
@@ -544,10 +551,13 @@ test_files:
|
|
|
544
551
|
- test/lib/generators/devise_token_auth/install_views_generator_test.rb
|
|
545
552
|
- test/models/concerns/mongoid_support_test.rb
|
|
546
553
|
- test/models/concerns/tokens_serialization_test.rb
|
|
554
|
+
- test/models/confirmable_support_concern_test.rb
|
|
547
555
|
- test/models/confirmable_user_test.rb
|
|
548
556
|
- test/models/multi_email_user_email_test.rb
|
|
549
557
|
- test/models/multi_email_user_test.rb
|
|
550
558
|
- test/models/only_email_user_test.rb
|
|
559
|
+
- test/models/user_concern_methods_test.rb
|
|
560
|
+
- test/models/user_omniauth_callbacks_concern_test.rb
|
|
551
561
|
- test/models/user_test.rb
|
|
552
562
|
- test/support/controllers/routes.rb
|
|
553
563
|
- test/test_helper.rb
|