UNPKG

@envelop/operation-field-permissions

Version:

Disallow executing operations that select certain fields. Useful if you want to restrict the scope of certain public API users to a subset of the public GraphQL schema, without triggering execution (e.g. how [graphql-shield](https://github.com/maticzav/gr

68 lines (66 loc) 3.17 kB
let graphql = require("graphql"); let _envelop_core = require("@envelop/core"); let _envelop_extended_validation = require("@envelop/extended-validation"); let _whatwg_node_promise_helpers = require("@whatwg-node/promise-helpers"); //#region src/index.ts const OPERATION_PERMISSIONS_SYMBOL = Symbol("OPERATION_PERMISSIONS_SYMBOL"); /** * Returns a set of type names that allow access to all fields in the type. */ const getWildcardTypes = (scope) => { const wildcardTypes = /* @__PURE__ */ new Set(); for (const item of scope) if (item.endsWith("*")) { const typeName = item.split(".")[0]; wildcardTypes.add(typeName); } return wildcardTypes; }; const toSet = (input) => typeof input === "string" ? new Set([input]) : input; const getContext = (input) => { if (typeof input !== "object" || !input || !(OPERATION_PERMISSIONS_SYMBOL in input)) throw new Error("OperationScopeRule was used without context."); return input[OPERATION_PERMISSIONS_SYMBOL]; }; /** * Validate whether a user is allowed to execute a certain GraphQL operation. */ const OperationScopeRule = (options) => (context, executionArgs) => { const permissionContext = getContext(executionArgs.contextValue); const handleField = (node, objectType) => { const schemaCoordinate = `${objectType.name}.${node.name.value}`; if (!permissionContext.allowAll && !permissionContext.wildcardTypes.has(objectType.name) && !permissionContext.schemaCoordinates.has(schemaCoordinate)) { const error = new graphql.GraphQLError(options.formatError(schemaCoordinate)); error.nodes = [node]; context.reportError(error); } }; return { Field(node) { const type = context.getType(); if (type) { if ((0, graphql.isIntrospectionType)((0, graphql.getNamedType)(type))) return false; } const parentType = context.getParentType(); if (parentType) { if ((0, graphql.isIntrospectionType)(parentType)) return false; if ((0, graphql.isObjectType)(parentType)) handleField(node, parentType); if ((0, graphql.isUnionType)(parentType)) for (const objectType of parentType.getTypes()) handleField(node, objectType); if ((0, graphql.isInterfaceType)(parentType)) for (const objectType of executionArgs.schema.getImplementations(parentType).objects) handleField(node, objectType); } } }; }; const defaultFormatError = (schemaCoordinate) => `Insufficient permissions for selecting '${schemaCoordinate}'.`; const useOperationFieldPermissions = (opts) => { return { onPluginInit({ addPlugin }) { addPlugin((0, _envelop_extended_validation.useExtendedValidation)({ rules: [OperationScopeRule({ formatError: opts.formatError ?? defaultFormatError })] })); addPlugin((0, _envelop_core.useExtendContext)((context) => (0, _whatwg_node_promise_helpers.handleMaybePromise)(() => opts.getPermissions(context), (permissions) => { const schemaCoordinates = toSet(permissions); const scopeContext = { schemaCoordinates, wildcardTypes: getWildcardTypes(schemaCoordinates), allowAll: schemaCoordinates.has("*") }; return { [OPERATION_PERMISSIONS_SYMBOL]: scopeContext }; }))); } }; }; //#endregion exports.useOperationFieldPermissions = useOperationFieldPermissions;