UNPKG

@quell/server

Version:

Quell is an open-source NPM package providing a light-weight caching layer implementation and cache invalidation for GraphQL responses on both the client- and server-side. Use Quell to prevent redundant client-side API requests and to minimize costly serv

87 lines (86 loc) 4.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createDepthLimit = void 0; const graphql_1 = require("graphql"); const quellHelpers_1 = require("../helpers/quellHelpers"); /** * Takes in the query, parses it, and identifies the general shape of the request in order * to compare the query's depth to the depth limit set on server connection. * * In the instance of a malicious or overly nested query, short-circuits the query before * it goes to the database and passes an error with a status code 413 (content too large). * @param {Object} req - Express request object * @param {Object} res - Express response object * @param {Function} next - Express next middleware function * @returns {void} Passes an error to Express if no query was included in the request or if the depth exceeds the maximum allowed depth. */ // what parameters should they take? If middleware, good as is, has to take in query obj in request, limit set inside. // If function inside whole of Quell, (query, limit), so they are explicitly defined and passed in function createDepthLimit(config) { return (req, res, next) => { var _a; // Get maximum depth limit from the cost parameters set on server connection. let { maxDepth } = config; // Get the GraphQL query string from request body. const queryString = req.body.query; // Pass error to Express if no query is found on the request. if (!queryString) { { const err = { log: "Invalid request, no query found in req.body", status: 400, message: { err: "Error in middleware function: depthLimit. Check server log for more details.", }, }; return next(err); } } // Create the abstract syntax tree with graphql-js parser. // If costLimit was included before depthLimit in middleware chain, we can get the AST and parsed AST from res.locals. const AST = res.locals.AST ? res.locals.AST : (0, graphql_1.parse)(queryString); // Create response prototype, operation type, and fragments object. // The response prototype is used as a template for most operations in Quell including caching, building modified requests, and more. const { proto, operationType, frags, } = (_a = res.locals.parsedAST) !== null && _a !== void 0 ? _a : (0, quellHelpers_1.parseAST)(AST); // Combine fragments on prototype so we can access fragment values in cache. const prototype = Object.keys(frags).length > 0 ? (0, quellHelpers_1.updateProtoWithFragment)(proto, frags) : proto; /** * Recursive helper function that determines if the depth of the prototype object * is greater than the maxDepth. * @param {Object} proto - The prototype object to determine the depth of. * @param {number} [currentDepth=0] - The current depth of the object. Defaults to 0. * @returns {void} Passes an error to Express if the depth of the prototype exceeds the maxDepth. */ const determineDepth = (proto, currentDepth = 0) => { if (currentDepth > maxDepth) { // Pass error to Express if the maximum depth has been exceeded. const err = { log: "Error in QuellCache.determineDepth: depth limit exceeded.", status: 413, message: { err: `Depth limit exceeded, tried to send query with the depth of ${currentDepth}.`, }, }; res.locals.queryErr = err; return next(err); } // Loop through the fields, recursing and increasing currentDepth by 1 if the field is nested. Object.keys(proto).forEach((key) => { if (typeof proto[key] === "object" && !key.includes("__")) { determineDepth(proto[key], currentDepth + 1); } }); }; // Call the helper function. determineDepth(prototype); // Attach the AST and parsed AST to res.locals so that the next middleware doesn't need to determine these again. res.locals.AST = AST; res.locals.parsedAST = { proto, operationType, frags }; return next(); }; } exports.createDepthLimit = createDepthLimit;