Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide "did you mean" suggestions via internal plugin to avoid leaking schema information #7916

Merged
Prev Previous commit
Next Next commit
rename option to hideSchemaDetailsFromClientErrors and add unit tests
  • Loading branch information
andrewmcgivery committed Aug 7, 2024
commit 1f06b525df6046d2afeefff951d49b813368b164
17 changes: 11 additions & 6 deletions packages/server/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export interface ApolloServerInternals<TContext extends BaseContext> {

rootValue?: ((parsedQuery: DocumentNode) => unknown) | unknown;
validationRules: Array<ValidationRule>;
didYouMeanEnabled: boolean;
hideSchemaDetailsFromClientErrors: boolean;
fieldResolver?: GraphQLFieldResolver<any, TContext>;
// TODO(AS5): remove OR warn + ignore with this option set, ignore option and
// flip default behavior.
Expand Down Expand Up @@ -282,7 +282,8 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
};

const introspectionEnabled = config.introspection ?? isDev;
const didYouMeanEnabled = config.didYouMean ?? true;
const hideSchemaDetailsFromClientErrors =
config.hideSchemaDetailsFromClientErrors ?? false;

// We continue to allow 'bounded' for backwards-compatibility with the AS3.9
// API.
Expand All @@ -300,7 +301,7 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
...(config.validationRules ?? []),
...(introspectionEnabled ? [] : [NoIntrospection]),
],
didYouMeanEnabled,
hideSchemaDetailsFromClientErrors,
dangerouslyDisableValidation:
config.dangerouslyDisableValidation ?? false,
fieldResolver: config.fieldResolver,
Expand Down Expand Up @@ -837,8 +838,12 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
}

private async addDefaultPlugins() {
const { plugins, apolloConfig, nodeEnv, didYouMeanEnabled } =
this.internals;
const {
plugins,
apolloConfig,
nodeEnv,
hideSchemaDetailsFromClientErrors,
} = this.internals;
const isDev = nodeEnv !== 'production';

const alreadyHavePluginWithInternalId = (id: InternalPluginId) =>
Expand Down Expand Up @@ -1001,7 +1006,7 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
{
const alreadyHavePlugin =
alreadyHavePluginWithInternalId('DisableSuggestions');
if (!didYouMeanEnabled && !alreadyHavePlugin) {
if (hideSchemaDetailsFromClientErrors && !alreadyHavePlugin) {
const { ApolloServerPluginDisableSuggestions } = await import(
'./plugin/disableSuggestions/index.js'
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ApolloServer, HeaderMap } from '../../..';
import { describe, it, expect } from '@jest/globals';
import assert from 'assert';

describe('ApolloServerPluginDisableSuggestions', () => {
async function makeServer({
withPlugin,
query,
}: {
withPlugin: boolean;
query: string;
}) {
const server = new ApolloServer({
typeDefs: 'type Query {hello: String}',
resolvers: {
Query: {
hello() {
return 'asdf';
},
},
},
hideSchemaDetailsFromClientErrors: withPlugin,
});

await server.start();

try {
return await server.executeHTTPGraphQLRequest({
httpGraphQLRequest: {
method: 'POST',
headers: new HeaderMap([['apollo-require-preflight', 't']]),
search: '',
body: {
query,
},
},
context: async () => ({}),
});
} finally {
await server.stop();
}
}

it('should not hide suggestions when plugin is not enabled', async () => {
const response = await makeServer({
withPlugin: false,
query: `#graphql
query {
helloo
}
`,
});

assert(response.body.kind === 'complete');
expect(JSON.parse(response.body.string).errors[0].message).toBe(
'Cannot query field "helloo" on type "Query". Did you mean "hello"?',
trevor-scheer marked this conversation as resolved.
Show resolved Hide resolved
);
});

it('should hide suggestions when plugin is enabled', async () => {
const response = await makeServer({
withPlugin: true,
query: `#graphql
query {
helloo
}
`,
});

assert(response.body.kind === 'complete');
expect(JSON.parse(response.body.string).errors[0].message).toBe(
'Cannot query field "helloo" on type "Query".',
);
});
});
2 changes: 1 addition & 1 deletion packages/server/src/externalTypes/constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ interface ApolloServerOptionsBase<TContext extends BaseContext> {
value: FormattedExecutionResult,
) => string | Promise<string>;
introspection?: boolean;
didYouMean?: boolean;
hideSchemaDetailsFromClientErrors?: boolean;
plugins?: ApolloServerPlugin<TContext>[];
persistedQueries?: PersistedQueryOptions | false;
stopOnTerminationSignals?: boolean;
Expand Down