From 8cac6951d728c9601a8fa1e9caa546f7c944e513 Mon Sep 17 00:00:00 2001 From: hzhang Date: Sun, 17 May 2026 20:29:22 +0100 Subject: [PATCH] feat(auth): admin OIDC settings page New admin page /settings/oidc to configure the OIDC provider (issuer, client id/secret, redirect/callback URL, scopes, post-login redirect). Prominently shows the callback URL to register at the IdP, current status/source, and the read-only deploy-level OIDC-only flag. Secret is write-only (blank = keep). Sidebar entry for admins. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/App.tsx | 2 + src/components/Sidebar.tsx | 1 + src/pages/OidcSettingsPage.tsx | 159 +++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 src/pages/OidcSettingsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 6c3ff0c..2cc02fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import CalendarPage from '@/pages/CalendarPage' import SupportDetailPage from '@/pages/SupportDetailPage' import MeetingDetailPage from '@/pages/MeetingDetailPage' import OidcCallbackPage from '@/pages/OidcCallbackPage' +import OidcSettingsPage from '@/pages/OidcSettingsPage' import axios from 'axios' const getStoredWizardPort = (): number | null => { @@ -135,6 +136,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 1246c3f..70fb676 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -41,6 +41,7 @@ export default function Sidebar({ user, onLogout }: Props) { ...(user.is_admin ? [ { to: '/users', icon: '๐Ÿ‘ฅ', label: 'Users' }, { to: '/roles', icon: '๐Ÿ”', label: 'Roles' }, + { to: '/settings/oidc', icon: '๐Ÿชช', label: 'OIDC' }, ] : []), ] : [ { to: '/monitor', icon: '๐Ÿ“ก', label: 'Monitor' }, diff --git a/src/pages/OidcSettingsPage.tsx b/src/pages/OidcSettingsPage.tsx new file mode 100644 index 0000000..feac13f --- /dev/null +++ b/src/pages/OidcSettingsPage.tsx @@ -0,0 +1,159 @@ +import { useEffect, useState } from 'react' +import api from '@/services/api' +import { useAuth } from '@/hooks/useAuth' + +interface Settings { + enabled: boolean + issuer: string | null + client_id: string | null + has_client_secret: boolean + redirect_uri: string | null + scopes: string | null + post_login_redirect: string | null + oidc_only: boolean + effective_enabled: boolean + source: string +} + +export default function OidcSettingsPage() { + const { user } = useAuth() + const isAdmin = user?.is_admin === true + + const [loaded, setLoaded] = useState(null) + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [message, setMessage] = useState('') + const [form, setForm] = useState({ + enabled: false, + issuer: '', + client_id: '', + client_secret: '', + redirect_uri: '', + scopes: 'openid email profile', + post_login_redirect: '', + }) + + useEffect(() => { + if (!isAdmin) { setLoading(false); return } + api.get('/auth/oidc/settings') + .then(({ data }) => { + setLoaded(data) + setForm({ + enabled: data.enabled, + issuer: data.issuer || '', + client_id: data.client_id || '', + client_secret: '', + redirect_uri: data.redirect_uri || '', + scopes: data.scopes || 'openid email profile', + post_login_redirect: data.post_login_redirect || '', + }) + }) + .catch((e) => setMessage(e.response?.data?.detail || 'Failed to load OIDC settings')) + .finally(() => setLoading(false)) + }, [isAdmin]) + + const save = async () => { + setSaving(true) + setMessage('') + try { + const payload: Record = { + enabled: form.enabled, + issuer: form.issuer.trim(), + client_id: form.client_id.trim(), + redirect_uri: form.redirect_uri.trim(), + scopes: form.scopes.trim(), + post_login_redirect: form.post_login_redirect.trim(), + } + if (form.client_secret) payload.client_secret = form.client_secret + const { data } = await api.put('/auth/oidc/settings', payload) + setLoaded(data) + setForm((f) => ({ ...f, client_secret: '' })) + setMessage('OIDC settings saved successfully') + } catch (e: any) { + setMessage(e.response?.data?.detail || 'Failed to save OIDC settings') + } finally { + setSaving(false) + } + } + + if (loading) return
Loading OIDC settings...
+ if (!isAdmin) { + return ( +
+

๐Ÿ” OIDC Settings

+

Admin access required.

+
+ ) + } + + const callbackHint = form.redirect_uri.trim() || loaded?.redirect_uri || '(set the Redirect / Callback URL below)' + + return ( +
+
+
+

๐Ÿ” OIDC Settings

+
Configure the OpenID Connect provider. Saved values override environment defaults.
+
+
+ + {message && ( +
{message}
+ )} + +
+
+
Status
+ + {loaded?.effective_enabled ? 'OIDC active' : 'OIDC inactive'} + +
+
+ config source: {loaded?.source} ยท OIDC-only mode (deploy env): {loaded?.oidc_only ? 'on' : 'off'} +
+
+
Register this Redirect / Callback URL at your identity provider:
+ {callbackHint} +
+
+ +
+ + + + + + + + +
+
+ ) +}