in controlplane/src/core/services/Authorization.ts [23:161]
public async authorize({
headers,
graph,
db,
authContext,
isDeleteOperation,
}: {
headers: Headers;
graph: {
targetId: string;
targetType: 'subgraph' | 'federatedGraph';
};
db: PostgresJsDatabase<typeof schema>;
authContext: AuthContext;
isDeleteOperation?: boolean;
}) {
const { targetId, targetType } = graph;
const { userId, organizationId } = authContext;
const orgRepo = new OrganizationRepository(this.logger, db, this.defaultBillingPlanId);
const fedRepo = new FederatedGraphRepository(this.logger, db, organizationId);
const subgraphRepo = new SubgraphRepository(this.logger, db, organizationId);
const apiKeyRepo = new ApiKeyRepository(db);
const authorization = headers.get('authorization');
const token = authorization?.replace(/^bearer\s+/i, '');
const org = await orgRepo.byId(organizationId);
if (!org) {
throw new Error(`Organization ${organizationId} not found`);
}
if (org.deactivation) {
throw new Error(`The organization is deactivated and is in read-only mode`);
}
/**
* Here we check access permissions using the new RBAC system. The idea is that when the no group have been
* added to the user/api key performing the request, we'll fall back to how the authorization checks were made
* before.
*
* The RBAC instance we are using is created by:
* - `ApiKeyAuthenticator`
* - `AccessTokenAuthenticator`
* - `Authentication`
*/
const { rbac } = authContext;
if (rbac && rbac.groups.length > 0) {
if (rbac.isOrganizationAdminOrDeveloper) {
// When the client have the organization admin or developer roles, they are allowed to access any organization
// resource, we don't need to perform any additional validation
return;
}
if (targetType === 'federatedGraph') {
const federatedGraph = await fedRepo.byTargetId(targetId);
if (
federatedGraph &&
((isDeleteOperation && rbac.canDeleteFederatedGraph(federatedGraph)) ||
(!isDeleteOperation && rbac.hasFederatedGraphWriteAccess(federatedGraph)))
) {
return;
}
throw new UnauthorizedError();
} else if (targetType === 'subgraph') {
const subgraph = await subgraphRepo.byTargetId(targetId);
if (
subgraph &&
((isDeleteOperation && rbac.canDeleteSubGraph(subgraph)) ||
(!isDeleteOperation && rbac.hasSubGraphWriteAccess(subgraph)))
) {
return;
}
throw new UnauthorizedError();
}
}
/**
* Below this point is legacy fallback, in case we couldn't retrieve any group for the requesting client
*/
/**
* We check if the organization has the rbac feature enabled.
* If it is not enabled, we return because the user is authorized to perform all the actions.
* This only works because we validate before if the user has write access to the organization.
* @TODO: Move hasWriteAccess check to the Authorization service.
*/
const rbacEnabled = await orgRepo.isFeatureEnabled(organizationId, 'rbac');
if (!rbacEnabled) {
return;
}
try {
/**
* If the user is using an API key, we verify if the API key has access to the resource.
* We only do this because RBAC is enabled otherwise the key is handled as an admin key.
*/
if (token && token.startsWith('cosmo')) {
const verified = await apiKeyRepo.verifyAPIKeyResources({ apiKey: token, accessedTargetId: targetId });
if (verified) {
return;
} else {
throw new AuthorizationError(EnumStatusCode.ERROR_NOT_AUTHORIZED, 'Not authorized');
}
}
/**
* We check if the user has access to the resource.
*/
if (targetType === 'federatedGraph') {
const fedGraph = await fedRepo.byTargetId(targetId);
if (!(fedGraph?.creatorUserId && fedGraph.creatorUserId === userId)) {
throw new Error('User is not authorized to perform the current action in the federated graph');
}
} else if (targetType === 'subgraph') {
const subgraph = await subgraphRepo.byTargetId(targetId);
const subgraphMembers = await subgraphRepo.getSubgraphMembersByTargetId(targetId);
const userIds = subgraphMembers.map((s) => s.userId);
if (!((subgraph?.creatorUserId && subgraph.creatorUserId === userId) || userIds.includes(userId))) {
throw new Error(
'User is not authorized to perform the current action in the federated graph because the user is not a member of the subgraph',
);
}
} else {
throw new Error('User is not authorized to perform the current action as the target type is not supported');
}
} catch (err: any) {
this.logger.error(err, 'User is not authorized to perform the current action as RBAC is enabled.');
throw new AuthorizationError(
EnumStatusCode.ERROR_NOT_AUTHORIZED,
'You are not authorized to perform the current action as RBAC is enabled. Please communicate with the organization admin to gain access.',
);
}
}