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.04 kB
import { GraphQLError, getNamedType, isInterfaceType, isIntrospectionType, isObjectType, isUnionType } from "graphql"; import { useExtendContext } from "@envelop/core"; import { useExtendedValidation } from "@envelop/extended-validation"; import { handleMaybePromise } from "@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 GraphQLError(options.formatError(schemaCoordinate)); error.nodes = [node]; context.reportError(error); } }; return { Field(node) { const type = context.getType(); if (type) { if (isIntrospectionType(getNamedType(type))) return false; } const parentType = context.getParentType(); if (parentType) { if (isIntrospectionType(parentType)) return false; if (isObjectType(parentType)) handleField(node, parentType); if (isUnionType(parentType)) for (const objectType of parentType.getTypes()) handleField(node, objectType); if (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(useExtendedValidation({ rules: [OperationScopeRule({ formatError: opts.formatError ?? defaultFormatError })] })); addPlugin(useExtendContext((context) => handleMaybePromise(() => opts.getPermissions(context), (permissions) => { const schemaCoordinates = toSet(permissions); const scopeContext = { schemaCoordinates, wildcardTypes: getWildcardTypes(schemaCoordinates), allowAll: schemaCoordinates.has("*") }; return { [OPERATION_PERMISSIONS_SYMBOL]: scopeContext }; }))); } }; }; //#endregion export { useOperationFieldPermissions };