graphql_devise 0.14.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|