graphql_devise 0.14.0 → 0.16.0
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/.circleci/config.yml +118 -0
- data/Appraisals +39 -5
- data/CHANGELOG.md +68 -6
- data/README.md +150 -51
- data/Rakefile +2 -1
- data/app/controllers/graphql_devise/concerns/additional_controller_methods.rb +72 -0
- data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +5 -27
- data/app/controllers/graphql_devise/graphql_controller.rb +1 -1
- data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
- data/app/models/graphql_devise/concerns/additional_model_methods.rb +21 -0
- data/app/models/graphql_devise/concerns/model.rb +6 -9
- data/docs/usage/reset_password_flow.md +90 -0
- data/graphql_devise.gemspec +2 -2
- data/lib/generators/graphql_devise/install_generator.rb +1 -1
- data/lib/graphql_devise.rb +20 -6
- data/lib/graphql_devise/concerns/controller_methods.rb +3 -3
- data/lib/graphql_devise/mount_method/operation_preparer.rb +6 -6
- data/lib/graphql_devise/mount_method/operation_preparers/custom_operation_preparer.rb +6 -4
- data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +6 -4
- data/lib/graphql_devise/mount_method/operation_preparers/{resource_name_setter.rb → resource_klass_setter.rb} +4 -4
- data/lib/graphql_devise/resolvers/confirm_account.rb +1 -1
- data/lib/graphql_devise/resource_loader.rb +26 -11
- data/lib/graphql_devise/schema_plugin.rb +41 -18
- data/lib/graphql_devise/version.rb +1 -1
- data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +13 -2
- data/spec/dummy/app/graphql/dummy_schema.rb +4 -3
- data/spec/dummy/app/graphql/types/query_type.rb +5 -0
- data/spec/dummy/config/routes.rb +2 -1
- data/spec/dummy/db/migrate/20200623003142_create_schema_users.rb +0 -1
- data/spec/dummy/db/migrate/20210516211417_add_vip_to_users.rb +5 -0
- data/spec/dummy/db/schema.rb +4 -4
- data/spec/generators/graphql_devise/install_generator_spec.rb +1 -1
- data/spec/graphql/user_queries_spec.rb +120 -0
- data/spec/requests/graphql_controller_spec.rb +12 -11
- data/spec/requests/queries/introspection_query_spec.rb +149 -0
- data/spec/requests/user_controller_spec.rb +93 -32
- data/spec/services/mount_method/operation_preparer_spec.rb +5 -5
- data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +5 -5
- data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +5 -5
- data/spec/services/mount_method/operation_preparers/{resource_name_setter_spec.rb → resource_klass_setter_spec.rb} +6 -6
- data/spec/services/resource_loader_spec.rb +5 -5
- data/spec/support/contexts/graphql_request.rb +11 -3
- data/spec/support/contexts/schema_test.rb +14 -0
- metadata +25 -14
- data/.travis.yml +0 -79
@@ -6,20 +6,13 @@ RSpec.describe GraphqlDevise::GraphqlController do
|
|
6
6
|
let(:password) { 'password123' }
|
7
7
|
let(:user) { create(:user, :confirmed, password: password) }
|
8
8
|
let(:params) { { query: query, variables: variables } }
|
9
|
-
let(:request_params) do
|
10
|
-
if Rails::VERSION::MAJOR >= 5
|
11
|
-
{ params: params }
|
12
|
-
else
|
13
|
-
params
|
14
|
-
end
|
15
|
-
end
|
16
9
|
|
17
10
|
context 'when variables are a string' do
|
18
11
|
let(:variables) { "{\"email\": \"#{user.email}\"}" }
|
19
12
|
let(:query) { "mutation($email: String!) { userLogin(email: $email, password: \"#{password}\") { user { email name signInCount } } }" }
|
20
13
|
|
21
14
|
it 'parses the string variables' do
|
22
|
-
|
15
|
+
post_request('/api/v1/graphql_auth')
|
23
16
|
|
24
17
|
expect(json_response).to match(
|
25
18
|
data: { userLogin: { user: { email: user.email, name: user.name, signInCount: 1 } } }
|
@@ -31,7 +24,7 @@ RSpec.describe GraphqlDevise::GraphqlController do
|
|
31
24
|
let(:query) { "mutation { userLogin(email: \"#{user.email}\", password: \"#{password}\") { user { email name signInCount } } }" }
|
32
25
|
|
33
26
|
it 'returns an empty hash as variables' do
|
34
|
-
|
27
|
+
post_request('/api/v1/graphql_auth')
|
35
28
|
|
36
29
|
expect(json_response).to match(
|
37
30
|
data: { userLogin: { user: { email: user.email, name: user.name, signInCount: 1 } } }
|
@@ -46,7 +39,7 @@ RSpec.describe GraphqlDevise::GraphqlController do
|
|
46
39
|
|
47
40
|
it 'raises an error' do
|
48
41
|
expect do
|
49
|
-
|
42
|
+
post_request('/api/v1/graphql_auth')
|
50
43
|
end.to raise_error(ArgumentError)
|
51
44
|
end
|
52
45
|
end
|
@@ -62,7 +55,7 @@ RSpec.describe GraphqlDevise::GraphqlController do
|
|
62
55
|
end
|
63
56
|
|
64
57
|
it 'executes multiple queries in the same request' do
|
65
|
-
|
58
|
+
post_request('/api/v1/graphql_auth')
|
66
59
|
|
67
60
|
expect(json_response).to match(
|
68
61
|
[
|
@@ -79,4 +72,12 @@ RSpec.describe GraphqlDevise::GraphqlController do
|
|
79
72
|
)
|
80
73
|
end
|
81
74
|
end
|
75
|
+
|
76
|
+
def post_request(path)
|
77
|
+
if Rails::VERSION::MAJOR >= 5
|
78
|
+
post(path, params: params)
|
79
|
+
else
|
80
|
+
post(path, params)
|
81
|
+
end
|
82
|
+
end
|
82
83
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Login Requests' do
|
6
|
+
include_context 'with graphql query request'
|
7
|
+
|
8
|
+
let(:query) do
|
9
|
+
<<-GRAPHQL
|
10
|
+
query IntrospectionQuery {
|
11
|
+
__schema {
|
12
|
+
queryType { name }
|
13
|
+
mutationType { name }
|
14
|
+
subscriptionType { name }
|
15
|
+
types {
|
16
|
+
...FullType
|
17
|
+
}
|
18
|
+
directives {
|
19
|
+
name
|
20
|
+
description
|
21
|
+
args {
|
22
|
+
...InputValue
|
23
|
+
}
|
24
|
+
onOperation
|
25
|
+
onFragment
|
26
|
+
onField
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
fragment FullType on __Type {
|
32
|
+
kind
|
33
|
+
name
|
34
|
+
description
|
35
|
+
fields(includeDeprecated: true) {
|
36
|
+
name
|
37
|
+
description
|
38
|
+
args {
|
39
|
+
...InputValue
|
40
|
+
}
|
41
|
+
type {
|
42
|
+
...TypeRef
|
43
|
+
}
|
44
|
+
isDeprecated
|
45
|
+
deprecationReason
|
46
|
+
}
|
47
|
+
inputFields {
|
48
|
+
...InputValue
|
49
|
+
}
|
50
|
+
interfaces {
|
51
|
+
...TypeRef
|
52
|
+
}
|
53
|
+
enumValues(includeDeprecated: true) {
|
54
|
+
name
|
55
|
+
description
|
56
|
+
isDeprecated
|
57
|
+
deprecationReason
|
58
|
+
}
|
59
|
+
possibleTypes {
|
60
|
+
...TypeRef
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
fragment InputValue on __InputValue {
|
65
|
+
name
|
66
|
+
description
|
67
|
+
type { ...TypeRef }
|
68
|
+
defaultValue
|
69
|
+
}
|
70
|
+
|
71
|
+
fragment TypeRef on __Type {
|
72
|
+
kind
|
73
|
+
name
|
74
|
+
ofType {
|
75
|
+
kind
|
76
|
+
name
|
77
|
+
ofType {
|
78
|
+
kind
|
79
|
+
name
|
80
|
+
ofType {
|
81
|
+
kind
|
82
|
+
name
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
GRAPHQL
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when using a schema plugin to mount devise operations' do
|
92
|
+
context 'when schema plugin is set to authenticate by default' do
|
93
|
+
context 'when the resource is authenticated' do
|
94
|
+
let(:user) { create(:user, :confirmed) }
|
95
|
+
let(:headers) { user.create_new_auth_token }
|
96
|
+
|
97
|
+
it 'return the schema information' do
|
98
|
+
post_request('/api/v1/graphql')
|
99
|
+
|
100
|
+
expect(json_response[:data][:__schema].keys).to contain_exactly(
|
101
|
+
:queryType, :mutationType, :subscriptionType, :types, :directives
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when the resource is *NOT* authenticated' do
|
107
|
+
context 'and instrospection is set to be public' do
|
108
|
+
it 'return the schema information' do
|
109
|
+
post_request('/api/v1/graphql')
|
110
|
+
|
111
|
+
expect(json_response[:data][:__schema].keys).to contain_exactly(
|
112
|
+
:queryType, :mutationType, :subscriptionType, :types, :directives
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'and introspection is set to require auth' do
|
118
|
+
before do
|
119
|
+
allow_any_instance_of(GraphqlDevise::SchemaPlugin).to(
|
120
|
+
receive(:public_introspection).and_return(false)
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'return an error' do
|
125
|
+
post_request('/api/v1/graphql')
|
126
|
+
|
127
|
+
expect(json_response[:data]).to be_nil
|
128
|
+
expect(json_response[:errors]).to contain_exactly(
|
129
|
+
hash_including(
|
130
|
+
message: '__schema field requires authentication',
|
131
|
+
extensions: { code: 'AUTHENTICATION_ERROR' }
|
132
|
+
)
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when schema plugin is set *NOT* to authenticate by default' do
|
140
|
+
it 'return the schema information' do
|
141
|
+
post_request('/api/v1/interpreter')
|
142
|
+
|
143
|
+
expect(json_response[:data][:__schema].keys).to contain_exactly(
|
144
|
+
:queryType, :mutationType, :subscriptionType, :types, :directives
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -5,6 +5,14 @@ require 'rails_helper'
|
|
5
5
|
RSpec.describe "Integrations with the user's controller" do
|
6
6
|
include_context 'with graphql query request'
|
7
7
|
|
8
|
+
shared_examples 'returns a must authenticate error' do |field|
|
9
|
+
it 'returns a must sign in error' do
|
10
|
+
expect(json_response[:errors]).to contain_exactly(
|
11
|
+
hash_including(message: "#{field} field requires authentication", extensions: { code: 'AUTHENTICATION_ERROR' })
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
8
16
|
let(:user) { create(:user, :confirmed) }
|
9
17
|
|
10
18
|
describe 'publicField' do
|
@@ -31,15 +39,6 @@ RSpec.describe "Integrations with the user's controller" do
|
|
31
39
|
expect(json_response[:data][:publicField]).to eq('Field does not require authentication')
|
32
40
|
end
|
33
41
|
end
|
34
|
-
|
35
|
-
context 'when using the failing route' do
|
36
|
-
it 'raises an invalid resource_name error' do
|
37
|
-
expect { post_request('/api/v1/failing') }.to raise_error(
|
38
|
-
GraphqlDevise::Error,
|
39
|
-
'Invalid resource_name `fail` provided to `graphql_context`. Possible values are: [:user, :admin, :guest, :users_customer, :schema_user].'
|
40
|
-
)
|
41
|
-
end
|
42
|
-
end
|
43
42
|
end
|
44
43
|
|
45
44
|
describe 'privateField' do
|
@@ -51,6 +50,22 @@ RSpec.describe "Integrations with the user's controller" do
|
|
51
50
|
GRAPHQL
|
52
51
|
end
|
53
52
|
|
53
|
+
context 'when authenticating before using the GQL schema' do
|
54
|
+
before { post_request('/api/v1/controller_auth') }
|
55
|
+
|
56
|
+
context 'when user is authenticated' do
|
57
|
+
let(:headers) { create(:schema_user).create_new_auth_token }
|
58
|
+
|
59
|
+
it 'allows authentication at the controller level' do
|
60
|
+
expect(json_response[:data][:privateField]).to eq('Field will always require authentication')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when user is not authenticated' do
|
65
|
+
it_behaves_like 'returns a must authenticate error', 'privateField'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
54
69
|
context 'when using a regular schema' do
|
55
70
|
before { post_request('/api/v1/graphql') }
|
56
71
|
|
@@ -71,9 +86,14 @@ RSpec.describe "Integrations with the user's controller" do
|
|
71
86
|
end
|
72
87
|
|
73
88
|
context 'when user is not authenticated' do
|
74
|
-
|
75
|
-
|
76
|
-
|
89
|
+
it_behaves_like 'returns a must authenticate error', 'privateField'
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when using the failing route' do
|
93
|
+
it 'raises an invalid resource_name error' do
|
94
|
+
expect { post_request('/api/v1/failing') }.to raise_error(
|
95
|
+
GraphqlDevise::Error,
|
96
|
+
'Invalid resource_name `fail` provided to `graphql_context`. Possible values are: [:user, :admin, :guest, :users_customer, :schema_user].'
|
77
97
|
)
|
78
98
|
end
|
79
99
|
end
|
@@ -91,11 +111,7 @@ RSpec.describe "Integrations with the user's controller" do
|
|
91
111
|
end
|
92
112
|
|
93
113
|
context 'when user is not authenticated' do
|
94
|
-
|
95
|
-
expect(json_response[:errors]).to contain_exactly(
|
96
|
-
hash_including(message: 'privateField field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
|
97
|
-
)
|
98
|
-
end
|
114
|
+
it_behaves_like 'returns a must authenticate error', 'privateField'
|
99
115
|
end
|
100
116
|
end
|
101
117
|
end
|
@@ -121,11 +137,7 @@ RSpec.describe "Integrations with the user's controller" do
|
|
121
137
|
end
|
122
138
|
|
123
139
|
context 'when user is not authenticated' do
|
124
|
-
|
125
|
-
expect(json_response[:errors]).to contain_exactly(
|
126
|
-
hash_including(message: 'dummyMutation field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
|
127
|
-
)
|
128
|
-
end
|
140
|
+
it_behaves_like 'returns a must authenticate error', 'dummyMutation'
|
129
141
|
end
|
130
142
|
end
|
131
143
|
|
@@ -141,11 +153,7 @@ RSpec.describe "Integrations with the user's controller" do
|
|
141
153
|
end
|
142
154
|
|
143
155
|
context 'when user is not authenticated' do
|
144
|
-
|
145
|
-
expect(json_response[:errors]).to contain_exactly(
|
146
|
-
hash_including(message: 'dummyMutation field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
|
147
|
-
)
|
148
|
-
end
|
156
|
+
it_behaves_like 'returns a must authenticate error', 'dummyMutation'
|
149
157
|
end
|
150
158
|
end
|
151
159
|
end
|
@@ -179,11 +187,7 @@ RSpec.describe "Integrations with the user's controller" do
|
|
179
187
|
end
|
180
188
|
|
181
189
|
context 'when user is not authenticated' do
|
182
|
-
|
183
|
-
expect(json_response[:errors]).to contain_exactly(
|
184
|
-
hash_including(message: 'user field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
|
185
|
-
)
|
186
|
-
end
|
190
|
+
it_behaves_like 'returns a must authenticate error', 'user'
|
187
191
|
end
|
188
192
|
end
|
189
193
|
|
@@ -251,4 +255,61 @@ RSpec.describe "Integrations with the user's controller" do
|
|
251
255
|
)
|
252
256
|
end
|
253
257
|
end
|
258
|
+
|
259
|
+
describe 'vipField' do
|
260
|
+
let(:error_message) { 'Field available only for VIP Users' }
|
261
|
+
let(:query) do
|
262
|
+
<<-GRAPHQL
|
263
|
+
query { vipField }
|
264
|
+
GRAPHQL
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'when using a regular schema' do
|
268
|
+
before { post_request('/api/v1/graphql') }
|
269
|
+
|
270
|
+
context 'when user is authenticated' do
|
271
|
+
let(:headers) { user.create_new_auth_token }
|
272
|
+
|
273
|
+
context 'when schema user is VIP' do
|
274
|
+
let(:user) { create(:user, :confirmed, vip: true) }
|
275
|
+
|
276
|
+
it 'allows to perform the query' do
|
277
|
+
expect(json_response[:data][:vipField]).to eq(error_message)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
context 'when schema user is not VIP' do
|
282
|
+
it_behaves_like 'returns a must authenticate error', 'vipField'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'when user is not authenticated' do
|
287
|
+
it_behaves_like 'returns a must authenticate error', 'vipField'
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context 'when using the interpreter schema' do
|
292
|
+
before { post_request('/api/v1/interpreter') }
|
293
|
+
|
294
|
+
context 'when user is authenticated' do
|
295
|
+
let(:headers) { user.create_new_auth_token }
|
296
|
+
|
297
|
+
context 'when schema user is VIP' do
|
298
|
+
let(:user) { create(:user, :confirmed, vip: true) }
|
299
|
+
|
300
|
+
it 'allows to perform the query' do
|
301
|
+
expect(json_response[:data][:vipField]).to eq(error_message)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'when schema user is not VIP' do
|
306
|
+
it_behaves_like 'returns a must authenticate error', 'vipField'
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
context 'when user is not authenticated' do
|
311
|
+
it_behaves_like 'returns a must authenticate error', 'vipField'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
254
315
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rails_helper'
|
4
4
|
|
5
5
|
RSpec.describe GraphqlDevise::MountMethod::OperationPreparer do
|
6
6
|
describe '#call' do
|
7
7
|
subject(:prepared_operations) do
|
8
8
|
described_class.new(
|
9
|
-
|
9
|
+
model: model,
|
10
10
|
selected_operations: selected,
|
11
11
|
preparer: preparer,
|
12
12
|
custom: custom,
|
@@ -15,14 +15,14 @@ RSpec.describe GraphqlDevise::MountMethod::OperationPreparer do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
let(:logout_class) { Class.new(GraphQL::Schema::Resolver) }
|
18
|
-
let(:
|
18
|
+
let(:model) { User }
|
19
19
|
let(:preparer) { double(:preparer, call: logout_class) }
|
20
20
|
let(:custom) { { login: double(:custom_login, graphql_name: nil) } }
|
21
21
|
let(:additional) { { user_additional: double(:user_additional) } }
|
22
22
|
let(:selected) do
|
23
23
|
{
|
24
|
-
login:
|
25
|
-
logout:{ klass: logout_class }
|
24
|
+
login: { klass: double(:login_default) },
|
25
|
+
logout: { klass: logout_class }
|
26
26
|
}
|
27
27
|
end
|
28
28
|
|
@@ -1,14 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rails_helper'
|
4
4
|
|
5
5
|
RSpec.describe GraphqlDevise::MountMethod::OperationPreparers::CustomOperationPreparer do
|
6
6
|
describe '#call' do
|
7
|
-
subject(:prepared) { described_class.new(selected_keys: selected_keys, custom_operations: operations,
|
7
|
+
subject(:prepared) { described_class.new(selected_keys: selected_keys, custom_operations: operations, model: model).call }
|
8
8
|
|
9
9
|
let(:login_operation) { double(:confirm_operation, graphql_name: nil) }
|
10
10
|
let(:logout_operation) { double(:sign_up_operation, graphql_name: nil) }
|
11
|
-
let(:
|
11
|
+
let(:model) { User }
|
12
12
|
let(:operations) { { login: login_operation, logout: logout_operation, invalid: double(:invalid) } }
|
13
13
|
let(:selected_keys) { [:login, :logout, :sign_up, :confirm] }
|
14
14
|
|
@@ -22,8 +22,8 @@ RSpec.describe GraphqlDevise::MountMethod::OperationPreparers::CustomOperationPr
|
|
22
22
|
|
23
23
|
prepared
|
24
24
|
|
25
|
-
expect(login_operation.instance_variable_get(:@
|
26
|
-
expect(logout_operation.instance_variable_get(:@
|
25
|
+
expect(login_operation.instance_variable_get(:@resource_klass)).to eq(User)
|
26
|
+
expect(logout_operation.instance_variable_get(:@resource_klass)).to eq(User)
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'when no selected keys are provided' do
|