Feat: role applicability controls (#14239)
Closes [#1404](https://github.com/twentyhq/core-team-issues/issues/1404) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@@ -55,6 +55,9 @@ describe('ApiKeyRoleService', () => {
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToApiKeys: true,
|
||||
};
|
||||
|
||||
const mockNewRole: Partial<RoleEntity> = {
|
||||
@@ -427,6 +430,10 @@ describe('ApiKeyRoleService', () => {
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToApiKeys: true,
|
||||
standardId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSource, type EntityManager, In, Repository } from 'typeorm';
|
||||
import {
|
||||
DataSource,
|
||||
type EntityManager,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
Repository,
|
||||
} from 'typeorm';
|
||||
|
||||
import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity';
|
||||
import {
|
||||
@@ -145,6 +152,13 @@ export class ApiKeyRoleService {
|
||||
);
|
||||
}
|
||||
|
||||
if (!role.canBeAssignedToApiKeys) {
|
||||
throw new ApiKeyException(
|
||||
`Role "${role.label}" cannot be assigned to API keys`,
|
||||
ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS,
|
||||
);
|
||||
}
|
||||
|
||||
const existingRoleTarget = await this.roleTargetsRepository.findOne({
|
||||
where: {
|
||||
apiKeyId,
|
||||
@@ -190,4 +204,35 @@ export class ApiKeyRoleService {
|
||||
|
||||
return rolesMap;
|
||||
}
|
||||
|
||||
public async getApiKeysAssignedToRole(
|
||||
roleId: string,
|
||||
workspaceId: string,
|
||||
): Promise<ApiKey[]> {
|
||||
const roleTargets = await this.roleTargetsRepository.find({
|
||||
where: {
|
||||
roleId,
|
||||
workspaceId,
|
||||
apiKeyId: Not(IsNull()),
|
||||
},
|
||||
});
|
||||
|
||||
const apiKeyIds = roleTargets
|
||||
.map((roleTarget) => roleTarget.apiKeyId)
|
||||
.filter((apiKeyId): apiKeyId is string => apiKeyId !== null);
|
||||
|
||||
if (!apiKeyIds.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const apiKeys = await this.apiKeyRepository.find({
|
||||
where: {
|
||||
id: In(apiKeyIds),
|
||||
workspaceId,
|
||||
revokedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
return apiKeys;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum ApiKeyExceptionCode {
|
||||
API_KEY_REVOKED = 'API_KEY_REVOKED',
|
||||
API_KEY_EXPIRED = 'API_KEY_EXPIRED',
|
||||
API_KEY_NO_ROLE_ASSIGNED = 'API_KEY_NO_ROLE_ASSIGNED',
|
||||
ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS = 'ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS',
|
||||
}
|
||||
|
||||
+4
@@ -27,6 +27,10 @@ export const apiKeyGraphqlApiExceptionHandler = (error: Error) => {
|
||||
throw new ForbiddenError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
case ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user