Skip to content

Commit 6898d21

Browse files
committed
Add SchemaPlugin
1 parent 2e63cda commit 6898d21

8 files changed

Lines changed: 132 additions & 25 deletions

File tree

lib/graphql_devise/resource_loader.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ def call(query, mutation)
2828
end
2929

3030
prepared_mutations.each do |action, prepared_mutation|
31-
mutation.field(action, mutation: prepared_mutation)
31+
mutation.field(action, mutation: prepared_mutation, authenticate: false)
3232
end
3333

3434
prepared_resolvers = prepare_resolvers(mapping_name, clean_options, authenticatable_type)
3535

3636
prepared_resolvers.each do |action, resolver|
37-
query.field(action, resolver: resolver)
37+
query.field(action, resolver: resolver, authenticate: false)
3838
end
3939

4040
GraphqlDevise.add_mapping(mapping_name, @resource)
Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,79 @@
11
module GraphqlDevise
22
class SchemaPlugin
3-
def initialize(query:, mutation: nil, resource_loaders: [])
4-
@query = query
5-
@mutation = mutation
6-
@resource_loaders = resource_loaders
3+
DEFAULT_NOT_AUTHENTICATED = ->(field) { raise GraphqlDevise::UserError, "#{field} field requires authentication" }
4+
5+
def initialize(query:, mutation: nil, authenticate_default: true, resource_loaders: [], unauthenticated_proc: DEFAULT_NOT_AUTHENTICATED)
6+
@query = query
7+
@mutation = mutation
8+
@resource_loaders = resource_loaders
9+
@authenticate_default = authenticate_default
10+
@unauthenticated_proc = unauthenticated_proc
711

812
# Must happen on initialize so operations are loaded before the types are added to the schema on GQL < 1.10
913
load_fields
1014
end
1115

16+
def use(schema_definition)
17+
schema_definition.tracer(self)
18+
end
19+
20+
def trace(event, trace_data)
21+
# Authenticate only root level queries
22+
return yield unless event == 'execute_field' && path(trace_data).count == 1
23+
24+
field = traced_field(trace_data)
25+
provided_value = authenticate_option(field, trace_data)
26+
27+
if (!provided_value.nil? && provided_value) || @authenticate_default
28+
raise_on_missing_resource(
29+
context(trace_data),
30+
field
31+
)
32+
end
33+
34+
yield
35+
end
36+
1237
private
1338

39+
def raise_on_missing_resource(context, field)
40+
@unauthenticated_proc.call(field.name) if context[:current_resource].blank?
41+
end
42+
43+
def context(trace_data)
44+
query = if trace_data[:context]
45+
trace_data[:context].query
46+
else
47+
trace_data[:query]
48+
end
49+
50+
query.context
51+
end
52+
53+
def path(trace_data)
54+
if trace_data[:context]
55+
trace_data[:context].path
56+
else
57+
trace_data[:path]
58+
end
59+
end
60+
61+
def traced_field(trace_data)
62+
if trace_data[:context]
63+
trace_data[:context].field
64+
else
65+
trace_data[:field]
66+
end
67+
end
68+
69+
def authenticate_option(field, trace_data)
70+
if trace_data[:context]
71+
field.metadata[:authenticate]
72+
else
73+
field.graphql_definition.metadata[:authenticate]
74+
end
75+
end
76+
1477
def load_fields
1578
@resource_loaders.each do |resource_loader|
1679
raise Error, 'Invalid resource loader instance' unless resource_loader.instance_of?(GraphqlDevise::ResourceLoader)
@@ -20,3 +83,6 @@ def load_fields
2083
end
2184
end
2285
end
86+
87+
GraphQL::Field.accepts_definitions(authenticate: GraphQL::Define.assign_metadata_key(:authenticate))
88+
GraphQL::Schema::Field.accepts_definition(:authenticate)

spec/dummy/app/controllers/api/v1/graphql_controller.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ module V1
33
class GraphqlController < ApplicationController
44
include GraphqlDevise::Concerns::SetUserByToken
55

6-
before_action :authenticate_user!
6+
before_action -> { set_user_by_token(:user) }
77

88
def graphql
9-
render json: DummySchema.execute(params[:query])
9+
render json: DummySchema.execute(params[:query], context: { current_resource: @resource, controller: self })
10+
end
11+
12+
def interpreter
13+
render json: InterpreterSchema.execute(params[:query], context: { current_resource: @resource, controller: self })
1014
end
1115

1216
private
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class DummySchema < GraphQL::Schema
2+
use GraphqlDevise::SchemaPlugin.new(query: Types::QueryType)
3+
24
mutation(Types::MutationType)
35
query(Types::QueryType)
46
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class InterpreterSchema < GraphQL::Schema
2+
use GraphQL::Execution::Interpreter if Gem::Version.new(GraphQL::VERSION) >= Gem::Version.new('1.9.0')
3+
use GraphQL::Analysis::AST if Gem::Version.new(GraphQL::VERSION) >= Gem::Version.new('1.10.0')
4+
5+
use GraphqlDevise::SchemaPlugin.new(query: Types::QueryType)
6+
7+
mutation(Types::MutationType)
8+
query(Types::QueryType)
9+
end

spec/dummy/config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@
2828
)
2929

3030
post '/api/v1/graphql', to: 'api/v1/graphql#graphql'
31+
post '/api/v1/interpreter', to: 'api/v1/graphql#interpreter'
3132
end

spec/requests/user_controller_spec.rb

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,49 @@
1717
GRAPHQL
1818
end
1919

20-
before { post_request('/api/v1/graphql') }
20+
context 'when using a regular schema' do
21+
before { post_request('/api/v1/graphql') }
2122

22-
context 'when user is authenticated' do
23-
let(:headers) { user.create_new_auth_token }
23+
context 'when user is authenticated' do
24+
let(:headers) { user.create_new_auth_token }
2425

25-
it 'allow to perform the query' do
26-
expect(json_response[:data][:user]).to match(
27-
email: user.email,
28-
id: user.id
29-
)
26+
it 'allow to perform the query' do
27+
expect(json_response[:data][:user]).to match(
28+
email: user.email,
29+
id: user.id
30+
)
31+
end
32+
end
33+
34+
context 'when user is not authenticated' do
35+
it 'returns a must sign in error' do
36+
expect(json_response[:errors]).to contain_exactly(
37+
hash_including(message: 'user field requires authentication', extensions: { code: 'USER_ERROR' })
38+
)
39+
end
3040
end
3141
end
3242

33-
context 'when user is not authenticated' do
34-
it 'returns a must sign in error' do
35-
expect(json_response[:errors]).to contain_exactly(
36-
'You need to sign in or sign up before continuing.'
37-
)
43+
context 'when using an interpreter schema' do
44+
before { post_request('/api/v1/interpreter') }
45+
46+
context 'when user is authenticated' do
47+
let(:headers) { user.create_new_auth_token }
48+
49+
it 'allow to perform the query' do
50+
expect(json_response[:data][:user]).to match(
51+
email: user.email,
52+
id: user.id
53+
)
54+
end
55+
end
56+
57+
context 'when user is not authenticated' do
58+
it 'returns a must sign in error' do
59+
expect(json_response[:errors]).to contain_exactly(
60+
hash_including(message: 'user field requires authentication', extensions: { code: 'USER_ERROR' })
61+
)
62+
end
3863
end
3964
end
4065
end

spec/services/resource_loader_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
end
1919

2020
it 'loads operations into the provided types' do
21-
expect(query).to receive(:field).with(:user_confirm_account, resolver: instance_of(Class))
22-
expect(mutation).to receive(:field).with(:user_login, mutation: instance_of(Class))
21+
expect(query).to receive(:field).with(:user_confirm_account, resolver: instance_of(Class), authenticate: false)
22+
expect(mutation).to receive(:field).with(:user_login, mutation: instance_of(Class), authenticate: false)
2323
expect(GraphqlDevise).to receive(:add_mapping).with(:user, resource)
2424
expect(GraphqlDevise).not_to receive(:mount_resource)
2525

@@ -58,8 +58,8 @@
5858
before { allow(GraphqlDevise).to receive(:resource_mounted?).with(:user).and_return(true) }
5959

6060
it 'skips schema loading' do
61-
expect(query).not_to receive(:field).with(:user_confirm_account, resolver: instance_of(Class))
62-
expect(mutation).not_to receive(:field).with(:user_login, mutation: instance_of(Class))
61+
expect(query).not_to receive(:field)
62+
expect(mutation).not_to receive(:field)
6363
expect(GraphqlDevise).not_to receive(:add_mapping).with(:user, resource)
6464
expect(GraphqlDevise).not_to receive(:mount_resource)
6565
end

0 commit comments

Comments
 (0)