@pothos/plugin-scope-auth
Version:
A Pothos plugin for adding scope based authorization checks to your GraphQL Schema
119 lines (101 loc) • 3.5 kB
text/typescript
import {
isThenable,
type MaybePromise,
type PothosOutputFieldConfig,
type SchemaTypes,
} from '@pothos/core';
import type { GraphQLResolveInfo } from 'graphql';
import type { PothosScopeAuthPlugin, UnauthorizedErrorFn } from '.';
import { ForbiddenError } from './errors';
import RequestCache from './request-cache';
import { AuthScopeFailureType, type ResolveStep, type UnauthorizedResolver } from './types';
const defaultUnauthorizedResolver: UnauthorizedResolver<never, never, never, never, never> = (
_root,
_args,
_context,
_info,
error,
) => {
throw error;
};
export function resolveHelper<Types extends SchemaTypes>(
steps: ResolveStep<Types>[],
plugin: PothosScopeAuthPlugin<Types>,
fieldConfig: PothosOutputFieldConfig<Types>,
) {
const unauthorizedResolver =
fieldConfig.pothosOptions.unauthorizedResolver ?? defaultUnauthorizedResolver;
const globalUnauthorizedError = plugin.builder.options.scopeAuth?.unauthorizedError;
const defaultUnauthorizedError: UnauthorizedErrorFn<Types, object, {}> = (
parent,
_args,
context,
info,
result,
) => {
if (globalUnauthorizedError) {
return globalUnauthorizedError(parent, context, info, result);
}
if (
(result.failure.kind === AuthScopeFailureType.AuthScope ||
result.failure.kind === AuthScopeFailureType.AuthScopeFunction) &&
result.failure.error
) {
return result.failure.error;
}
return result.message;
};
const createError: UnauthorizedErrorFn<Types, object, {}> =
fieldConfig.pothosOptions.unauthorizedError ?? defaultUnauthorizedError;
return (parent: unknown, args: {}, context: Types['Context'], info: GraphQLResolveInfo) => {
let resolvedValue: unknown;
const cache = RequestCache.fromContext(context, plugin.builder);
function runSteps(index: number): MaybePromise<unknown> {
for (let i = index; i < steps.length; i += 1) {
const { run, errorMessage } = steps[i];
const stepResult = run(cache, parent, args, context, info, (val) => {
resolvedValue = val;
});
if (isThenable(stepResult)) {
return stepResult.then((result) => {
if (result) {
const error = createError(parent as object, args, context, info, {
message:
typeof errorMessage === 'function'
? errorMessage(parent, args, context, info)
: errorMessage,
failure: result,
});
return unauthorizedResolver(
parent as never,
args,
context as never,
info,
typeof error === 'string' ? new ForbiddenError(error, result) : error,
);
}
return runSteps(i + 1);
});
}
if (stepResult) {
const error = createError(parent as object, args, context, info, {
message:
typeof errorMessage === 'function'
? errorMessage(parent, args, context, info)
: errorMessage,
failure: stepResult,
});
return unauthorizedResolver(
parent as never,
args,
context as never,
info,
typeof error === 'string' ? new ForbiddenError(error, stepResult) : error,
);
}
}
return resolvedValue;
}
return runSteps(0);
};
}