Skip to content

Instantly share code, notes, and snippets.

@abrkn
Created May 1, 2020 13:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save abrkn/2edb028da330a875c1598250a1a8d8e5 to your computer and use it in GitHub Desktop.
Save abrkn/2edb028da330a875c1598250a1a8d8e5 to your computer and use it in GitHub Desktop.
Whitelist GraphQL introspection types returned by apollo-server
import { middleware as whitelistMiddleware } from './utils/introspecton-whitelist';
import { whitelist as introspectionWhitelist } from './utils/introspecton-whitelist/whitelist';
// Your express app
// Whitelist GraphQL introspection responses
app.use(whitelistMiddleware(introspectionWhitelist));
// Apollo middleware must be below whitelisting middleware
import { createHash } from 'crypto';
import { NextFunction, Request, Response } from 'express';
import LRU from 'lru-cache';
/* eslint-disable no-underscore-dangle */
interface IntospectionResponse {
data: {
__schema: {
types: {
kind: string;
name: string;
fields: {
name: string;
}[];
}[];
};
};
}
export type WhitelistEntry =
| {
kind?: string;
name: string;
fields?: string[];
}
| string;
export type Whitelist = WhitelistEntry[];
export function withWhitelist(whitelist: Whitelist, response: unknown): IntospectionResponse {
const responseTyped = response as IntospectionResponse;
return {
...responseTyped,
data: {
...responseTyped.data,
__schema: {
...responseTyped.data.__schema,
types: responseTyped.data.__schema.types.reduce((prev, type) => {
const entry = whitelist.find(_ => {
if (typeof _ === 'string') {
return _ === type.name;
}
return _.name === type.name && (_.kind === undefined || _.kind === type.kind);
});
if (!entry) {
return prev;
}
const allowedFields = typeof entry === 'string' ? undefined : entry.fields;
if (!allowedFields) {
return [...prev, type];
}
const { fields } = type;
return [
...prev,
{
...type,
fields: fields.filter(field => allowedFields.includes(field.name)),
},
];
}, [] as IntospectionResponse['data']['__schema']['types']),
},
},
};
}
export function middleware(whitelist: Whitelist) {
const cache = new LRU<string, any>(10);
return function whitelistMiddleware(req: Request, res: Response, next: NextFunction) {
const isIntrospection = req.body?.operationName === 'IntrospectionQuery';
if (!isIntrospection) {
next();
return;
}
const { send } = res;
// TODO: Prevent infinite recursion
let sent = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
res.send = function sendWithWhitelist(body: any) {
if (sent) {
send.call(this, body);
return res;
}
const hash = createHash('sha256')
.update(JSON.stringify(req.body))
.digest('hex');
const cached = cache.get(hash);
if (cached !== undefined) {
sent = true;
send.call(this, cached);
return res;
}
const result = withWhitelist(whitelist, JSON.parse(body));
cache.set(hash, result);
sent = true;
send.call(this, result);
return res;
};
next();
};
}
import { Whitelist } from '.';
export const whitelist: Whitelist = [
{
name: 'Query',
fields: [
'recentDeposits',
'order',
'ordersForSession',
'rate',
'rates',
'session',
'affiliateTransfers',
'stats',
'depositMethods',
'settleMethods',
'assets',
'permissions',
'paymentMethodCategories',
],
},
{
name: 'Mutation',
fields: ['createOrder'],
},
{
name: 'CacheControlScope',
},
{
name: 'Deposit',
},
'OwnedOrder',
'CreateOrderInput',
'JSON',
'OwnedDeposit',
'Session',
'AffiliateTransfer',
'Rate',
'Stats',
'DepositMethod',
'SettleMethod',
'Asset',
'Permissions',
'PaymentMethodCategory',
];
@abrkn
Copy link
Author

abrkn commented May 1, 2020

Start off by allowing a single query and mutation. Open the GraphQL playground and look at the browser console errors for types you missed.

@minox86
Copy link

minox86 commented May 1, 2020

A clean and simple solution, I thank you very much for it!

The only problem I see is that you need to specific in an explicit list all the nodes you want to show/hide. I'd rather prefer a solution that involves a directive, as an example a @Private or @hidden directive, that can be placed to types and fields. What do you think about it? Is there a way, in your opinion, to change/extend your middleware to handle a GraphQL directive?

@abrkn
Copy link
Author

abrkn commented May 2, 2020

Yes, by looking at the declarations from typeDefs. Hoping someone else wants to try first.

@abrkn
Copy link
Author

abrkn commented May 2, 2020

Published this as a module with directives support. https://github.com/sideshift/apollo-server-restrict-introspection

@mixno86

@minox86
Copy link

minox86 commented May 2, 2020

Thanks a lot, very very kind!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment