UNPKG

nexus

Version:

Scalable, strongly typed GraphQL schema development

132 lines (124 loc) 4.72 kB
import type { GraphQLResolveInfo } from 'graphql' import { plugin } from '../plugin' import type { ArgsValue, GetGen, MaybePromise, SourceValue } from '../typegenTypeHelpers' import { printedGenTyping, printedGenTypingImport } from '../utils' const FieldauthorizeResolverImport = printedGenTypingImport({ module: 'nexus/dist/plugins/fieldAuthorizePlugin', bindings: ['FieldAuthorizeResolver'], }) const fieldDefTypes = printedGenTyping({ optional: true, name: 'authorize', description: ` Authorization for an individual field. Returning "true" or "Promise<true>" means the field can be accessed. Returning "false" or "Promise<false>" will respond with a "Not Authorized" error for the field. Returning or throwing an error will also prevent the resolver from executing. `, type: 'FieldAuthorizeResolver<TypeName, FieldName>', imports: [FieldauthorizeResolverImport], }) export type FieldAuthorizeResolver<TypeName extends string, FieldName extends string> = ( root: SourceValue<TypeName>, args: ArgsValue<TypeName, FieldName>, context: GetGen<'context'>, info: GraphQLResolveInfo ) => MaybePromise<boolean | Error> export interface FieldAuthorizePluginErrorConfig { error: Error root: any args: any ctx: GetGen<'context'> info: GraphQLResolveInfo } export interface FieldAuthorizePluginConfig { formatError?: (authConfig: FieldAuthorizePluginErrorConfig) => Error } export const defaultFormatError = ({ error }: FieldAuthorizePluginErrorConfig): Error => { const err: Error & { originalError?: Error } = new Error('Not authorized') err.originalError = error return err } export const fieldAuthorizePlugin = (authConfig: FieldAuthorizePluginConfig = {}) => { const { formatError = defaultFormatError } = authConfig const ensureError = (root: any, args: any, ctx: GetGen<'context'>, info: GraphQLResolveInfo) => (error: Error) => { const finalErr = formatError({ error, root, args, ctx, info }) if (finalErr instanceof Error) { throw finalErr } console.error(`Non-Error value ${finalErr} returned from custom formatError in authorize plugin`) throw new Error('Not authorized') } let hasWarned = false return plugin({ name: 'NexusAuthorize', description: 'The authorize plugin provides field-level authorization for a schema.', fieldDefTypes: fieldDefTypes, onCreateFieldResolver(config) { const authorize = config.fieldConfig.extensions?.nexus?.config.authorize // If the field doesn't have an authorize field, don't worry about wrapping the resolver if (authorize == null) { return } // If it does have this field, but it's not a function, it's wrong - let's provide a warning if (typeof authorize !== 'function') { console.error( new Error( `The authorize property provided to ${config.fieldConfig.name} with type ${ config.fieldConfig.type } should be a function, saw ${typeof authorize}` ) ) return } // If they have it, but didn't explicitly specify a plugins array, warn them. if (!config.schemaConfig.plugins?.find((p) => p.config.name === 'NexusAuthorize')) { if (!hasWarned) { console.warn( 'The GraphQL Nexus "authorize" feature has been moved to a plugin, add [fieldAuthorizePlugin()] to your makeSchema plugin config to remove this warning.' ) hasWarned = true } } // The authorize wrapping resolver. return function (root, args, ctx, info, next) { let toComplete try { toComplete = authorize(root, args, ctx, info) } catch (e) { toComplete = Promise.reject(e) } return plugin.completeValue( toComplete, (authResult) => { if (authResult === true) { return next(root, args, ctx, info) } const finalFormatError = ensureError(root, args, ctx, info) if (authResult instanceof Error) { finalFormatError(authResult) } if (authResult === false) { finalFormatError(new Error('Not authorized')) } const { fieldName, parentType: { name: parentTypeName }, } = info finalFormatError( new Error( `Nexus authorize for ${parentTypeName}.${fieldName} Expected a boolean or Error, saw ${authResult}` ) ) }, (err) => { ensureError(root, args, ctx, info)(err) } ) } }, }) }