From bc3112a999c81b48f238d00bdaa558b40b75a33e Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Tue, 21 Jul 2026 15:31:05 +0200
Subject: [PATCH] Fix: allow API key creation without Roles permission (#23102)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Problem
A user with the **API keys & webhooks** permission but **without** the
**Roles** setting permission cannot create an API key through the UI.
The role selector relies on the `getRoles` query, which is guarded by
the `ROLES` permission, so the roles list comes back empty,
`SettingsDevelopersRoleSelector` early-returns, and no role can be
selected — leaving the form unsavable.
## Fix
Expose the assignable roles through the API-key permission scope so
users can **pick** a role to assign to an API key without being able to
**edit** roles.
- **Backend**: add `getApiKeyRoles` query on `ApiKeyResolver` (already
guarded by `API_KEYS_AND_WEBHOOKS`), backed by
`ApiKeyRoleService.getApiKeyAssignableRoles` which returns roles where
`canBeAssignedToApiKeys = true`.
- **Frontend**: add a `GetApiKeyRoles` query and use it in the API key
create and detail pages instead of `getRoles`. The role selector prop
type is narrowed to the fields it actually uses.
---
.../src/metadata/generated/schema.graphql | 1 +
.../src/metadata/generated/schema.ts | 2 ++
.../src/metadata/generated/types.ts | 3 +++
.../src/generated-metadata/graphql.ts | 7 +++++++
.../SettingsDevelopersRoleSelector.tsx | 9 +++++++--
.../developers/graphql/queries/getApiKeyRoles.ts | 12 ++++++++++++
.../api-keys/SettingsDevelopersApiKeyDetail.tsx | 8 +++++---
.../api-keys/SettingsDevelopersApiKeysNew.tsx | 12 +++++++-----
.../core-modules/api-key/api-key.resolver.ts | 7 +++++++
.../api-key/services/api-key-role.service.ts | 16 +++++++++++++++-
10 files changed, 66 insertions(+), 11 deletions(-)
create mode 100644 packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
index f0b9964a63..4134d0e67e 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
@@ -3062,6 +3062,7 @@ type Query {
getViewFieldGroups(viewId: String!): [ViewFieldGroup!]!
getViewFieldGroup(id: String!): ViewFieldGroup
apiKeys: [ApiKey!]!
+ getApiKeyRoles: [Role!]!
apiKey(input: GetApiKeyInput!): ApiKey
getInviteSuggestions: [InviteSuggestion!]!
applicationConnectionProviders(applicationId: UUID!): [ApplicationConnectionProvider!]!
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
index b6cbda603a..56728c9221 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
@@ -2720,6 +2720,7 @@ export interface Query {
getViewFieldGroups: ViewFieldGroup[]
getViewFieldGroup?: ViewFieldGroup
apiKeys: ApiKey[]
+ getApiKeyRoles: Role[]
apiKey?: ApiKey
getInviteSuggestions: InviteSuggestion[]
applicationConnectionProviders: ApplicationConnectionProvider[]
@@ -5913,6 +5914,7 @@ export interface QueryGenqlSelection{
getViewFieldGroups?: (ViewFieldGroupGenqlSelection & { __args: {viewId: Scalars['String']} })
getViewFieldGroup?: (ViewFieldGroupGenqlSelection & { __args: {id: Scalars['String']} })
apiKeys?: ApiKeyGenqlSelection
+ getApiKeyRoles?: RoleGenqlSelection
apiKey?: (ApiKeyGenqlSelection & { __args: {input: GetApiKeyInput} })
getInviteSuggestions?: InviteSuggestionGenqlSelection
applicationConnectionProviders?: (ApplicationConnectionProviderGenqlSelection & { __args: {applicationId: Scalars['UUID']} })
diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts
index f01348b7e0..3f5ed57ebd 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/types.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts
@@ -6199,6 +6199,9 @@ export default {
"apiKeys": [
2
],
+ "getApiKeyRoles": [
+ 29
+ ],
"apiKey": [
2,
{
diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index 2ad93994ec..5ea53486e8 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -4387,6 +4387,7 @@ export type Query = {
frontComponents: Array;
getAddressDetails: PlaceDetailsResult;
getAiSystemPromptPreview: AiSystemPromptPreview;
+ getApiKeyRoles: Array;
getApprovedAccessDomains: Array;
getAutoCompleteAddress: Array;
getAvailablePackages: Scalars['JSON']['output'];
@@ -8070,6 +8071,11 @@ export type GetApiKeyQueryVariables = Exact<{
export type GetApiKeyQuery = { __typename?: 'Query', apiKey?: { __typename?: 'ApiKey', createdAt: string, id: string, name: string, expiresAt: string, revokedAt?: string | null, role: { __typename?: 'Role', id: string, label: string, icon?: string | null } } | null };
+export type GetApiKeyRolesQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type GetApiKeyRolesQuery = { __typename?: 'Query', getApiKeyRoles: Array<{ __typename?: 'Role', id: string, label: string, icon?: string | null, canBeAssignedToApiKeys: boolean }> };
+
export type GetApiKeysQueryVariables = Exact<{ [key: string]: never; }>;
@@ -9073,6 +9079,7 @@ export const RevokeApiKeyDocument = {"kind":"Document","definitions":[{"kind":"O
export const UpdateApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApiKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode;
export const UpdateWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWebhookInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWebhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode;
export const GetApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetApiKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode;
+export const GetApiKeyRolesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKeyRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getApiKeyRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}}]}}]} as unknown as DocumentNode;
export const GetApiKeysDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode;
export const GetWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode;
export const GetWebhooksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode;
diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx
index acddde4749..d87cccda66 100644
--- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx
+++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx
@@ -1,13 +1,18 @@
-import { type RoleWithPartialMembers } from '@/settings/roles/types/RoleWithPartialMembers';
import { Select } from '@/ui/input/components/Select';
+import { type Role } from '~/generated-metadata/graphql';
import { type IconComponent, useIcons } from 'twenty-ui/icon';
+type ApiKeyAssignableRole = Pick<
+ Role,
+ 'id' | 'label' | 'icon' | 'canBeAssignedToApiKeys'
+>;
+
type SettingsDevelopersRoleSelectorProps = {
value?: string | null | undefined;
onChange: (roleId: string) => void;
label?: string;
description?: string;
- roles: RoleWithPartialMembers[];
+ roles: ApiKeyAssignableRole[];
};
export const SettingsDevelopersRoleSelector = ({
diff --git a/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts b/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts
new file mode 100644
index 0000000000..4c1758ad5f
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts
@@ -0,0 +1,12 @@
+import gql from 'graphql-tag';
+
+export const GET_API_KEY_ROLES = gql`
+ query GetApiKeyRoles {
+ getApiKeyRoles {
+ id
+ label
+ icon
+ canBeAssignedToApiKeys
+ }
+ }
+`;
diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
index 778c0f5f8f..1e1d006ef6 100644
--- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
@@ -32,7 +32,7 @@ import {
CreateApiKeyDocument,
GenerateApiKeyTokenDocument,
GetApiKeyDocument,
- GetRolesDocument,
+ GetApiKeyRolesDocument,
RevokeApiKeyDocument,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
@@ -100,9 +100,11 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
}, [apiKeyData]);
- const { data: rolesData, loading: rolesLoading } = useQuery(GetRolesDocument);
+ const { data: rolesData, loading: rolesLoading } = useQuery(
+ GetApiKeyRolesDocument,
+ );
- const roles = rolesData?.getRoles ?? [];
+ const roles = rolesData?.getApiKeyRoles ?? [];
const apiKey = apiKeyData?.apiKey;
const [apiKeyName, setApiKeyName] = useState('');
diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
index 9496890b14..86e7c37c6a 100644
--- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
@@ -21,8 +21,8 @@ import { Section } from 'twenty-ui/layout';
import {
CreateApiKeyDocument,
GenerateApiKeyTokenDocument,
+ GetApiKeyRolesDocument,
GetApiKeysDocument,
- GetRolesDocument,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { SETTINGS_API_WEBHOOKS_TABS } from '~/pages/settings/api-webhooks/constants/SettingsApiWebhooksTabs';
@@ -31,8 +31,10 @@ export const SettingsDevelopersApiKeysNew = () => {
const { t } = useLingui();
const [generateOneApiKeyToken] = useMutation(GenerateApiKeyTokenDocument);
const navigateSettings = useNavigateSettings();
- const { data: rolesData, loading: rolesLoading } = useQuery(GetRolesDocument);
- const roles = rolesData?.getRoles ?? [];
+ const { data: rolesData, loading: rolesLoading } = useQuery(
+ GetApiKeyRolesDocument,
+ );
+ const roles = rolesData?.getApiKeyRoles ?? [];
const [formValues, setFormValues] = useState<{
name: string;
@@ -45,8 +47,8 @@ export const SettingsDevelopersApiKeysNew = () => {
});
useEffect(() => {
- if (isDefined(rolesData?.getRoles)) {
- const apiKeyAssignableRoles = rolesData.getRoles.filter(
+ if (isDefined(rolesData?.getApiKeyRoles)) {
+ const apiKeyAssignableRoles = rolesData.getApiKeyRoles.filter(
(role) => role.canBeAssignedToApiKeys,
);
if (apiKeyAssignableRoles.length > 0) {
diff --git a/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts b/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts
index e838b33c79..bd13f69b2b 100644
--- a/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts
@@ -43,6 +43,13 @@ export class ApiKeyResolver {
return this.apiKeyService.findActiveByWorkspaceId(workspace.id);
}
+ @Query(() => [RoleDTO])
+ async getApiKeyRoles(
+ @AuthWorkspace() workspace: WorkspaceEntity,
+ ): Promise {
+ return this.apiKeyRoleService.getApiKeyAssignableRoles(workspace.id);
+ }
+
@Query(() => ApiKeyEntity, { nullable: true })
async apiKey(
@Args('input') input: GetApiKeyInput,
diff --git a/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts b/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts
index 314a7bddae..cf271f69d8 100644
--- a/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts
@@ -14,7 +14,10 @@ import { RoleTargetService } from 'src/engine/metadata-modules/role-target/servi
import { type RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { fromFlatRoleToRoleDto } from 'src/engine/metadata-modules/role/utils/fromFlatRoleToRoleDto.util';
-import { fromRoleEntityToRoleDto } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util';
+import {
+ fromRoleEntitiesToRoleDtos,
+ fromRoleEntityToRoleDto,
+} from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util';
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@@ -173,6 +176,17 @@ export class ApiKeyRoleService {
};
}
+ public async getApiKeyAssignableRoles(
+ workspaceId: string,
+ ): Promise {
+ const roles = await this.roleRepository.find(workspaceId, {
+ where: { canBeAssignedToApiKeys: true },
+ order: { label: 'ASC' },
+ });
+
+ return fromRoleEntitiesToRoleDtos(roles);
+ }
+
public async getRolesByApiKeys({
apiKeyIds,
workspaceId,