UNPKG

postgraphile

Version:

A GraphQL schema created by reflection over a PostgreSQL schema 🐘 (previously known as PostGraphQL)

457 lines 37.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.debugPgClient = void 0; const tslib_1 = require("tslib"); const createDebugger = require("debug"); const jwt = require("jsonwebtoken"); const graphql_1 = require("graphql"); const sql = require("pg-sql2"); const pgClientFromContext_1 = require("../postgres/inventory/pgClientFromContext"); const pluginHook_1 = require("./pluginHook"); const postgraphile_core_1 = require("postgraphile-core"); const undefinedIfEmpty = (o) => o && (!Array.isArray(o) || o.length) ? o : undefined; const debugPg = createDebugger('postgraphile:postgres'); const debugPgError = createDebugger('postgraphile:postgres:error'); const debugPgNotice = createDebugger('postgraphile:postgres:notice'); /** * Formats an error/notice from `pg` and feeds it into a `debug` function. */ function debugPgErrorObject(debugFn, object) { debugFn('%s%s: %s%s%s', object.severity || 'ERROR', object.code ? `[${object.code}]` : '', object.message || object, object.where ? ` | WHERE: ${object.where}` : '', object.hint ? ` | HINT: ${object.hint}` : ''); } function swallowErrors() { /* noop */ } const simpleWithPgClientCache = new WeakMap(); function simpleWithPgClient(pgPool) { const cached = simpleWithPgClientCache.get(pgPool); if (cached) { return cached; } const func = async (cb) => { const pgClient = await pgPool.connect(); pgClient.on('error', swallowErrors); try { return await cb(pgClient); } finally { pgClient.removeListener('error', swallowErrors); pgClient.release(); } }; simpleWithPgClientCache.set(pgPool, func); return func; } const withDefaultPostGraphileContext = async (options, callback) => { const { pgPool, jwtToken, jwtSecret, jwtPublicKey, jwtAudiences, jwtRole = ['role'], jwtVerifyOptions, pgDefaultRole, pgSettings, explain, queryDocumentAst, operationName, pgForceTransaction, singleStatement, } = options; let operation; if (!pgForceTransaction && queryDocumentAst) { // tslint:disable-next-line for (let i = 0, l = queryDocumentAst.definitions.length; i < l; i++) { const definition = queryDocumentAst.definitions[i]; if (definition.kind === graphql_1.Kind.OPERATION_DEFINITION) { if (!operationName && operation) { throw new Error('Multiple operations present in GraphQL query, you must specify an `operationName` so we know which one to execute.'); } else if (!operationName || (definition.name && definition.name.value === operationName)) { operation = definition; } } } } // Warning: this is only set if pgForceTransaction is falsy const operationType = operation != null ? operation.operation : null; const { role: pgRole, localSettings, jwtClaims } = await getSettingsForPgClientTransaction({ jwtToken, jwtSecret, jwtPublicKey, jwtAudiences, jwtRole, jwtVerifyOptions, pgDefaultRole, pgSettings, }); const sqlSettings = []; if (localSettings.length > 0) { // Later settings should win, so we're going to loop backwards and not // add settings for keys we've already seen. const seenKeys = []; // TODO:perf: looping backwards is slow for (let i = localSettings.length - 1; i >= 0; i--) { const [key, value] = localSettings[i]; if (!seenKeys.includes(key)) { seenKeys.push(key); // Make sure that the third config is always `true` so that we are only // ever setting variables on the transaction. // Also, we're using `unshift` to undo the reverse-looping we're doing sqlSettings.unshift(sql.fragment `set_config(${sql.value(key)}, ${sql.value(value)}, true)`); } } } const sqlSettingsQuery = sqlSettings.length > 0 ? sql.compile(sql.query `select ${sql.join(sqlSettings, ', ')}`) : null; // If we can avoid transactions, we get greater performance. const needTransaction = pgForceTransaction || !!sqlSettingsQuery || (operationType !== 'query' && operationType !== 'subscription'); // Now we've caught as many errors as we can at this stage, let's create a DB connection. const withAuthenticatedPgClient = !needTransaction ? simpleWithPgClient(pgPool) : async (cb) => { // Connect a new Postgres client const pgClient = await pgPool.connect(); pgClient.on('error', swallowErrors); try { // Begin our transaction await pgClient.query('begin'); try { // If there is at least one local setting, load it into the database. if (sqlSettingsQuery) { await pgClient.query(sqlSettingsQuery); } // Use the client, wait for it to be finished with, then go to 'finally' return await cb(pgClient); } finally { // Cleanup our Postgres client by ending the transaction and releasing // the client back to the pool. Always do this even if the query fails. await pgClient.query('commit'); } } finally { pgClient.removeListener('error', swallowErrors); pgClient.release(); } }; if (singleStatement) { // TODO:v5: remove this workaround /* * This is a workaround for subscriptions; the GraphQL context is allocated * for the entire duration of the subscription, however hogging a pgClient * for more than a few milliseconds (let alone hours!) is a no-no. So we * fake a PG client that will set up the transaction each time `query` is * called. It's a very thin/dumb wrapper, so it supports nothing but * `query`. */ const fakePgClient = { query(textOrQueryOptions, values, // tslint:disable-line no-any cb) { if (!textOrQueryOptions) { throw new Error('Incompatible call to singleStatement - no statement passed?'); } else if (typeof textOrQueryOptions === 'object') { if (values || cb) { throw new Error('Incompatible call to singleStatement - expected no callback'); } } else if (typeof textOrQueryOptions !== 'string') { throw new Error('Incompatible call to singleStatement - bad query'); } else if (values && !Array.isArray(values)) { throw new Error('Incompatible call to singleStatement - bad values'); } else if (cb) { throw new Error('Incompatible call to singleStatement - expected to return promise'); } // Generate an authenticated client on the fly return withAuthenticatedPgClient(pgClient => pgClient.query(textOrQueryOptions, values)); }, }; // tslint:disable-line no-any return callback({ [pgClientFromContext_1.$$pgClient]: fakePgClient, pgRole, jwtClaims, }); } else { return withAuthenticatedPgClient(async (pgClient) => { let results = null; if (explain) { pgClient.startExplain(); } try { return await callback(Object.assign({ [pgClientFromContext_1.$$pgClient]: pgClient, pgRole, jwtClaims }, (explain ? { getExplainResults: () => { results = results || pgClient.stopExplain(); return results; }, } : null))); } finally { if (explain) { results = results || pgClient.stopExplain(); } } }); } }; /** * Creates a PostGraphile context object which should be passed into a GraphQL * execution. This function will also connect a client from a Postgres pool and * setup a transaction in that client. * * This function is intended to wrap a call to GraphQL-js execution like so: * * ```js * const result = await withPostGraphileContext({ * pgPool, * jwtToken, * jwtSecret, * pgDefaultRole, * }, async context => { * return await graphql( * schema, * query, * null, * { ...context }, * variables, * operationName, * ); * }); * ``` */ const withPostGraphileContext = async (options, callback) => { const pluginHook = pluginHook_1.pluginHookFromOptions(options); const withContext = pluginHook('withPostGraphileContext', withDefaultPostGraphileContext, { options, }); return withContext(options, callback); }; exports.default = withPostGraphileContext; /** * Sets up the Postgres client transaction by decoding the JSON web token and * doing some other cool things. */ // THIS METHOD SHOULD NEVER RETURN EARLY. If this method returns early then it // may skip the super important step of setting the role on the Postgres // client. If this happens it’s a huge security vulnerability. Never using the // keyword `return` in this function is a good first step. You can still throw // errors, however, as this will stop the request execution. async function getSettingsForPgClientTransaction({ jwtToken, jwtSecret, jwtPublicKey, jwtAudiences, jwtRole, jwtVerifyOptions, pgDefaultRole, pgSettings, }) { // Setup our default role. Once we decode our token, the role may change. let role = pgDefaultRole; let jwtClaims = {}; // If we were provided a JWT token, let us try to verify it. If verification // fails we want to throw an error. if (jwtToken) { // Try to run `jwt.verify`. If it fails, capture the error and re-throw it // as a 403 error because the token is not trustworthy. try { const jwtVerificationSecret = jwtPublicKey || jwtSecret; // If a JWT token was defined, but a secret was not provided to the server or // secret had unsupported type, throw a 403 error. if (!Buffer.isBuffer(jwtVerificationSecret) && typeof jwtVerificationSecret !== 'string' && typeof jwtVerificationSecret !== 'function') { // tslint:disable-next-line no-console console.error(`ERROR: '${jwtPublicKey ? 'jwtPublicKey' : 'jwtSecret'}' was not set to a string or buffer - rejecting JWT-authenticated request.`); throw new Error('Not allowed to provide a JWT token.'); } if (jwtAudiences != null && jwtVerifyOptions && 'audience' in jwtVerifyOptions) throw new Error(`Provide either 'jwtAudiences' or 'jwtVerifyOptions.audience' but not both`); const claims = await new Promise((resolve, reject) => { jwt.verify(jwtToken, jwtVerificationSecret, Object.assign(Object.assign({}, jwtVerifyOptions), { audience: jwtAudiences || (jwtVerifyOptions && 'audience' in jwtVerifyOptions ? undefinedIfEmpty(jwtVerifyOptions.audience) : ['postgraphile']) }), (err, decoded) => { if (err) reject(err); else resolve(decoded); }); }); if (typeof claims === 'string') { throw new Error('Invalid JWT payload'); } // jwt.verify returns `object | string`; but the `object` part is really a map jwtClaims = claims; const roleClaim = getPath(jwtClaims, jwtRole); // If there is a `role` property in the claims, use that instead of our // default role. if (typeof roleClaim !== 'undefined') { if (typeof roleClaim !== 'string') throw new Error(`JWT \`role\` claim must be a string. Instead found '${typeof jwtClaims['role']}'.`); role = roleClaim; } } catch (error) { // In case this error is thrown in an HTTP context, we want to add status code // Note. jwt.verify will add a name key to its errors. (https://github.com/auth0/node-jsonwebtoken#errors--codes) error.statusCode = 'name' in error && error.name === 'TokenExpiredError' ? // The correct status code for an expired ( but otherwise acceptable token is 401 ) 401 : // All other authentication errors should get a 403 status code. 403; throw error; } } // Instantiate a map of local settings. This map will be transformed into a // Sql query. const localSettings = []; // Set the custom provided settings before jwt claims and role are set // this prevents an accidentional overwriting if (pgSettings && typeof pgSettings === 'object') { for (const key in pgSettings) { if (Object.prototype.hasOwnProperty.call(pgSettings, key) && isPgSettingValid(pgSettings[key])) { if (key === 'role') { role = String(pgSettings[key]); } else { localSettings.push([key, String(pgSettings[key])]); } } } } // If there is a rule, we want to set the root `role` setting locally // to be our role. The role may only be null if we have no default role. if (typeof role === 'string') { localSettings.push(['role', role]); } // If we have some JWT claims, we want to set those claims as local // settings with the namespace `jwt.claims`. for (const key in jwtClaims) { if (Object.prototype.hasOwnProperty.call(jwtClaims, key)) { const rawValue = jwtClaims[key]; // Unsafe to pass raw object/array to pg.query -> set_config; instead JSONify const value = rawValue != null && typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue; if (isPgSettingValid(value)) { localSettings.push([`jwt.claims.${key}`, String(value)]); } } } return { localSettings, role, jwtClaims: jwtToken ? jwtClaims : null, }; } const $$pgClientOrigQuery = Symbol(); /** * Monkey-patches the `query` method of a pg Client to add debugging * functionality. Use with care. */ function debugPgClient(pgClient, allowExplain = false) { // If Postgres debugging is enabled, enhance our query function by adding // a debug statement. if (!pgClient[$$pgClientOrigQuery]) { // Set the original query method to a key on our client. If that key is // already set, use that. pgClient[$$pgClientOrigQuery] = pgClient.query; pgClient.startExplain = () => { pgClient._explainResults = []; }; pgClient.stopExplain = async () => { const results = pgClient._explainResults; pgClient._explainResults = null; if (!results) { return Promise.resolve([]); } return (await Promise.all(results.map(async (r) => { const { result: resultPromise } = r, rest = tslib_1.__rest(r, ["result"]); const result = await resultPromise; const firstKey = result && result[0] && Object.keys(result[0])[0]; if (!firstKey) { return null; } const plan = result.map((r) => r[firstKey]).join('\n'); return Object.assign(Object.assign({}, rest), { plan }); }))).filter((entry) => !!entry); }; if (debugPgNotice.enabled) { pgClient.on('notice', (msg) => { debugPgErrorObject(debugPgNotice, msg); }); } const logError = (error) => { if (error.name && error['severity']) { debugPgErrorObject(debugPgError, error); } else { debugPgError('%O', error); } }; if (debugPg.enabled || debugPgNotice.enabled || allowExplain) { // tslint:disable-next-line only-arrow-functions pgClient.query = function (...args) { const [a, b, c] = args; // If we understand it (and it uses the promises API) if ((typeof a === 'string' && !c && (!b || Array.isArray(b))) || (typeof a === 'object' && !b && !c)) { if (debugPg.enabled) { // Debug just the query text. We don’t want to debug variables because // there may be passwords in there. debugPg('%s', postgraphile_core_1.formatSQLForDebugging(a && a.text ? a.text : a)); } if (pgClient._explainResults) { const query = a && a.text ? a.text : a; const values = a && a.text ? a.values : b; if (query.match(/^\s*(select|insert|update|delete|with)\s/i) && !query.includes(';')) { // Explain it const explain = `explain ${query}`; pgClient._explainResults.push({ query, result: pgClient[$$pgClientOrigQuery] .call(this, explain, values) .then((data) => data.rows) // swallow errors during explain .catch(() => null), }); } } const promiseResult = pgClient[$$pgClientOrigQuery].apply(this, args); if (debugPgError.enabled) { // Report the error with our Postgres debugger. promiseResult.catch(logError); } return promiseResult; } else { // We don't understand it (e.g. `pgPool.query`), just let it happen. return pgClient[$$pgClientOrigQuery].apply(this, args); } }; } } return pgClient; } exports.debugPgClient = debugPgClient; /** * Safely gets the value at `path` (array of keys) of `inObject`. * * @private */ function getPath(inObject, path) { let object = inObject; // From https://github.com/lodash/lodash/blob/master/.internal/baseGet.js let index = 0; const length = path.length; while (object && index < length) { object = object[path[index++]]; } return index && index === length ? object : undefined; } /** * Check if a pgSetting is a string or a number. * Null and Undefined settings are not valid and will be ignored. * pgSettings of other types throw an error. * * @private */ function isPgSettingValid(pgSetting) { if (pgSetting === undefined || pgSetting === null) { return false; } const typeOfPgSetting = typeof pgSetting; if (typeOfPgSetting === 'string' || typeOfPgSetting === 'number' || typeOfPgSetting === 'boolean') { return true; } // TODO: booleans! throw new Error(`Error converting pgSetting: ${typeof pgSetting} needs to be of type string, number or boolean.`); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2l0aFBvc3RHcmFwaGlsZUNvbnRleHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcG9zdGdyYXBoaWxlL3dpdGhQb3N0R3JhcGhpbGVDb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSx3Q0FBeUM7QUFDekMsb0NBQXFDO0FBRXJDLHFDQUF5RTtBQUN6RSwrQkFBK0I7QUFDL0IsbUZBQXVFO0FBQ3ZFLDZDQUFxRDtBQUVyRCx5REFBMEQ7QUFFMUQsTUFBTSxnQkFBZ0IsR0FBRyxDQUN2QixDQUE0QyxFQUNVLEVBQUUsQ0FDeEQsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFZdkQsTUFBTSxPQUFPLEdBQUcsY0FBYyxDQUFDLHVCQUF1QixDQUFDLENBQUM7QUFDeEQsTUFBTSxZQUFZLEdBQUcsY0FBYyxDQUFDLDZCQUE2QixDQUFDLENBQUM7QUFDbkUsTUFBTSxhQUFhLEdBQUcsY0FBYyxDQUFDLDhCQUE4QixDQUFDLENBQUM7QUFFckU7O0dBRUc7QUFDSCxTQUFTLGtCQUFrQixDQUFDLE9BQWlDLEVBQUUsTUFBZ0I7SUFDN0UsT0FBTyxDQUNMLGNBQWMsRUFDZCxNQUFNLENBQUMsUUFBUSxJQUFJLE9BQU8sRUFDMUIsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxNQUFNLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFDckMsTUFBTSxDQUFDLE9BQU8sSUFBSSxNQUFNLEVBQ3hCLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLGFBQWEsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQy9DLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFlBQVksTUFB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