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