import { useEffect, useState } from 'react'; import { Button, Input, Toggle, FormField, SectionHeader, Tag, ConfirmDialog, Alert, } from '@cameleer/design-system'; import { useToast } from '@cameleer/design-system'; import { adminFetch } from '../../api/queries/admin/admin-api'; import styles from './OidcConfigPage.module.css'; interface OidcFormData { enabled: boolean; autoSignup: boolean; issuerUri: string; clientId: string; clientSecret: string; rolesClaim: string; displayNameClaim: string; userIdClaim: string; defaultRoles: string[]; audience: string; additionalScopes: string[]; } const EMPTY_CONFIG: OidcFormData = { enabled: false, autoSignup: true, issuerUri: '', clientId: '', clientSecret: '', rolesClaim: 'roles', displayNameClaim: 'name', userIdClaim: 'sub', defaultRoles: ['VIEWER'], audience: '', additionalScopes: [], }; export default function OidcConfigPage() { const [form, setForm] = useState(null); const [newRole, setNewRole] = useState(''); const [deleteOpen, setDeleteOpen] = useState(false); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [error, setError] = useState(null); const { toast } = useToast(); useEffect(() => { adminFetch & { configured?: boolean }>('/oidc') .then((data) => setForm({ enabled: data.enabled ?? false, autoSignup: data.autoSignup ?? true, issuerUri: data.issuerUri ?? '', clientId: data.clientId ?? '', clientSecret: data.clientSecret ?? '', rolesClaim: data.rolesClaim ?? 'roles', displayNameClaim: data.displayNameClaim ?? 'name', userIdClaim: data.userIdClaim ?? 'sub', defaultRoles: data.defaultRoles ?? ['VIEWER'], audience: (data as any).audience ?? '', additionalScopes: (data as any).additionalScopes ?? [], })) .catch(() => setForm(EMPTY_CONFIG)); }, []); function update(key: K, value: OidcFormData[K]) { setForm((prev) => prev ? { ...prev, [key]: value } : prev); } function addRole() { if (!form) return; const role = newRole.trim().toUpperCase(); if (role && !(form.defaultRoles || []).includes(role)) { update('defaultRoles', [...(form.defaultRoles || []), role]); setNewRole(''); } } function removeRole(role: string) { if (!form) return; update('defaultRoles', (form.defaultRoles || []).filter((r) => r !== role)); } async function handleSave() { if (!form) return; setSaving(true); setError(null); try { await adminFetch('/oidc', { method: 'PUT', body: JSON.stringify(form) }); toast({ title: 'Settings saved', description: 'OIDC configuration updated successfully.', variant: 'success' }); } catch (e: any) { setError(e.message); toast({ title: 'Save failed', description: e.message, variant: 'error', duration: 86_400_000 }); } finally { setSaving(false); } } async function handleTest() { if (!form) return; setTesting(true); setError(null); try { const result = await adminFetch<{ status: string; authorizationEndpoint?: string }>('/oidc/test', { method: 'POST' }); toast({ title: 'Connection test', description: `OIDC provider responded: ${result.status}`, variant: 'success' }); } catch (e: any) { setError(e.message); toast({ title: 'Connection test failed', description: e.message, variant: 'error', duration: 86_400_000 }); } finally { setTesting(false); } } async function handleDelete() { setDeleteOpen(false); setError(null); try { await adminFetch('/oidc', { method: 'DELETE' }); setForm(EMPTY_CONFIG); toast({ title: 'Configuration deleted', description: 'OIDC configuration has been removed.', variant: 'warning' }); } catch (e: any) { setError(e.message); toast({ title: 'Delete failed', description: e.message, variant: 'error', duration: 86_400_000 }); } } if (!form) return null; return (
{error &&
{error}
}
Behavior
update('enabled', e.target.checked)} />
update('autoSignup', e.target.checked)} /> Automatically create accounts for new OIDC users
Provider Settings update('issuerUri', e.target.value)} /> update('clientId', e.target.value)} /> update('clientSecret', e.target.value)} /> update('audience', e.target.value)} /> update('additionalScopes', e.target.value.split(',').map(s => s.trim()).filter(Boolean))} />
Claim Mapping update('rolesClaim', e.target.value)} /> update('userIdClaim', e.target.value)} /> update('displayNameClaim', e.target.value)} />
Default Roles
{(form.defaultRoles || []).map((role) => ( removeRole(role)} /> ))} {(form.defaultRoles || []).length === 0 && ( No default roles configured )}
setNewRole(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addRole(); } }} className={styles.roleInput} />
Danger Zone setDeleteOpen(false)} onConfirm={handleDelete} message="Delete OIDC configuration? All users signed in via OIDC will lose access." confirmText="delete oidc" />
); }