diff --git a/frontend/src/__tests__/components/OIDCProviderSettings.test.tsx b/frontend/src/__tests__/components/OIDCProviderSettings.test.tsx index 1f40953b9..1436b7e20 100644 --- a/frontend/src/__tests__/components/OIDCProviderSettings.test.tsx +++ b/frontend/src/__tests__/components/OIDCProviderSettings.test.tsx @@ -325,3 +325,54 @@ describe('OIDCProviderSettings', () => { }); }); }); + +describe('env-managed provider (#2593)', () => { + const envManagedProvider = { + ...mockProviders[0], + id: 2, + name: 'EnvIdP', + is_env_managed: true, + }; + + it('marks the provider as environment managed', async () => { + server.use( + http.get('/api/v1/auth/oidc/providers/all', () => HttpResponse.json([envManagedProvider])) + ); + render(); + + await waitFor(() => { + expect(screen.getByText('EnvIdP')).toBeInTheDocument(); + }); + expect(screen.getByText(/Environment Managed/i)).toBeInTheDocument(); + }); + + it('offers no edit or delete control for it', async () => { + server.use( + http.get('/api/v1/auth/oidc/providers/all', () => HttpResponse.json([envManagedProvider])) + ); + render(); + + await waitFor(() => { + expect(screen.getByText('EnvIdP')).toBeInTheDocument(); + }); + // Startup rewrites this row from the environment on every boot, and the API + // answers 409 — offering the controls would promise an edit that cannot land. + expect(screen.queryByTestId('edit-provider-2')).not.toBeInTheDocument(); + expect(screen.queryByTestId('delete-provider-2')).not.toBeInTheDocument(); + }); + + it('still offers them for a UI-created provider', async () => { + server.use( + http.get('/api/v1/auth/oidc/providers/all', () => + HttpResponse.json([{ ...mockProviders[0], is_env_managed: false }]) + ) + ); + render(); + + await waitFor(() => { + expect(screen.getByText('TestIdP')).toBeInTheDocument(); + }); + expect(screen.getByTestId('edit-provider-1')).toBeInTheDocument(); + expect(screen.getByTestId('delete-provider-1')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index f9cbbb212..a0da7257d 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -3518,6 +3518,11 @@ export interface OIDCProvider { // #1589: when true, the LoginPage redirects unauthenticated visitors // straight to this provider on mount. At most one provider may carry this. is_autologin: boolean; + // #2593: defined by BAMBUDDY_OIDC_* and rewritten from the environment on + // every boot. The API answers 409 to any write, so the settings UI must not + // offer edit/delete controls that cannot succeed. Optional so a response + // from an older backend still type-checks. + is_env_managed?: boolean; } export interface OIDCProviderCreate { diff --git a/frontend/src/components/OIDCProviderSettings.tsx b/frontend/src/components/OIDCProviderSettings.tsx index cacc0d952..6ec27aca9 100644 --- a/frontend/src/components/OIDCProviderSettings.tsx +++ b/frontend/src/components/OIDCProviderSettings.tsx @@ -1,6 +1,6 @@ import { useState, type ReactNode } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Plus, Edit2, Trash2, Globe, Check, X, RefreshCw, ExternalLink, ImageOff } from 'lucide-react'; +import { Plus, Edit2, Trash2, Globe, Check, X, RefreshCw, ExternalLink, ImageOff, Lock } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { api } from '../api/client'; import type { Group, OIDCProvider, OIDCProviderCreate } from '../api/client'; @@ -386,6 +386,11 @@ export function OIDCProviderSettings() { {t('common.disabled')} )} + {provider.is_env_managed && ( + + {t('settings.environmentManagedLabel')} + + )}
@@ -417,21 +422,34 @@ export function OIDCProviderSettings() { )} - toggleEnabled(provider)} - disabled={updateMutation.isPending} - /> - - + {/* #2593: startup rewrites the env-managed row from BAMBUDDY_OIDC_* + and the API answers 409, so offering these would promise a + change that cannot land. */} + {!provider.is_env_managed && ( + <> + toggleEnabled(provider)} + disabled={updateMutation.isPending} + /> + + + + )}