@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
JavaScript
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 };