diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index d3fb4642..5c14cf72 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -1166,11 +1166,28 @@ router.get("/oidc/callback", async (req, res) => { } } - const oidcAllowRegistration = - (process.env.OIDC_ALLOW_REGISTRATION || "").trim().toLowerCase() === - "true"; + let oidcAutoProvision = false; + try { + const oidcProvRow = db.$client + .prepare( + "SELECT value FROM settings WHERE key = 'oidc_auto_provision'", + ) + .get(); + if (oidcProvRow) { + oidcAutoProvision = + (oidcProvRow as Record).value === "true"; + } + } catch { + // fall through to env var check + } - if (!isFirstUser && !oidcAllowRegistration) { + if (!oidcAutoProvision) { + oidcAutoProvision = + (process.env.OIDC_ALLOW_REGISTRATION || "").trim().toLowerCase() === + "true"; + } + + if (!isFirstUser && !oidcAutoProvision) { try { const regRow = db.$client .prepare( @@ -1924,6 +1941,52 @@ router.patch("/registration-allowed", authenticateJWT, async (req, res) => { } }); +router.get("/oidc-auto-provision", async (_req, res) => { + try { + const row = db.$client + .prepare("SELECT value FROM settings WHERE key = 'oidc_auto_provision'") + .get(); + res.json({ + enabled: row + ? (row as Record).value === "true" + : false, + }); + } catch (err) { + authLogger.error("Failed to get OIDC auto-provision setting", err); + res.status(500).json({ error: "Failed to get OIDC auto-provision setting" }); + } +}); + +router.patch("/oidc-auto-provision", authenticateJWT, async (req, res) => { + const userId = (req as AuthenticatedRequest).userId; + try { + const user = await db.select().from(users).where(eq(users.id, userId)); + if (!user || user.length === 0 || !user[0].isAdmin) { + return res.status(403).json({ error: "Not authorized" }); + } + const { enabled } = req.body; + if (typeof enabled !== "boolean") { + return res.status(400).json({ error: "Invalid value for enabled" }); + } + const existing = db.$client + .prepare("SELECT value FROM settings WHERE key = 'oidc_auto_provision'") + .get(); + if (existing) { + db.$client + .prepare("UPDATE settings SET value = ? WHERE key = 'oidc_auto_provision'") + .run(enabled ? "true" : "false"); + } else { + db.$client + .prepare("INSERT INTO settings (key, value) VALUES ('oidc_auto_provision', ?)") + .run(enabled ? "true" : "false"); + } + res.json({ enabled }); + } catch (err) { + authLogger.error("Failed to set OIDC auto-provision", err); + res.status(500).json({ error: "Failed to set OIDC auto-provision" }); + } +}); + /** * @openapi * /users/password-login-allowed: diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 8529974c..c0b47ba3 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -3326,6 +3326,28 @@ export async function updateRegistrationAllowed( } } +export async function getOidcAutoProvision(): Promise<{ enabled: boolean }> { + try { + const response = await authApi.get("/users/oidc-auto-provision"); + return response.data; + } catch (error) { + handleApiError(error, "check OIDC auto-provision status"); + } +} + +export async function updateOidcAutoProvision( + enabled: boolean, +): Promise> { + try { + const response = await authApi.patch("/users/oidc-auto-provision", { + enabled, + }); + return response.data; + } catch (error) { + handleApiError(error, "update OIDC auto-provision"); + } +} + export async function updatePasswordLoginAllowed( allowed: boolean, ): Promise<{ allowed: boolean }> { diff --git a/src/ui/sidebar/AdminSettingsPanel.tsx b/src/ui/sidebar/AdminSettingsPanel.tsx index f17bf316..e69a40e9 100644 --- a/src/ui/sidebar/AdminSettingsPanel.tsx +++ b/src/ui/sidebar/AdminSettingsPanel.tsx @@ -31,6 +31,8 @@ import { getAdminOIDCConfig, updateOIDCConfig, disableOIDCConfig, + getOidcAutoProvision, + updateOidcAutoProvision, isElectron, getUserRoles, assignRoleToUser, @@ -134,6 +136,7 @@ export function AdminSettingsPanel() { const [logLevel, setLogLevel] = useState("info"); // OIDC state + const [oidcAutoProvision, setOidcAutoProvision] = useState(false); const [oidcClientId, setOidcClientId] = useState(""); const [oidcClientSecret, setOidcClientSecret] = useState(""); const [oidcAuthUrl, setOidcAuthUrl] = useState(""); @@ -238,7 +241,7 @@ export function AdminSettingsPanel() { async function loadGeneralSettings() { try { - const [reg, pwLogin, pwReset, timeout, monitoring, level, guac] = + const [reg, pwLogin, pwReset, timeout, monitoring, level, guac, oidcProv] = await Promise.allSettled([ getRegistrationAllowed(), getPasswordLoginAllowed(), @@ -247,11 +250,14 @@ export function AdminSettingsPanel() { getGlobalMonitoringSettings(), getLogLevel(), getGuacamoleSettings(), + getOidcAutoProvision(), ]); if (reg.status === "fulfilled") setAllowRegistration(reg.value.allowed); if (pwLogin.status === "fulfilled") setAllowPasswordLogin(pwLogin.value.allowed); + if (oidcProv.status === "fulfilled") + setOidcAutoProvision(oidcProv.value.enabled); if (pwReset.status === "fulfilled") setAllowPasswordReset(pwReset.value); if (timeout.status === "fulfilled") setSessionTimeout(String(timeout.value.timeoutHours)); @@ -321,6 +327,17 @@ export function AdminSettingsPanel() { } } + async function handleToggleOidcAutoProvision() { + const newVal = !oidcAutoProvision; + setOidcAutoProvision(newVal); + try { + await updateOidcAutoProvision(newVal); + } catch { + setOidcAutoProvision(!newVal); + toast.error("Failed to update OIDC auto-provision setting"); + } + } + async function handleTogglePasswordReset() { const newVal = !allowPasswordReset; setAllowPasswordReset(newVal); @@ -700,6 +717,15 @@ export function AdminSettingsPanel() { onToggle={handleTogglePasswordLogin} /> + + +