@pyramation/postgraphile
Version:
A GraphQL schema created by reflection over a PostgreSQL schema đ (previously known as PostGraphQL)
831 lines ⢠68.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEmpty = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any,require-atomic-updates */
const graphql_1 = require("graphql");
const extendedFormatError_1 = require("../extendedFormatError");
const koaMiddleware_1 = require("./koaMiddleware");
const pluginHook_1 = require("../pluginHook");
const setupServerSentEvents_1 = require("./setupServerSentEvents");
const withPostGraphileContext_1 = require("../withPostGraphileContext");
const lru_1 = require("@graphile/lru");
const chalk_1 = require("chalk");
const Debugger = require("debug"); // tslint:disable-line variable-name
const httpError = require("http-errors");
const parseUrl = require("parseurl");
const finalHandler = require("finalhandler");
const bodyParser = require("body-parser");
const crypto = require("crypto");
const CACHE_MULTIPLIER = 100000;
const ALLOW_EXPLAIN_PLACEHOLDER = '__SHOULD_ALLOW_EXPLAIN__';
const noop = () => {
/* noop */
};
const { createHash } = crypto;
/**
* The favicon file in `Buffer` format. We can send a `Buffer` directly to the
* client.
*
* @type {Buffer}
*/
const favicon_ico_1 = require("../../assets/favicon.ico");
/**
* The GraphiQL HTML file as a string. We need it to be a string, because we
* will use a regular expression to replace some variables.
*/
const graphiql_html_1 = require("../../assets/graphiql.html");
const subscriptions_1 = require("./subscriptions");
/**
* When writing JSON to the browser, we need to be careful that it doesn't get
* interpretted as HTML.
*/
const JS_ESCAPE_LOOKUP = {
'<': '\\u003c',
'>': '\\u003e',
'/': '\\u002f',
'\u2028': '\\u2028',
'\u2029': '\\u2029',
};
function safeJSONStringify(obj) {
return JSON.stringify(obj).replace(/[<>/\u2028\u2029]/g, chr => JS_ESCAPE_LOOKUP[chr]);
}
/**
* When people webpack us up, e.g. for lambda, if they don't want GraphiQL then
* they can seriously reduce bundle size by omitting the assets.
*/
const shouldOmitAssets = process.env.POSTGRAPHILE_OMIT_ASSETS === '1';
// Used by `createPostGraphileHttpRequestHandler`
let lastString;
let lastHash;
const calculateQueryHash = (queryString) => {
if (queryString !== lastString) {
lastString = queryString;
lastHash = createHash('sha1').update(queryString).digest('base64');
}
return lastHash;
};
// Fast way of checking if an object is empty,
// faster than `Object.keys(value).length === 0`.
// NOTE: we don't need a `hasOwnProperty` call here because isEmpty is called
// with an `Object.create(null)` object, so it has no no-own properties.
/* tslint:disable forin */
function isEmpty(value) {
for (const _key in value) {
return false;
}
return true;
}
exports.isEmpty = isEmpty;
/* tslint:enable forin */
const isPostGraphileDevelopmentMode = process.env.POSTGRAPHILE_ENV === 'development';
const debugGraphql = Debugger('postgraphile:graphql');
const debugRequest = Debugger('postgraphile:request');
/**
* We need to be able to share the withPostGraphileContext logic between HTTP
* and websockets
*/
function withPostGraphileContextFromReqResGenerator(options) {
const { pgSettings: pgSettingsGenerator, allowExplain: allowExplainGenerator, jwtSecret, additionalGraphQLContextFromRequest, } = options;
return async (req, res, moreOptions, fn) => {
const jwtToken = jwtSecret ? getJwtToken(req) : null;
const additionalContext = typeof additionalGraphQLContextFromRequest === 'function'
? await additionalGraphQLContextFromRequest(req, res)
: null;
const pgSettings = typeof pgSettingsGenerator === 'function'
? await pgSettingsGenerator(req)
: pgSettingsGenerator;
const allowExplain = typeof allowExplainGenerator === 'function'
? await allowExplainGenerator(req)
: allowExplainGenerator;
return withPostGraphileContext_1.default({
...options,
jwtToken,
pgSettings,
explain: allowExplain && req.headers['x-postgraphile-explain'] === 'on',
...moreOptions,
}, context => {
const graphqlContext = additionalContext
? { ...additionalContext, ...context }
: context;
return fn(graphqlContext);
});
};
}
/**
* Creates a GraphQL request handler that can support many different `http` frameworks, including:
*
* - Native Node.js `http`.
* - `connect`.
* - `express`.
* - `koa` (2.0).
*/
function createPostGraphileHttpRequestHandler(options) {
const MEGABYTE = 1024 * 1024;
const { getGqlSchema, pgPool, pgSettings, pgDefaultRole, queryCacheMaxSize = 50 * MEGABYTE, extendedErrors, showErrorStack, watchPg, disableQueryLog, enableQueryBatching, } = options;
const subscriptions = !!options.subscriptions;
const live = !!options.live;
const enhanceGraphiql = options.enhanceGraphiql === false ? false : !!options.enhanceGraphiql || subscriptions || live;
const enableCors = !!options.enableCors || isPostGraphileDevelopmentMode;
const graphiql = options.graphiql === true;
if (options['absoluteRoutes']) {
throw new Error('Sorry - the `absoluteRoutes` setting has been replaced with `externalUrlBase` which solves the issue in a cleaner way. Please update your settings. Thank you for testing a PostGraphile pre-release đ');
}
// Using let because we might override it on the first request.
let externalUrlBase = options.externalUrlBase;
if (externalUrlBase && externalUrlBase.endsWith('/')) {
throw new Error('externalUrlBase must not end with a slash (`/`)');
}
const pluginHook = pluginHook_1.pluginHookFromOptions(options);
const origGraphiqlHtml = pluginHook('postgraphile:graphiql:html', graphiql_html_1.default, { options });
if (pgDefaultRole && typeof pgSettings === 'function') {
throw new Error('pgDefaultRole cannot be combined with pgSettings(req) - please remove pgDefaultRole and instead always return a `role` key from pgSettings(req).');
}
if (pgDefaultRole &&
pgSettings &&
typeof pgSettings === 'object' &&
Object.keys(pgSettings)
.map(s => s.toLowerCase())
.includes('role')) {
throw new Error('pgDefaultRole cannot be combined with pgSettings.role - please use one or the other.');
}
if (graphiql && shouldOmitAssets) {
throw new Error('Cannot enable GraphiQL when POSTGRAPHILE_OMIT_ASSETS is set');
}
// Gets the route names for our GraphQL endpoint, and our GraphiQL endpoint.
const graphqlRoute = options.graphqlRoute || '/graphql';
const graphiqlRoute = graphiql ? options.graphiqlRoute || '/graphiql' : null;
const streamRoute = `${graphqlRoute.replace(/\/*$/, '')}/stream`;
// Throw an error of the GraphQL and GraphiQL routes are the same.
if (graphqlRoute === graphiqlRoute)
throw new Error(`Cannot use the same route, '${graphqlRoute}', for both GraphQL and GraphiQL. Please use different routes.`);
// Formats an error using the default GraphQL `formatError` function, and
// custom formatting using some other options.
const formatError = (error) => {
// Get the appropriate formatted error object, including any extended error
// fields if the user wants them.
const formattedError = extendedErrors && extendedErrors.length
? extendedFormatError_1.extendedFormatError(error, extendedErrors)
: graphql_1.formatError(error);
// If the user wants to see the errorâs stack, letâs add it to the
// formatted error.
if (showErrorStack)
formattedError['stack'] =
error.stack != null && showErrorStack === 'json' ? error.stack.split('\n') : error.stack;
return formattedError;
};
const DEFAULT_HANDLE_ERRORS = (errors) => errors.map(formatError);
const handleErrors = options.handleErrors || DEFAULT_HANDLE_ERRORS;
function convertKoaBodyParserToConnect(req, _res, next) {
if (req._koaCtx && req._koaCtx.request && req._koaCtx.request.body) {
req._body = true;
req.body = req._koaCtx.request.body;
}
next();
}
// Define a list of middlewares that will get run before our request handler.
// Note though that none of these middlewares will intercept a request (i.e.
// not call `next`). Middlewares that handle a request like favicon
// middleware will result in a promise that never resolves, and we donât
// want that.
const bodyParserMiddlewares = [
// Convert koa body to connect-compatible body
convertKoaBodyParserToConnect,
// Parse JSON bodies.
bodyParser.json({ limit: options.bodySizeLimit }),
// Parse URL encoded bodies (forms).
bodyParser.urlencoded({ extended: false, limit: options.bodySizeLimit }),
// Parse `application/graphql` content type bodies as text.
bodyParser.text({ type: 'application/graphql', limit: options.bodySizeLimit }),
];
// We'll turn this into one function now so it can be better JIT optimised
const bodyParserMiddlewaresComposed = bodyParserMiddlewares.reduce((parent, fn) => {
return (req, res, next) => {
parent(req, res, error => {
if (error) {
return next(error);
}
fn(req, res, next);
});
};
}, (_req, _res, next) => next());
// And we really want that function to be await-able
const parseBody = (req, res) => new Promise((resolve, reject) => {
bodyParserMiddlewaresComposed(req, res, (error) => {
if (error) {
reject(error);
}
else {
resolve();
}
});
});
// We only need to calculate the graphiql HTML once; but we need to receive the first request to do so.
let graphiqlHtml;
const withPostGraphileContextFromReqRes = withPostGraphileContextFromReqResGenerator(options);
const staticValidationRules = pluginHook('postgraphile:validationRules:static', graphql_1.specifiedRules, {
options,
});
const cacheSize = Math.ceil(queryCacheMaxSize / CACHE_MULTIPLIER);
// Do not create an LRU for cache size < 2 because @graphile/lru will baulk.
const cacheEnabled = cacheSize >= 2;
const queryCache = cacheEnabled ? new lru_1.default({ maxLength: cacheSize }) : null;
let lastGqlSchema;
const parseQuery = (gqlSchema, queryString) => {
if (gqlSchema !== lastGqlSchema) {
if (queryCache) {
queryCache.reset();
}
lastGqlSchema = gqlSchema;
}
// Only cache queries that are less than 100kB, we don't want DOS attacks
// attempting to exhaust our memory.
const canCache = cacheEnabled && queryString.length < 100000;
const hash = canCache ? calculateQueryHash(queryString) : null;
const result = canCache ? queryCache.get(hash) : null;
if (result) {
return result;
}
else {
const source = new graphql_1.Source(queryString, 'GraphQL Http Request');
let queryDocumentAst;
// Catch an errors while parsing so that we can set the `statusCode` to
// 400. Otherwise we donât need to parse this way.
try {
queryDocumentAst = graphql_1.parse(source);
}
catch (error) {
error.statusCode = 400;
throw error;
}
if (debugRequest.enabled)
debugRequest('GraphQL query is parsed.');
// Validate our GraphQL query using given rules.
const validationErrors = graphql_1.validate(gqlSchema, queryDocumentAst, staticValidationRules);
const cacheResult = {
queryDocumentAst,
validationErrors,
length: queryString.length,
};
if (canCache) {
queryCache.set(hash, cacheResult);
}
return cacheResult;
}
};
let firstRequestHandler = (req, pathname) => {
// Never be called again
firstRequestHandler = null;
if (externalUrlBase == null) {
// User hasn't specified externalUrlBase; let's try and guess it
const { pathname: originalPathname = '' } = parseUrl.original(req) || {};
if (originalPathname !== pathname && originalPathname.endsWith(pathname)) {
// We were mounted on a subpath (e.g. `app.use('/path/to', postgraphile(...))`).
// Figure out our externalUrlBase for ourselves.
externalUrlBase = originalPathname.substr(0, originalPathname.length - pathname.length);
}
// Make sure we have a string, at least
externalUrlBase = externalUrlBase || '';
}
// Takes the original GraphiQL HTML file and replaces the default config object.
graphiqlHtml = origGraphiqlHtml
? origGraphiqlHtml.replace(/<\/head>/, ` <script>window.POSTGRAPHILE_CONFIG=${safeJSONStringify({
graphqlUrl: `${externalUrlBase}${graphqlRoute}`,
streamUrl: watchPg ? `${externalUrlBase}${streamRoute}` : null,
enhanceGraphiql,
subscriptions,
allowExplain: typeof options.allowExplain === 'function'
? ALLOW_EXPLAIN_PLACEHOLDER
: !!options.allowExplain,
})};</script>\n </head>`)
: null;
if (subscriptions) {
const server = req && req.connection && req.connection['server'];
if (!server) {
// tslint:disable-next-line no-console
console.warn("Failed to find server to add websocket listener to, you'll need to call `enhanceHttpServerWithSubscriptions` manually");
}
else {
// Relying on this means that a normal request must come in before an
// upgrade attempt. It's better to call it manually.
subscriptions_1.enhanceHttpServerWithSubscriptions(server, middleware);
}
}
};
/*
* If we're not in watch mode, then avoid the cost of `await`ing the schema
* on every tick by having it available once it was generated.
*/
let theOneAndOnlyGraphQLSchema = null;
if (!watchPg) {
getGqlSchema()
.then(schema => {
theOneAndOnlyGraphQLSchema = schema;
})
.catch(noop);
}
/**
* The actual request handler. Itâs an async function so it will return a
* promise when complete. If the function doesnât handle anything, it calls
* `next` to let the next middleware try and handle it.
*/
const requestHandler = async (incomingReq, res, next) => {
// You can use this hook either to modify the incoming request or to tell
// PostGraphile not to handle the request further (return null). NOTE: if
// you return `null` from this hook then you are also responsible for
// calling `next()` (should that be required).
const req = pluginHook('postgraphile:http:handler', incomingReq, {
options,
res,
next,
});
if (req == null) {
return;
}
// Add our CORS headers to be good web citizens (there are perf
// implications though so be careful!)
//
// Always enable CORS when developing PostGraphile because GraphiQL will be
// on port 5783.
if (enableCors)
addCORSHeaders(res);
const { pathname = '' } = parseUrl(req) || {};
// Certain things depend on externalUrlBase, which we guess if the user
// doesn't supply it, so we calculate them on the first request. After
// first request, this function becomes a NOOP
if (firstRequestHandler)
firstRequestHandler(req, pathname);
const isGraphqlRoute = pathname === graphqlRoute;
// ========================================================================
// Serve GraphiQL and Related Assets
// ========================================================================
if (!shouldOmitAssets && graphiql && !isGraphqlRoute) {
// ======================================================================
// Favicon
// ======================================================================
// If this is the favicon path and it has not yet been handled, let us
// serve our GraphQL favicon.
if (pathname === '/favicon.ico') {
// If this is the wrong method, we should let the client know.
if (!(req.method === 'GET' || req.method === 'HEAD')) {
res.statusCode = req.method === 'OPTIONS' ? 200 : 405;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.end();
return;
}
// Otherwise we are good and should pipe the favicon to the browser.
res.statusCode = 200;
res.setHeader('Cache-Control', 'public, max-age=86400');
res.setHeader('Content-Type', 'image/x-icon');
// End early if the method is `HEAD`.
if (req.method === 'HEAD') {
res.end();
return;
}
res.end(favicon_ico_1.default);
return;
}
// ======================================================================
// GraphiQL Watch Stream
// ======================================================================
// Setup an event stream so we can broadcast events to graphiql, etc.
if (pathname === streamRoute || pathname === '/_postgraphile/stream') {
if (!watchPg || req.headers.accept !== 'text/event-stream') {
res.statusCode = 405;
res.end();
return;
}
setupServerSentEvents_1.default(req, res, options);
return;
}
// ======================================================================
// GraphiQL HTML
// ======================================================================
// If this is the GraphiQL route, show GraphiQL and stop execution.
if (pathname === graphiqlRoute) {
// If we are developing PostGraphile, instead just redirect.
if (isPostGraphileDevelopmentMode) {
res.statusCode = 302;
res.setHeader('Location', 'http://localhost:5783');
res.end();
return;
}
// If using the incorrect method, let the user know.
if (!(req.method === 'GET' || req.method === 'HEAD')) {
res.statusCode = req.method === 'OPTIONS' ? 200 : 405;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.end();
return;
}
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
// End early if the method is `HEAD`.
if (req.method === 'HEAD') {
res.end();
return;
}
// Actually renders GraphiQL.
if (graphiqlHtml && typeof options.allowExplain === 'function') {
res.end(graphiqlHtml.replace(`"${ALLOW_EXPLAIN_PLACEHOLDER}"`, // Because JSON escaped
JSON.stringify(!!(await options.allowExplain(req)))));
}
else {
res.end(graphiqlHtml);
}
return;
}
}
// Donât handle any requests if this is not the correct route.
if (!isGraphqlRoute)
return next();
// ========================================================================
// Execute GraphQL Queries
// ========================================================================
// If we didnât call `next` above, all requests will return 200 by default!
res.statusCode = 200;
if (watchPg) {
// Inform GraphiQL and other clients that they can subscribe to events
// (such as the schema being updated) at the following URL
res.setHeader('X-GraphQL-Event-Stream', `${externalUrlBase}${streamRoute}`);
}
// Donât execute our GraphQL stuffs for `OPTIONS` requests.
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
return;
}
// The `result` will be used at the very end in our `finally` block.
// Statements inside the `try` will assign to `result` when they get
// a result. We also keep track of `params`.
let paramsList;
let results = [];
const queryTimeStart = !disableQueryLog && process.hrtime();
let pgRole;
if (debugRequest.enabled)
debugRequest('GraphQL query request has begun.');
let returnArray = false;
// This big `try`/`catch`/`finally` block represents the execution of our
// GraphQL query. All errors thrown in this block will be returned to the
// client as GraphQL errors.
try {
// First thing we need to do is get the GraphQL schema for this request.
// It should never really change unless we are in watch mode.
const gqlSchema = theOneAndOnlyGraphQLSchema || (await getGqlSchema());
// Note that we run our middleware after we make sure we are on the
// correct route. This is so that if our middleware modifies the `req` or
// `res` objects, only we downstream will see the modifications.
//
// We also run our middleware inside the `try` so that we get the GraphQL
// error reporting style for syntax errors.
await parseBody(req, res);
// If this is not one of the correct methods, throw an error.
if (req.method !== 'POST') {
res.setHeader('Allow', 'POST, OPTIONS');
throw httpError(405, 'Only `POST` requests are allowed.');
}
// Get the parameters we will use to run a GraphQL request. `params` may
// include:
//
// - `query`: The required GraphQL query string.
// - `variables`: An optional JSON object containing GraphQL variables.
// - `operationName`: The optional name of the GraphQL operation we will
// be executing.
const body = req.body;
paramsList = typeof body === 'string' ? { query: body } : body;
// Validate our paramsList object a bit.
if (paramsList == null)
throw httpError(400, 'Must provide an object parameters, not nullish value.');
if (typeof paramsList !== 'object')
throw httpError(400, `Expected parameter object, not value of type '${typeof paramsList}'.`);
if (Array.isArray(paramsList)) {
if (!enableQueryBatching) {
throw httpError(501, 'Batching queries as an array is currently unsupported. Please provide a single query object.');
}
else {
returnArray = true;
}
}
else {
paramsList = [paramsList];
}
paramsList = pluginHook('postgraphile:httpParamsList', paramsList, {
options,
req,
res,
returnArray,
httpError,
});
results = await Promise.all(paramsList.map(async (params) => {
let queryDocumentAst = null;
let result;
const meta = Object.create(null);
try {
if (!params)
throw httpError(400, 'Invalid query structure.');
const { query, operationName } = params;
let { variables } = params;
if (!query)
throw httpError(400, 'Must provide a query string.');
// If variables is a string, we assume it is a JSON string and that it
// needs to be parsed.
if (typeof variables === 'string') {
// If variables is just an empty string, we should set it to null and
// ignore it.
if (variables === '') {
variables = null;
}
else {
// Otherwise, let us try to parse it as JSON.
try {
variables = JSON.parse(variables);
}
catch (error) {
error.statusCode = 400;
throw error;
}
}
}
// Throw an error if `variables` is not an object.
if (variables != null && typeof variables !== 'object')
throw httpError(400, `Variables must be an object, not '${typeof variables}'.`);
// Throw an error if `operationName` is not a string.
if (operationName != null && typeof operationName !== 'string')
throw httpError(400, `Operation name must be a string, not '${typeof operationName}'.`);
let validationErrors;
({ queryDocumentAst, validationErrors } = parseQuery(gqlSchema, query));
if (validationErrors.length === 0) {
// You are strongly encouraged to use
// `postgraphile:validationRules:static` if possible - you should
// only use this one if you need access to variables.
const moreValidationRules = pluginHook('postgraphile:validationRules', [], {
options,
req,
res,
variables,
operationName,
meta,
});
if (moreValidationRules.length) {
validationErrors = graphql_1.validate(gqlSchema, queryDocumentAst, moreValidationRules);
}
}
// If we have some validation errors, donât execute the query. Instead
// send the errors to the client with a `400` code.
if (validationErrors.length > 0) {
result = { errors: validationErrors, statusCode: 400 };
}
else if (!queryDocumentAst) {
throw new Error('Could not process query');
}
else {
if (debugRequest.enabled)
debugRequest('GraphQL query is validated.');
// Lazily log the query. If this debugger isnât enabled, donât run it.
if (debugGraphql.enabled)
debugGraphql('%s', graphql_1.print(queryDocumentAst).replace(/\s+/g, ' ').trim());
result = await withPostGraphileContextFromReqRes(req, res, {
singleStatement: false,
queryDocumentAst,
variables,
operationName,
}, (graphqlContext) => {
pgRole = graphqlContext.pgRole;
const graphqlResult = graphql_1.execute(gqlSchema, queryDocumentAst, null, graphqlContext, variables, operationName);
if (typeof graphqlContext.getExplainResults === 'function') {
return Promise.resolve(graphqlResult).then(async (obj) => ({
...obj,
// Add our explain data
explain: await graphqlContext.getExplainResults(),
}));
}
else {
return graphqlResult;
}
});
}
}
catch (error) {
result = {
errors: [error],
statusCode: error.status || error.statusCode || 500,
};
// If the status code is 500, letâs log our error.
if (result.statusCode === 500)
// tslint:disable-next-line no-console
console.error(error.stack);
}
finally {
// Format our errors so the client doesnât get the full thing.
if (result && result.errors) {
result.errors = handleErrors(result.errors, req, res);
}
if (!isEmpty(meta)) {
result.meta = meta;
}
result = pluginHook('postgraphile:http:result', result, {
options,
returnArray,
queryDocumentAst,
req,
pgRole,
});
// Log the query. If this debugger isnât enabled, donât run it.
if (!disableQueryLog && queryDocumentAst) {
// To appease TypeScript
const definitelyQueryDocumentAst = queryDocumentAst;
// We must reference this before it's deleted!
const resultStatusCode = result.statusCode;
const timeDiff = queryTimeStart && process.hrtime(queryTimeStart);
setImmediate(() => {
const prettyQuery = graphql_1.print(definitelyQueryDocumentAst)
.replace(/\s+/g, ' ')
.trim();
const errorCount = (result.errors || []).length;
const ms = timeDiff[0] * 1e3 + timeDiff[1] * 1e-6;
let message;
if (resultStatusCode === 401) {
// Users requested that JWT errors were raised differently:
//
// https://github.com/graphile/postgraphile/issues/560
message = chalk_1.default.red(`401 authentication error`);
}
else if (resultStatusCode === 403) {
message = chalk_1.default.red(`403 forbidden error`);
}
else {
message = chalk_1.default[errorCount === 0 ? 'green' : 'red'](`${errorCount} error(s)`);
}
// tslint:disable-next-line no-console
console.log(`${message} ${pgRole != null ? `as ${chalk_1.default.magenta(pgRole)} ` : ''}in ${chalk_1.default.grey(`${ms.toFixed(2)}ms`)} :: ${prettyQuery}`);
});
}
if (debugRequest.enabled)
debugRequest('GraphQL query has been executed.');
}
return result;
}));
}
catch (error) {
// Set our status code and send the client our results!
if (res.statusCode === 200)
res.statusCode = error.status || error.statusCode || 500;
// Overwrite entire response
returnArray = false;
results = [{ errors: handleErrors([error], req, res) }];
// If the status code is 500, letâs log our error.
if (res.statusCode === 500)
// tslint:disable-next-line no-console
console.error(error.stack);
}
finally {
// Finally, we send the client the results.
if (!returnArray) {
if (res.statusCode === 200 && results[0].statusCode) {
res.statusCode = results[0].statusCode;
}
results[0].statusCode = undefined;
}
res.setHeader('Content-Type', 'application/json; charset=utf-8');
const { statusCode, result } = pluginHook('postgraphile:http:end', {
statusCode: res.statusCode,
result: returnArray ? results : results[0],
}, {
options,
returnArray,
req,
res,
});
if (statusCode) {
res.statusCode = statusCode;
}
res.end(JSON.stringify(result));
if (debugRequest.enabled)
debugRequest('GraphQL ' + (returnArray ? 'queries' : 'query') + ' request finished.');
}
};
/**
* A polymorphic request handler that should detect what `http` framework is
* being used and specifically handle that framework.
*
* Supported frameworks include:
*
* - Native Node.js `http`.
* - `connect`.
* - `express`.
* - `koa` (2.0).
*/
const middleware = (a, b, c) => {
// If are arguments look like the arguments to koa middleware, this is
// `koa` middleware.
if (koaMiddleware_1.isKoaApp(a, b)) {
// Set the correct `koa` variable namesâŚ
const ctx = a;
const next = b;
return koaMiddleware_1.middleware(ctx, next, requestHandler);
}
else {
// Set the correct `connect` style variable names. If there was no `next`
// defined (likely the case if the client is using `http`) we use the
// final handler.
const req = a;
const res = b;
const next = c || finalHandler(req, res);
// Execute our request handler. If the request errored out, call `next` with the error.
requestHandler(req, res, next).catch(next);
}
};
middleware.getGraphQLSchema = getGqlSchema;
middleware.formatError = formatError;
middleware.pgPool = pgPool;
middleware.withPostGraphileContextFromReqRes = withPostGraphileContextFromReqRes;
middleware.handleErrors = handleErrors;
middleware.options = options;
const hookedMiddleware = pluginHook('postgraphile:middleware', middleware, {
options,
});
// Sanity check:
if (!hookedMiddleware.getGraphQLSchema) {
throw new Error("Hook for 'postgraphile:middleware' has not copied over the helpers; e.g. missing `Object.assign(newMiddleware, oldMiddleware)`");
}
return hookedMiddleware;
}
exports.default = createPostGraphileHttpRequestHandler;
/**
* Adds CORS to a request. See [this][1] flowchart for an explanation of how
* CORS works. Note that these headers are set for all requests, CORS
* algorithms normally run a preflight request using the `OPTIONS` method to
* get these headers.
*
* Note though, that enabling CORS will incur extra costs when it comes to the
* preflight requests. It is much better if you choose to use a proxy and
* bypass CORS altogether.
*
* [1]: http://www.html5rocks.com/static/images/cors_server_flowchart.png
*/
function addCORSHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'HEAD, GET, POST');
res.setHeader('Access-Control-Allow-Headers', [
'Origin',
'X-Requested-With',
// Used by `express-graphql` to determine whether to expose the GraphiQL
// interface (`text/html`) or not.
'Accept',
// Used by PostGraphile for auth purposes.
'Authorization',
// Used by GraphQL Playground and other Apollo-enabled servers
'X-Apollo-Tracing',
// The `Content-*` headers are used when making requests with a body,
// like in a POST request.
'Content-Type',
'Content-Length',
// For our 'Explain' feature
'X-PostGraphile-Explain',
].join(', '));
res.setHeader('Access-Control-Expose-Headers', ['X-GraphQL-Event-Stream'].join(', '));
}
function createBadAuthorizationHeaderError() {
return httpError(400, 'Authorization header is not of the correct bearer scheme format.');
}
/**
* Parses the `Bearer` auth scheme token out of the `Authorization` header as
* defined by [RFC7235][1].
*
* ```
* Authorization = credentials
* credentials = auth-scheme [ 1*SP ( token68 / #auth-param ) ]
* token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" )*"="
* ```
*
* [1]: https://tools.ietf.org/html/rfc7235
*
* @private
*/
const authorizationBearerRex = /^\s*bearer\s+([a-z0-9\-._~+/]+=*)\s*$/i;
/**
* Gets the JWT token from the Http requestâs headers. Specifically the
* `Authorization` header in the `Bearer` format. Will throw an error if the
* header is in the incorrect format, but will not throw an error if the header
* does not exist.
*
* @private
* @param {IncomingMessage} request
* @returns {string | null}
*/
function getJwtToken(request) {
const { authorization } = request.headers;
if (Array.isArray(authorization))
throw createBadAuthorizationHeaderError();
// If there was no authorization header, just return null.
if (authorization == null)
return null;
const match = authorizationBearerRex.exec(authorization);
// If we did not match the authorization header with our expected format,
// throw a 400 error.
if (!match)
throw createBadAuthorizationHeaderError();
// Return the token from our match.
return match[1];
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlUG9zdEdyYXBoaWxlSHR0cFJlcXVlc3RIYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3Bvc3RncmFwaGlsZS9odHRwL2NyZWF0ZVBvc3RHcmFwaGlsZUh0dHBSZXF1ZXN0SGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw4RUFBOEU7QUFDOUUscUNBV2lCO0FBQ2pCLGdFQUE2RDtBQUU3RCxtREFBd0U7QUFDeEUsOENBQXNEO0FBRXRELG1FQUE0RDtBQUM1RCx3RUFBaUU7QUFFakUsdUNBQWdDO0FBRWhDLGlDQUEwQjtBQUMxQixrQ0FBbUMsQ0FBQyxvQ0FBb0M7QUFDeEUseUNBQTBDO0FBQzFDLHFDQUFzQztBQUN0Qyw2Q0FBOEM7QUFDOUMsMENBQTJDO0FBQzNDLGlDQUFrQztBQUVsQyxNQUFNLGdCQUFnQixHQUFHLE1BQU0sQ0FBQztBQUVoQyxNQUFNLHlCQUF5QixHQUFHLDBCQUEwQixDQUFDO0FBQzdELE1BQU0sSUFBSSxHQUFHLEdBQUcsRUFBRTtJQUNoQixVQUFVO0FBQ1osQ0FBQyxDQUFDO0FBRUYsTUFBTSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztBQUU5Qjs7Ozs7R0FLRztBQUNILDBEQUErQztBQUUvQzs7O0dBR0c7QUFDSCw4REFBMEQ7QUFDMUQsbURBQXFFO0FBRXJFOzs7R0FHRztBQUNILE1BQU0sZ0JBQWdCLEdBQUc7SUFDdkIsR0FBRyxFQUFFLFNBQVM7SUFDZCxHQUFHLEVBQUUsU0FBUztJQUNkLEdBQUcsRUFBRSxTQUFTO0lBQ2QsUUFBUSxFQUFFLFNBQVM7SUFDbkIsUUFBUSxFQUFFLFNBQVM7Q0FDcEIsQ0FBQztBQUNGLFNBQVMsaUJBQWlCLENBQUMsR0FBd0I7SUFDakQsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxvQkFBb0IsRUFBRSxHQUFHLENBQUMsRUFBRSxDQUFDLGdCQUFnQixDQUFD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