@pothos/plugin-scope-auth
Version:
A Pothos plugin for adding scope based authorization checks to your GraphQL Schema
222 lines (195 loc) • 6.14 kB
text/typescript
import type {
FieldNullability,
InputFieldMap,
InputShapeFromFields,
MaybePromise,
Merge,
SchemaTypes,
ShapeFromTypeParam,
TypeParam,
UnionToIntersection,
} from '@pothos/core';
import type { GraphQLResolveInfo } from 'graphql';
import type RequestCache from './request-cache.js';
export interface ScopeAuthPluginOptions<Types extends SchemaTypes> {
unauthorizedError?: UnauthorizedForTypeErrorFn<Types, {}>;
cacheKey?: (value: unknown) => unknown;
runScopesOnType?: boolean;
treatErrorsAsUnauthorized?: boolean;
authorizeOnSubscribe?: boolean;
defaultStrategy?: Types['DefaultAuthStrategy'];
authScopes: ScopeAuthInitializer<Types>;
}
export interface BuiltInScopes<Types extends SchemaTypes> {
$all?: true extends true ? AuthScopeMap<Types> : never;
$any?: true extends true ? AuthScopeMap<Types> : never;
$granted?: string;
}
export type AuthScopeMap<Types extends SchemaTypes> = Merge<
BuiltInScopes<Types> & Partial<Types['AuthScopes']>
>;
export type ScopeLoaderMap<Types extends SchemaTypes> = {
[K in keyof Types['AuthScopes']]:
| boolean
| ((param: Types['AuthScopes'][K]) => MaybePromise<boolean>);
};
export type ScopeAuthInitializer<Types extends SchemaTypes> = (
context: Types['Context'],
) => MaybePromise<ScopeLoaderMap<Types>>;
export type TypeAuthScopesFunction<Types extends SchemaTypes, Parent> = (
parent: Parent,
context: Types['Context'],
) => MaybePromise<AuthScopeMap<Types> | boolean>;
export type TypeAuthScopes<Types extends SchemaTypes, Parent> =
| AuthScopeMap<Types>
| TypeAuthScopesFunction<Types, Parent>;
export type FieldAuthScopes<Types extends SchemaTypes, Parent, Args extends {}> =
| AuthScopeMap<Types>
| ((
parent: Parent,
args: Args,
context: Types['Context'],
info: GraphQLResolveInfo,
) => MaybePromise<AuthScopeMap<Types> | boolean>);
export type TypeGrantScopes<Types extends SchemaTypes, Parent> = (
parent: Parent,
context: Types['Context'],
) => MaybePromise<readonly string[]>;
export type FieldGrantScopes<Types extends SchemaTypes, Parent, Args extends {}> =
| string[]
| ((
parent: Parent,
args: Args,
context: Types['Context'],
info: GraphQLResolveInfo,
) => MaybePromise<readonly string[]>);
export enum AuthScopeFailureType {
AuthScope = 'AuthScope',
AuthScopeFunction = 'AuthScopeFunction',
GrantedScope = 'GrantedScope',
AnyAuthScopes = 'AnyAuthScopes',
AllAuthScopes = 'AllAuthScopes',
Unknown = 'Unknown',
}
export interface AuthScopeFailure {
kind: AuthScopeFailureType.AuthScope;
scope: string;
parameter: unknown;
error: Error | null;
}
export interface AuthScopeFunctionFailure {
kind: AuthScopeFailureType.AuthScopeFunction;
error: Error | null;
}
export interface UnknownAuthFailure {
kind: AuthScopeFailureType.Unknown;
}
export interface AnyAuthScopesFailure {
kind: AuthScopeFailureType.AnyAuthScopes;
failures: AuthFailure[];
}
export interface AllAuthScopesFailure {
kind: AuthScopeFailureType.AllAuthScopes;
failures: AuthFailure[];
}
export interface GrantedScopeFailure {
kind: AuthScopeFailureType.GrantedScope;
scope: string;
}
export type AuthFailure =
| AllAuthScopesFailure
| AnyAuthScopesFailure
| AuthScopeFailure
| AuthScopeFunctionFailure
| GrantedScopeFailure
| UnknownAuthFailure;
export interface ForbiddenResult {
message: string;
failure: AuthFailure;
}
export interface ResolveStep<Types extends SchemaTypes> {
run: (
cache: RequestCache<Types>,
parent: unknown,
args: Record<string, unknown>,
context: {},
info: GraphQLResolveInfo,
setResolved: (val: unknown) => void,
) => MaybePromise<AuthFailure | null>;
errorMessage:
| string
| ((
parent: unknown,
args: Record<string, unknown>,
context: {},
info: GraphQLResolveInfo,
) => string);
}
export type ContextForAuth<
Types extends SchemaTypes,
Scopes,
> = 'any' extends Types['DefaultAuthStrategy']
? ContextForAuthUnion<Types, Scopes>
: UnionToIntersection<ContextForAuthUnion<Types, Scopes>>;
type ContextForAuthUnion<Types extends SchemaTypes, Scopes> = Scopes extends (
// biome-ignore lint/suspicious/noExplicitAny: this is fine
...args: any[]
) => infer R
? ContextForAuthUnion<Types, R>
: Scopes extends boolean
? Types['Context']
: keyof Scopes extends infer Scope
? Scope extends keyof Types['AuthContexts']
? Types['AuthContexts'][Scope]
: Scope extends '$any'
? ContextForAuthUnion<Types, Scopes[Scope & keyof Scopes]>
: Scope extends '$all'
? UnionToIntersection<ContextForAuthUnion<Types, Scopes[Scope & keyof Scopes]>>
: Types['Context']
: never;
export type UnauthorizedResolver<
Types extends SchemaTypes,
ParentShape,
Type extends TypeParam<Types>,
Nullable extends FieldNullability<Type>,
Args extends InputFieldMap,
> = (
parent: ParentShape,
args: InputShapeFromFields<Args>,
context: Types['Context'],
info: GraphQLResolveInfo,
error: Error,
) => MaybePromise<ShapeFromTypeParam<Types, Type, Nullable>>;
export type UnauthorizedErrorFn<
Types extends SchemaTypes,
ParentShape,
Args extends InputFieldMap,
> = (
parent: ParentShape,
args: InputShapeFromFields<Args>,
context: Types['Context'],
info: GraphQLResolveInfo,
result: ForbiddenResult,
) => Error | string;
export type UnauthorizedForTypeErrorFn<Types extends SchemaTypes, ParentShape> = (
parent: ParentShape,
context: Types['Context'],
info: GraphQLResolveInfo,
result: ForbiddenResult,
) => Error | string;
export interface UnauthorizedOptions<
Types extends SchemaTypes,
ParentShape,
Type extends TypeParam<Types>,
Nullable extends FieldNullability<Type>,
Args extends InputFieldMap,
> {
unauthorizedError?: UnauthorizedErrorFn<Types, ParentShape, Args>;
unauthorizedResolver?: UnauthorizedResolver<Types, ParentShape, Type, Nullable, Args>;
}
export type ReplaceContext<Types extends SchemaTypes, Context extends object> = Omit<
Types,
'Context'
> & {
Context: Context;
};