Unify auth context → role permission config resolution into a single pure utility (#18927)

## Summary

- Consolidates duplicated auth-context-to-role-ID resolution logic
(previously in
`PermissionsService.resolveRolePermissionConfigFromAuthContext` and
`CommonBaseQueryRunnerService.getRoleIdOrThrow`) into a single pure
utility function `resolveRolePermissionConfig` in the ORM layer
- The utility is synchronous and operates on cached data
(`userWorkspaceRoleMap`, `apiKeyRoleMap`) already loaded into the
workspace context — no async calls, no service dependencies
- Adds `apiKeyRoleMap` to `ORMWorkspaceContext` (it was already in the
workspace cache, just not loaded into the ORM context)
- Removes `PermissionsService` dependency from
`NavigationMenuItemRecordIdentifierService`
- Removes `UserRoleService` and `ApiKeyRoleService` injections from
`CommonBaseQueryRunnerService`

## Test plan

- [ ] Existing typecheck passes (`npx nx typecheck twenty-server`)
- [ ] Verify record identifier resolution still works for navigation
menu items (user, system, API key, and application auth contexts)
- [ ] Verify GraphQL CRUD queries still enforce correct role-based
permissions
- [ ] Verify API key authenticated requests resolve permissions
correctly


Made with [Cursor](https://cursor.com)
This commit is contained in:
Félix Malfait
2026-03-24 21:37:58 +01:00
committed by GitHub
parent 37640521d5
commit d5b41b2801
10 changed files with 100 additions and 152 deletions
@@ -321,6 +321,7 @@ describe('WorkspaceEntityManager', () => {
userWorkspaceRoleMap: {
'user-workspace-id': 'role-id',
},
apiKeyRoleMap: {},
};
setWorkspaceContext(mockWorkspaceContext);
@@ -93,6 +93,7 @@ export class GlobalWorkspaceOrmManager {
rolesPermissions: permissionsPerRoleId,
ORMEntityMetadatas: entityMetadatas,
userWorkspaceRoleMap,
apiKeyRoleMap,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
@@ -103,6 +104,7 @@ export class GlobalWorkspaceOrmManager {
'rolesPermissions',
'ORMEntityMetadatas',
'userWorkspaceRoleMap',
'apiKeyRoleMap',
'flatRowLevelPermissionPredicateMaps',
'flatRowLevelPermissionPredicateGroupMaps',
]);
@@ -122,6 +124,7 @@ export class GlobalWorkspaceOrmManager {
permissionsPerRoleId,
entityMetadatas,
userWorkspaceRoleMap,
apiKeyRoleMap,
};
}
@@ -167,6 +170,7 @@ export class GlobalWorkspaceOrmManager {
permissionsPerRoleId: {},
entityMetadatas,
userWorkspaceRoleMap: {},
apiKeyRoleMap: {},
};
}
}
@@ -25,6 +25,7 @@ export type ORMWorkspaceContext = {
permissionsPerRoleId: ObjectsPermissionsByRoleId;
entityMetadatas: EntityMetadata[];
userWorkspaceRoleMap: UserWorkspaceRoleMap;
apiKeyRoleMap: Record<string, string>;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
};
@@ -0,0 +1,52 @@
import { isDefined } from 'twenty-shared/utils';
import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard';
import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard';
import { isSystemAuthContext } from 'src/engine/core-modules/auth/guards/is-system-auth-context.guard';
import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard';
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
import { type UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/types/user-workspace-role-map';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
export const resolveRolePermissionConfig = ({
authContext,
userWorkspaceRoleMap,
apiKeyRoleMap,
}: {
authContext: WorkspaceAuthContext;
userWorkspaceRoleMap: UserWorkspaceRoleMap;
apiKeyRoleMap: Record<string, string>;
}): RolePermissionConfig | null => {
if (isSystemAuthContext(authContext)) {
return { shouldBypassPermissionChecks: true };
}
if (isApiKeyAuthContext(authContext)) {
const roleId = apiKeyRoleMap[authContext.apiKey.id];
if (!isDefined(roleId)) {
return null;
}
return { intersectionOf: [roleId] };
}
if (
isApplicationAuthContext(authContext) &&
isDefined(authContext.application.defaultRoleId)
) {
return { intersectionOf: [authContext.application.defaultRoleId] };
}
if (isUserAuthContext(authContext)) {
const roleId = userWorkspaceRoleMap[authContext.userWorkspaceId];
if (!isDefined(roleId)) {
return null;
}
return { intersectionOf: [roleId] };
}
return null;
};