Skip to content

Commit 1d101e6

Browse files
authored
Merge pull request #154 from graphql-devise/allow-interpreter-query-to-be-executed-without-auth
Add config for public introspection query on schema plugin
2 parents 6cfbe96 + 401b9e5 commit 1d101e6

3 files changed

Lines changed: 164 additions & 5 deletions

File tree

lib/graphql_devise/schema_plugin.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
module GraphqlDevise
44
class SchemaPlugin
5+
# NOTE: Based on GQL-Ruby docs https://graphql-ruby.org/schema/introspection.html
6+
INTROSPECTION_FIELDS = ['__schema', '__type', '__typename']
57
DEFAULT_NOT_AUTHENTICATED = ->(field) { raise GraphqlDevise::AuthenticationError, "#{field} field requires authentication" }
68

7-
def initialize(query: nil, mutation: nil, authenticate_default: true, resource_loaders: [], unauthenticated_proc: DEFAULT_NOT_AUTHENTICATED)
9+
def initialize(query: nil, mutation: nil, authenticate_default: true, public_introspection: !Rails.env.production?, resource_loaders: [], unauthenticated_proc: DEFAULT_NOT_AUTHENTICATED)
810
@query = query
911
@mutation = mutation
1012
@resource_loaders = resource_loaders
1113
@authenticate_default = authenticate_default
14+
@public_introspection = public_introspection
1215
@unauthenticated_proc = unauthenticated_proc
1316

1417
# Must happen on initialize so operations are loaded before the types are added to the schema on GQL < 1.10
@@ -28,7 +31,7 @@ def trace(event, trace_data)
2831
auth_required = authenticate_option(field, trace_data)
2932
context = context_from_data(trace_data)
3033

31-
if auth_required
34+
if auth_required && !(public_introspection && introspection_field?(field))
3235
context = set_current_resource(context)
3336
raise_on_missing_resource(context, field)
3437
end
@@ -38,6 +41,8 @@ def trace(event, trace_data)
3841

3942
private
4043

44+
attr_reader :public_introspection
45+
4146
def set_current_resource(context)
4247
controller = context[:controller]
4348
resource_names = Array(context[:resource_name])
@@ -109,6 +114,10 @@ def load_fields
109114
resource_loader.call(@query, @mutation)
110115
end
111116
end
117+
118+
def introspection_field?(field)
119+
INTROSPECTION_FIELDS.include?(field.name)
120+
end
112121
end
113122
end
114123

spec/dummy/app/graphql/dummy_schema.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
class DummySchema < GraphQL::Schema
44
use GraphqlDevise::SchemaPlugin.new(
5-
query: Types::QueryType,
6-
mutation: Types::MutationType,
7-
resource_loaders: [
5+
query: Types::QueryType,
6+
mutation: Types::MutationType,
7+
public_introspection: true,
8+
resource_loaders: [
89
GraphqlDevise::ResourceLoader.new(
910
'User',
1011
only: [
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)