import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { getOIDCConfig, updateOIDCConfig } from '../api'; import { OIDCConfig } from '../types'; import './AdminOIDCPage.css'; function AdminOIDCPage() { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); // Form state const [enabled, setEnabled] = useState(false); const [issuerUrl, setIssuerUrl] = useState(''); const [clientId, setClientId] = useState(''); const [clientSecret, setClientSecret] = useState(''); const [scopes, setScopes] = useState('openid profile email'); const [autoCreateUsers, setAutoCreateUsers] = useState(true); const [adminGroup, setAdminGroup] = useState(''); const [isSaving, setIsSaving] = useState(false); useEffect(() => { if (!authLoading && !user) { navigate('/login', { state: { from: '/admin/oidc' } }); } }, [user, authLoading, navigate]); useEffect(() => { if (user && user.is_admin) { loadConfig(); } }, [user]); useEffect(() => { if (successMessage) { const timer = setTimeout(() => setSuccessMessage(null), 3000); return () => clearTimeout(timer); } }, [successMessage]); async function loadConfig() { setLoading(true); setError(null); try { const data = await getOIDCConfig(); setConfig(data); setEnabled(data.enabled); setIssuerUrl(data.issuer_url); setClientId(data.client_id); setScopes(data.scopes.join(' ')); setAutoCreateUsers(data.auto_create_users); setAdminGroup(data.admin_group); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load OIDC configuration'); } finally { setLoading(false); } } async function handleSave(e: React.FormEvent) { e.preventDefault(); if (enabled && !issuerUrl.trim()) { setError('Issuer URL is required when OIDC is enabled'); return; } if (enabled && !clientId.trim()) { setError('Client ID is required when OIDC is enabled'); return; } setIsSaving(true); setError(null); try { const scopesList = scopes.split(/\s+/).filter(s => s.length > 0); const updateData: Record = { enabled, issuer_url: issuerUrl.trim(), client_id: clientId.trim(), scopes: scopesList, auto_create_users: autoCreateUsers, admin_group: adminGroup.trim(), }; if (clientSecret) { updateData.client_secret = clientSecret; } await updateOIDCConfig(updateData); setSuccessMessage('OIDC configuration saved successfully'); setClientSecret(''); await loadConfig(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save OIDC configuration'); } finally { setIsSaving(false); } } if (authLoading) { return (
Loading...
); } if (!user) { return null; } if (!user.is_admin) { return (

Access Denied

You do not have permission to access this page. Admin privileges are required.

); } return (

Single Sign-On (OIDC)

Configure OpenID Connect for SSO authentication

{successMessage && (
{successMessage}
)} {error && (
{error}
)} {loading ? (
Loading configuration...
) : (

Status

When enabled, users can sign in using your organization's identity provider.

Provider Configuration

setIssuerUrl(e.target.value)} placeholder="https://your-provider.com" disabled={isSaving} />

The base URL of your OIDC provider. Discovery document will be fetched from /.well-known/openid-configuration.

setClientId(e.target.value)} placeholder="your-client-id" disabled={isSaving} />
setClientSecret(e.target.value)} placeholder={config?.has_client_secret ? 'Leave blank to keep current' : 'Enter client secret'} disabled={isSaving} />
setScopes(e.target.value)} placeholder="openid profile email" disabled={isSaving} />

Space-separated list of OIDC scopes to request. Common scopes: openid, profile, email, groups.

User Provisioning

When enabled, new users will be created automatically when they sign in via OIDC for the first time.

setAdminGroup(e.target.value)} placeholder="admin, orchard-admins" disabled={isSaving} />

Users in this group (from the groups claim) will be granted admin privileges. Leave blank to disable automatic admin assignment.

)}

Callback URL

Configure your identity provider with the following callback URL:

{window.location.origin}/api/v1/auth/oidc/callback
); } export default AdminOIDCPage;