Files
twenty/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx
T
Marie bc3112a999 Fix: allow API key creation without Roles permission (#23102)
## 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.

<img width="1058" height="408" alt="Screenshot 2026-07-21 at 13 38 34"
src="https://github.com/user-attachments/assets/fe97ba78-e116-458d-af10-11c5969c4636"
/>

## 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.

<img width="1025" height="455" alt="Screenshot 2026-07-21 at 13 45 01"
src="https://github.com/user-attachments/assets/f1be8f97-5a30-4afc-9eee-c928f4607471"
/>
2026-07-21 13:31:05 +00:00

61 lines
1.3 KiB
TypeScript

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: ApiKeyAssignableRole[];
};
export const SettingsDevelopersRoleSelector = ({
value,
onChange,
label,
description,
roles,
}: SettingsDevelopersRoleSelectorProps) => {
const { getIcon } = useIcons();
if (roles.length === 0) {
return null;
}
const options = roles.reduce<
Array<{ label: string; value: string; Icon?: IconComponent }>
>((acc, role) => {
{
if (role.canBeAssignedToApiKeys) {
acc.push({
label: role.label,
value: role.id,
Icon: getIcon(role.icon) ?? undefined,
});
}
return acc;
}
}, []);
const selectValue = value || options[0]?.value;
return (
<Select
dropdownId="role-selector"
options={options}
value={selectValue}
onChange={onChange}
label={label}
description={description}
withSearchInput
fullWidth
/>
);
};