@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
60 lines (59 loc) • 2.84 kB
JavaScript
import { GraphQLError } from "graphql";
import { parseFields } from "../../../subgraph/helpers.js";
import { TypeKind } from "../../../subgraph/state.js";
import { ensureAccessToSelectionSet, ProvisionedAccess, } from "./auth-on-requires-rule.js";
export function AuthOnContextRule(context, supergraph) {
return {
ObjectTypeField(objectTypeState, fieldState) {
for (const { graphId, fromContext } of contextualArgs(fieldState)) {
const subgraphState = context.subgraphStates.get(graphId);
const selectionSet = parseFields(fromContext.selection);
if (!subgraphState || !selectionSet) {
continue;
}
const provisionedAccess = new ProvisionedAccess(objectTypeState, fieldState);
for (const providerName of findContextProviders(subgraphState, fromContext.context)) {
const providerType = supergraph.objectTypes.get(providerName) ??
supergraph.interfaceTypes.get(providerName) ??
supergraph.unionTypes.get(providerName);
if (!providerType ||
!ensureAccessToSelectionSet(supergraph, providerType, selectionSet, provisionedAccess)) {
continue;
}
context.reportError(createContextAccessRequirementError(context.graphIdToName(graphId), `${objectTypeState.name}.${fieldState.name}`, `${context.graphIdToName(graphId)}__${fromContext.context}`));
return;
}
}
},
};
}
function contextualArgs(fieldState) {
const args = [];
for (const arg of fieldState.args.values()) {
for (const [graphId, argInGraph] of arg.byGraph) {
if (argInGraph.fromContext) {
args.push({ graphId, fromContext: argInGraph.fromContext });
}
}
}
return args;
}
function findContextProviders(subgraphState, contextName) {
const providers = [];
for (const [typeName, typeState] of subgraphState.types) {
if ((typeState.kind === TypeKind.OBJECT ||
typeState.kind === TypeKind.INTERFACE ||
typeState.kind === TypeKind.UNION) &&
typeState.contexts.has(contextName)) {
providers.push(typeName);
}
}
return providers;
}
function createContextAccessRequirementError(graphName, fieldCoordinate, contextName) {
return new GraphQLError(`[${graphName}] Field "${fieldCoordinate}" does not specify necessary @authenticated, @requiresScopes and/or @policy auth requirements to access the transitive data in context ${contextName} from @fromContext selection set.`, {
extensions: {
code: "MISSING_TRANSITIVE_AUTH_REQUIREMENTS",
},
});
}