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

Re-add dynamic validation by request #2232

Closed
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
1 change: 1 addition & 0 deletions packages/apollo-server-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### vNEXT

* `apollo-server-core`: Added a function wrapper to `validationRules` for dynamic validation [PR#2232](https://github.com/apollographql/apollo-server/pull/2232)
* `apollo-server-core`: Add persisted queries [PR#1149](https://github.com/apollographql/apollo-server/pull/1149)
* `apollo-server-core`: added `BadUserInputError`
* `apollo-server-core`: **breaking** gql is exported from gql-tag and ApolloServer requires a DocumentNode [PR#1146](https://github.com/apollographql/apollo-server/pull/1146)
Expand Down
45 changes: 29 additions & 16 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { ValidationRule } from 'graphql/validation/ValidationContext';
import { Server as HttpServer } from 'http';
import {
execute,
Expand All @@ -9,6 +10,7 @@ import {
GraphQLFieldResolver,
ValidationContext,
FieldDefinitionNode,
ASTVisitor,
} from 'graphql';
import { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingAgent } from 'apollo-engine-reporting';
Expand All @@ -25,6 +27,7 @@ import { formatApolloErrors } from 'apollo-server-errors';
import {
GraphQLServerOptions as GraphQLOptions,
PersistedQueryOptions,
ValidationRulesFunction,
} from './graphqlOptions';

import {
Expand Down Expand Up @@ -55,18 +58,19 @@ import {
import { Headers } from 'apollo-server-env';
import { buildServiceDefinition } from '@apollographql/apollo-tools';

const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQLError(
'GraphQL introspection is not allowed by Apollo Server, but the query contained __schema or __type. To enable introspection, pass introspection: true to ApolloServer in production',
[node],
),
);
}
},
});
const NoIntrospection: ValidationRule = (context: ValidationContext) =>
({
Field(node: FieldDefinitionNode) {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQLError(
'GraphQL introspection is not allowed by Apollo Server, but the query contained __schema or __type. To enable introspection, pass introspection: true to ApolloServer in production',
[node],
),
);
}
},
} as ASTVisitor);

function getEngineServiceId(engine: Config['engine']): string | undefined {
const keyFromEnv = process.env.ENGINE_API_KEY || '';
Expand Down Expand Up @@ -154,10 +158,19 @@ export class ApolloServerBase {
(typeof introspection === 'boolean' && !introspection) ||
(introspection === undefined && !isDev)
) {
const noIntro = [NoIntrospection];
requestOptions.validationRules = requestOptions.validationRules
? requestOptions.validationRules.concat(noIntro)
: noIntro;
let rules: ValidationRule[] | ValidationRulesFunction = [NoIntrospection];
if (requestOptions.validationRules) {
if (typeof requestOptions.validationRules === 'function') {
rules = req => [
...(rules as ValidationRule[]),
...(requestOptions.validationRules as ValidationRulesFunction)(req),
];
} else {
rules = [...rules, ...requestOptions.validationRules];
}
}

requestOptions.validationRules = rules;
}

if (requestOptions.cacheControl !== false) {
Expand Down
16 changes: 8 additions & 8 deletions packages/apollo-server-core/src/graphqlOptions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {
GraphQLSchema,
ValidationContext,
GraphQLFieldResolver,
DocumentNode,
} from 'graphql';
import { GraphQLSchema, GraphQLFieldResolver, DocumentNode } from 'graphql';
import { GraphQLExtension } from 'graphql-extensions';
import { CacheControlExtensionOptions } from 'apollo-cache-control';
import { KeyValueCache } from 'apollo-server-caching';
import { DataSource } from 'apollo-datasource';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import { ApolloServerPlugin, GraphQLRequest } from 'apollo-server-plugin-base';
import { ValidationRule } from 'graphql/validation/ValidationContext';

/*
* GraphQLServerOptions
Expand All @@ -24,6 +20,10 @@ import { ApolloServerPlugin } from 'apollo-server-plugin-base';
* - (optional) extensions: an array of functions which create GraphQLExtensions (each GraphQLExtension object is used for one request)
*
*/
export type ValidationRulesFunction = (
request: GraphQLRequest,
) => ValidationRule[];

export interface GraphQLServerOptions<
TContext = Record<string, any>,
TRootValue = any
Expand All @@ -32,7 +32,7 @@ export interface GraphQLServerOptions<
formatError?: Function;
rootValue?: ((parsedQuery: DocumentNode) => TRootValue) | TRootValue;
context?: TContext | (() => never);
validationRules?: Array<(context: ValidationContext) => any>;
validationRules?: ValidationRule[] | ValidationRulesFunction;
formatResponse?: Function;
fieldResolver?: GraphQLFieldResolver<any, TContext>;
debug?: boolean;
Expand Down
18 changes: 14 additions & 4 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export interface GraphQLRequestPipelineConfig<TContext> {
schema: GraphQLSchema;

rootValue?: ((document: DocumentNode) => any) | any;
validationRules?: ValidationRule[];
validationRules?:
| ValidationRule[]
| ((request: GraphQLRequest) => ValidationRule[]);
fieldResolver?: GraphQLFieldResolver<any, TContext>;

dataSources?: () => DataSources<TContext>;
Expand Down Expand Up @@ -190,7 +192,7 @@ export async function processGraphQLRequest<TContext>(
requestContext as WithRequired<typeof requestContext, 'document'>,
);

const validationErrors = validate(document);
const validationErrors = validate(document, request);

if (validationErrors.length === 0) {
validationDidEnd();
Expand Down Expand Up @@ -278,10 +280,18 @@ export async function processGraphQLRequest<TContext>(
}
}

function validate(document: DocumentNode): ReadonlyArray<GraphQLError> {
function validate(
document: DocumentNode,
request: GraphQLRequest,
): ReadonlyArray<GraphQLError> {
let rules = specifiedRules;
if (config.validationRules) {
rules = rules.concat(config.validationRules);
rules = [
...rules,
...(typeof config.validationRules === 'function'
? config.validationRules(request)
: config.validationRules),
];
}

const validationDidEnd = extensionStack.validationDidStart();
Expand Down