UNPKG

@cran/gql.core

Version:

Cran/GraphQL Core Utilities

123 lines (122 loc) 5.42 kB
/* eslint-disable max-lines */ import { PluginPhase } from "./Plugin"; import { createDirective } from "../utilities/createDirective"; import { reportMissing } from "../utilities/reportMissing"; import { withSchemaFiltering } from "../addons/withSchemaFiltering"; import { MapperKind, mapSchema } from "@graphql-tools/utils"; import { defaultFieldResolver, getNamedType } from "graphql"; import { getExtension, setExtension } from "../utilities/extensions"; // eslint-disable-next-line max-lines-per-function export function withRbac(options) { const { name = "rbac", enumName = "role", publicName = "public", rolesParameter = "roles", defaultGrants = {}, } = options; const roles = [...options.roles, publicName,].map(upper); if (!options.denyPublic) { defaultGrants[publicName] = true; } withSchemaFiltering.filter.types.push(function filterType(type, context, schema) { if (!context) { return true; } // internal call const usedTypes = getExtension(schema, "usedTypes"); const userRoles = (context[rolesParameter] || [publicName,]).map(upper); for (const role of userRoles) { const types = usedTypes[role] || usedTypes[publicName]; if (types[type.name]) { return true; } } return false; }); withSchemaFiltering.filter.fields.push(function filterField(fieldConfig, context) { if (!context) { return true; } // internal call return isAuthorized(getExtension(fieldConfig, "granted"), context[rolesParameter]); }); return [{ typeDefs: [ `enum ${enumName}{${roles.map(upper).join(" ")}}`, ], transformer: { // eslint-disable-next-line max-statements, max-lines-per-function [PluginPhase.FINALIZE](schema) { const directives = schema.getDirectives() .filter(function filterDirective(directive) { return withSchemaFiltering .filterDirectives(directive, null, schema); }); for (const directive of directives) { for (const arg of directive.args) { setExtension(getNamedType(arg.type), "authRequired", true); } } const emptyAuth = { grant: [], revoke: [], }; const usedTypes = {}; for (const role of roles) { usedTypes[role] = {}; } setExtension(schema, "usedTypes", usedTypes); return mapSchema(schema, { [MapperKind.COMPOSITE_FIELD](fieldConfig, _fieldName, typeName) { const { resolve = defaultFieldResolver, } = fieldConfig; const typeExt = getExtension(schema.getType(typeName), "auth", emptyAuth); const fieldExt = getExtension(fieldConfig, "auth", emptyAuth); const granted = grants(fieldExt.grant, fieldExt.revoke, grants(typeExt.grant, typeExt.revoke, defaultGrants)); setExtension(fieldConfig, "granted", granted); for (const grant in granted) { usedTypes[grant][getNamedType(fieldConfig.type).name] = true; for (const arg in (fieldConfig.args || {})) { usedTypes[grant][getNamedType(fieldConfig.args[arg].type).name] = true; } } fieldConfig.resolve = function resolver(source, args, context, info) { if (!isAuthorized(granted, context[rolesParameter])) { return void reportMissing(info); } return resolve.call(this, source, args, context, info); }; return fieldConfig; }, }); }, }, }, createDirective(name, { grant: `[${enumName}!]`, revoke: `[${enumName}!]`, }, { [MapperKind.TYPE]([directive,], type) { setExtension(type, "auth", { grant: directive.grant || [], revoke: directive.revoke || [], }); }, [MapperKind.COMPOSITE_FIELD]([directive,], fieldConfig) { setExtension(fieldConfig, "auth", { grant: directive.grant || [], revoke: directive.revoke || [], }); }, }),]; } function isAuthorized(granted, roles) { if (roles) { for (const role of roles) { if (granted[role]) { return true; } } } // TODO else public return false; } function upper(value) { return value.toUpperCase(); } function grants(grant, revoke, initial) { const roles = { ...initial, }; for (const role of grant) { roles[role] = true; } for (const role of revoke) { delete roles[role]; } return roles; }