Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,99 @@ describe('ParseGraphQLServer', () => {
expect(introspection.data).toBeDefined();
});

it('should have public introspection enabled if enabled via the Parse Server option', async () => {
const parseServer = await reconfigureServer({ graphQLPublicIntrospection: true });
await createGQLFromParseServer(parseServer);

const introspection = await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
});
expect(introspection.data.__schema).toBeDefined();
});

it('should keep "Did you mean" suggestions when public introspection is enabled via the Parse Server option', async () => {
const parseServer = await reconfigureServer({ graphQLPublicIntrospection: true });
await createGQLFromParseServer(parseServer);

try {
await apolloClient.query({
query: gql`
query Typo {
healt
}
`,
});
fail('should have thrown a validation error');
} catch (e) {
const message = e.networkError.result.errors[0].message;
expect(message).toContain('Cannot query field "healt"');
expect(message).toMatch(/Did you mean/);
expect(message).toContain('health');
}
});

it('should prefer the GraphQL server option over the Parse Server option for public introspection', async () => {
const parseServer = await reconfigureServer({ graphQLPublicIntrospection: true });
await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: false });

try {
await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
});
fail('should have thrown an error');
} catch (e) {
expect(e.message).toEqual('Response not successful: Received status code 403');
expect(e.networkError.result.errors[0].message).toEqual('Introspection is not allowed');
}
});

describe('mounted via Parse Server option mountGraphQL', () => {
const introspectionRequest = async () => {
const res = await fetch('http://localhost:8378/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
},
body: JSON.stringify({ query: '{ __schema { types { name } } }' }),
});
return { status: res.status, body: JSON.parse(await res.text()) };
};

it('should have public introspection disabled by default without master key', async () => {
await reconfigureServer({ mountGraphQL: true });
const response = await introspectionRequest();
expect(response.status).toEqual(403);
expect(response.body.data).toBeUndefined();
expect(response.body.errors[0].message).toEqual('Introspection is not allowed');
});

it('should have public introspection enabled if enabled', async () => {
await reconfigureServer({ mountGraphQL: true, graphQLPublicIntrospection: true });
const response = await introspectionRequest();
expect(response.status).toEqual(200);
expect(response.body.errors).toBeUndefined();
expect(response.body.data.__schema).toBeDefined();
});
});

it('should block __type introspection without master key', async () => {
try {
await apolloClient.query({
Expand Down
7 changes: 6 additions & 1 deletion src/GraphQL/ParseGraphQLServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ class ParseGraphQLServer {
const createServer = async () => {
try {
const { schema, context } = await this._getGraphQLOptions();
// A value passed to this GraphQL server takes precedence; otherwise use the Parse Server
// option, where the option is documented and where the security check reads it.
const publicIntrospection =
this.config.graphQLPublicIntrospection ??
this.parseServer.config.graphQLPublicIntrospection;
const apollo = new ApolloServer({
csrfPrevention: {
// See https://www.apollographql.com/docs/router/configuration/csrf/
Expand All @@ -357,7 +362,7 @@ class ParseGraphQLServer {
// We need always true introspection because apollo server have changing behavior based on the NODE_ENV variable
// we delegate the introspection control to the IntrospectionControlPlugin
introspection: true,
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), SchemaSuggestionsControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)],
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(publicIntrospection), SchemaSuggestionsControlPlugin(publicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)],
schema,
});
await apollo.start();
Expand Down
Loading