@envelop/generic-auth
Version:
This plugin allows you to implement custom authentication flow by providing a custom user resolver based on the original HTTP request. The resolved user is injected into the GraphQL execution `context`, and you can use it in your resolvers to fetch the cu
112 lines (111 loc) • 5.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useGenericAuth = exports.defaultProtectSingleValidateFn = exports.defaultProtectAllValidateFn = exports.SKIP_AUTH_DIRECTIVE_SDL = exports.DIRECTIVE_SDL = exports.UnauthenticatedError = void 0;
const graphql_1 = require("graphql");
const extended_validation_1 = require("@envelop/extended-validation");
class UnauthenticatedError extends graphql_1.GraphQLError {
}
exports.UnauthenticatedError = UnauthenticatedError;
exports.DIRECTIVE_SDL = `
directive @auth on FIELD_DEFINITION
`;
exports.SKIP_AUTH_DIRECTIVE_SDL = `
directive @skipAuth on FIELD_DEFINITION
`;
function defaultProtectAllValidateFn(params) {
if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) {
const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
}
}
exports.defaultProtectAllValidateFn = defaultProtectAllValidateFn;
function defaultProtectSingleValidateFn(params) {
if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) {
const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
}
}
exports.defaultProtectSingleValidateFn = defaultProtectSingleValidateFn;
const useGenericAuth = (options) => {
const contextFieldName = options.contextFieldName || 'currentUser';
if (options.mode === 'protect-all' || options.mode === 'protect-granular') {
const directiveOrExtensionFieldName = options.directiveOrExtensionFieldName ?? (options.mode === 'protect-all' ? 'skipAuth' : 'auth');
const validateUser = options.validateUser ??
(options.mode === 'protect-all' ? defaultProtectAllValidateFn : defaultProtectSingleValidateFn);
const extractAuthMeta = (input) => {
return {
fieldAuthExtension: input.extensions?.[directiveOrExtensionFieldName],
fieldAuthDirectiveNode: input.astNode?.directives?.find(directive => directive.name.value === directiveOrExtensionFieldName),
};
};
return {
onPluginInit({ addPlugin }) {
addPlugin((0, extended_validation_1.useExtendedValidation)({
rules: [
function AuthorizationExtendedValidationRule(context, args) {
const user = args.contextValue[contextFieldName];
const handleField = (fieldNode, objectType) => {
const field = objectType.getFields()[fieldNode.name.value];
if (field == null) {
// field is null/undefined if this is an introspection field
return;
}
const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field);
const error = validateUser({
user,
fieldNode,
objectType,
fieldAuthDirectiveNode,
fieldAuthExtension,
});
if (error) {
context.reportError(error);
}
};
return {
Field(node) {
const fieldType = (0, graphql_1.getNamedType)(context.getParentType());
if ((0, graphql_1.isIntrospectionType)(fieldType)) {
return false;
}
if ((0, graphql_1.isObjectType)(fieldType)) {
handleField(node, fieldType);
}
else if ((0, graphql_1.isUnionType)(fieldType)) {
for (const objectType of fieldType.getTypes()) {
handleField(node, objectType);
}
}
else if ((0, graphql_1.isInterfaceType)(fieldType)) {
for (const objectType of args.schema.getImplementations(fieldType).objects) {
handleField(node, objectType);
}
}
return undefined;
},
};
},
],
}));
},
async onContextBuilding({ context, extendContext }) {
const user = await options.resolveUserFn(context);
extendContext({
[contextFieldName]: user,
});
},
};
}
if (options.mode === 'resolve-only') {
return {
async onContextBuilding({ context, extendContext }) {
const user = await options.resolveUserFn(context);
extendContext({
[contextFieldName]: user,
});
},
};
}
return {};
};
exports.useGenericAuth = useGenericAuth;