Created
May 1, 2020 13:07
-
-
Save abrkn/2edb028da330a875c1598250a1a8d8e5 to your computer and use it in GitHub Desktop.
Whitelist GraphQL introspection types returned by apollo-server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot, very very kind!