@accounter/server
Version:
Accounter GraphQL server
100 lines • 4.44 kB
JavaScript
import { defaultFieldResolver, GraphQLError } from 'graphql';
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
import { AuthContextProvider } from '../providers/auth-context.provider.js';
function extractModuleIdFromResolver(resolve) {
if (typeof resolve !== 'function') {
return undefined;
}
const resolverWithSymbols = resolve;
for (const symbol of Object.getOwnPropertySymbols(resolve)) {
const metadata = resolverWithSymbols[symbol];
if (metadata &&
typeof metadata === 'object' &&
'moduleId' in metadata &&
typeof metadata.moduleId === 'string') {
return metadata.moduleId;
}
}
return undefined;
}
function resolveInjector(context, moduleId) {
if (context?.injector) {
return context.injector;
}
if (context?.ɵinjector) {
return context.ɵinjector;
}
if (typeof context?.ɵgetModuleContext !== 'function') {
return undefined;
}
const moduleCandidates = [moduleId, 'auth'].filter((candidate) => !!candidate);
for (const candidate of moduleCandidates) {
try {
const moduleContext = context.ɵgetModuleContext(candidate, context);
if (moduleContext?.injector) {
return moduleContext.injector;
}
}
catch {
// Ignore invalid module candidate and continue with the next fallback.
}
}
return undefined;
}
function throwAuthInfrastructureError(reason) {
console.error(`[auth-directives] ${reason}`);
throw new GraphQLError('Authentication subsystem unavailable', {
extensions: { code: 'INTERNAL_SERVER_ERROR' },
});
}
export function authDirectiveTransformer(schema) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: fieldConfig => {
const requiresAuthDirective = getDirective(schema, fieldConfig, 'requiresAuth')?.[0];
const requiresRoleDirective = getDirective(schema, fieldConfig, 'requiresRole')?.[0];
const requiresAnyRoleDirective = getDirective(schema, fieldConfig, 'requiresAnyRole')?.[0];
if (!requiresAuthDirective && !requiresRoleDirective && !requiresAnyRoleDirective) {
return fieldConfig;
}
const { resolve = defaultFieldResolver } = fieldConfig;
const moduleId = extractModuleIdFromResolver(resolve);
return {
...fieldConfig,
resolve: async (source, args, context, info) => {
const injector = resolveInjector(context, moduleId);
if (!injector) {
throwAuthInfrastructureError(`Failed to resolve injector for auth directives (moduleId: ${moduleId ?? 'unknown'})`);
}
const authProvider = injector.get(AuthContextProvider);
if (!authProvider) {
throwAuthInfrastructureError(`AuthContextProvider is not available in injector (moduleId: ${moduleId ?? 'unknown'})`);
}
const authContext = await authProvider.getAuthContext();
if (!authContext?.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
if (requiresRoleDirective) {
const role = requiresRoleDirective.role;
if (authContext.user.roleId !== role) {
throw new GraphQLError(`Requires role: ${role}`, {
extensions: { code: 'FORBIDDEN' },
});
}
}
if (requiresAnyRoleDirective) {
const roles = requiresAnyRoleDirective.roles;
if (!roles.includes(authContext.user.roleId)) {
throw new GraphQLError(`Requires one of roles: ${roles.join(', ')}`, {
extensions: { code: 'FORBIDDEN' },
});
}
}
return resolve(source, args, context, info);
},
};
},
});
}
//# sourceMappingURL=auth-directives.js.map