feat: support authType on AI providers for IAM role authentication (#19016)

## Summary
- Adds an `authType` field (`'api_key' | 'access_key' | 'iam_role'`) to
AI provider config
- Providers like Amazon Bedrock that authenticate via IAM role (instance
profile) can now be registered without explicit API keys or access keys
- Backend: `isProviderConfigured()` checks `apiKey || accessKeyId ||
authType`
- Frontend admin panel: shows green "Configured" badge and "IAM role"
description for providers with `authType: "iam_role"`
- Provider detail page shows "IAM role (instance profile)" in the
credentials row

## Companion PR
- twentyhq/twenty-infra#528 — patches `authType: "iam_role"` into
dev/staging Bedrock catalogs

## Changed files
- **Backend**: new `AiProviderAuthType` type, `isProviderConfigured`
util, updated registry + resolver
- **Frontend**: new `AiProviderAuthType` type, updated provider list
card + detail page

## Test plan
- [ ] Deploy with a Bedrock catalog that includes `"authType":
"iam_role"` — verify Bedrock shows "Configured" in admin AI panel
- [ ] Verify OpenAI/Anthropic with `apiKey` still show "Configured"
- [ ] Verify a provider with no credentials and no `authType` still
shows "No credentials"

Made with [Cursor](https://cursor.com)
This commit is contained in:
Félix Malfait
2026-03-26 16:43:09 +01:00
committed by GitHub
parent 1c297e5ace
commit 7a341c6475
9 changed files with 26 additions and 5 deletions
@@ -33,7 +33,9 @@ const getProviderDescription = (provider: AiProviderItem): string => {
parts.push(provider.baseUrl);
}
if (provider.apiKey) {
if (provider.authType === 'role') {
parts.push(t`IAM role`);
} else if (provider.apiKey) {
parts.push(t`API key configured`);
} else if (provider.hasAccessKey) {
parts.push(t`IAM credentials`);
@@ -42,6 +44,9 @@ const getProviderDescription = (provider: AiProviderItem): string => {
return parts.join(' · ');
};
const isProviderConfigured = (provider: AiProviderItem): boolean =>
!!(provider.authType || provider.apiKey || provider.hasAccessKey);
export const SettingsAdminAiProviderListCard = ({
providers,
showAddButton = true,
@@ -70,7 +75,7 @@ export const SettingsAdminAiProviderListCard = ({
getItemLabel={(provider) => provider.label ?? provider.id}
getItemDescription={getProviderDescription}
RowRightComponent={({ item: provider }) =>
provider.apiKey || provider.hasAccessKey ? (
isProviderConfigured(provider) ? (
<Status color="green" text={t`Configured`} weight="medium" />
) : (
<Status color="orange" text={t`No credentials`} weight="medium" />
@@ -0,0 +1 @@
export type AiProviderAuthType = 'key' | 'credentials' | 'role';
@@ -1,5 +1,6 @@
import { type AiSdkPackage, type DataResidency } from 'twenty-shared/ai';
import { type AiProviderAuthType } from '@/settings/admin-panel/ai/types/AiProviderAuthType';
import { type AiProviderSource } from '@/settings/admin-panel/ai/types/AiProviderSource';
// AiProviderItem = RawAiProviderConfig (from the backend's Record<string,
@@ -11,6 +12,7 @@ export type AiProviderItem = {
// Optional provider display/catalog name from config (not a model name; models use `models[].name` on the backend).
name?: string;
label?: string;
authType?: AiProviderAuthType;
source?: AiProviderSource;
baseUrl?: string;
region?: string;