bc3112a999
## 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" />
61 lines
1.3 KiB
TypeScript
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
|
|
/>
|
|
);
|
|
};
|