mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
feat: add OIDC auto-provision setting independent of registration toggle (#795)
Allows OIDC-authenticated users to be auto-provisioned even when "Allow new account registration" is disabled. Adds an admin UI toggle and corresponding API endpoints (GET/PATCH /users/oidc-auto-provision). Falls back to OIDC_ALLOW_REGISTRATION env var if the setting is unset.
This commit is contained in:
@@ -1166,11 +1166,28 @@ router.get("/oidc/callback", async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oidcAllowRegistration =
|
let oidcAutoProvision = false;
|
||||||
(process.env.OIDC_ALLOW_REGISTRATION || "").trim().toLowerCase() ===
|
try {
|
||||||
"true";
|
const oidcProvRow = db.$client
|
||||||
|
.prepare(
|
||||||
|
"SELECT value FROM settings WHERE key = 'oidc_auto_provision'",
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
if (oidcProvRow) {
|
||||||
|
oidcAutoProvision =
|
||||||
|
(oidcProvRow as Record<string, unknown>).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 {
|
try {
|
||||||
const regRow = db.$client
|
const regRow = db.$client
|
||||||
.prepare(
|
.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<string, unknown>).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
|
* @openapi
|
||||||
* /users/password-login-allowed:
|
* /users/password-login-allowed:
|
||||||
|
|||||||
@@ -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<Record<string, unknown>> {
|
||||||
|
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(
|
export async function updatePasswordLoginAllowed(
|
||||||
allowed: boolean,
|
allowed: boolean,
|
||||||
): Promise<{ allowed: boolean }> {
|
): Promise<{ allowed: boolean }> {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import {
|
|||||||
getAdminOIDCConfig,
|
getAdminOIDCConfig,
|
||||||
updateOIDCConfig,
|
updateOIDCConfig,
|
||||||
disableOIDCConfig,
|
disableOIDCConfig,
|
||||||
|
getOidcAutoProvision,
|
||||||
|
updateOidcAutoProvision,
|
||||||
isElectron,
|
isElectron,
|
||||||
getUserRoles,
|
getUserRoles,
|
||||||
assignRoleToUser,
|
assignRoleToUser,
|
||||||
@@ -134,6 +136,7 @@ export function AdminSettingsPanel() {
|
|||||||
const [logLevel, setLogLevel] = useState("info");
|
const [logLevel, setLogLevel] = useState("info");
|
||||||
|
|
||||||
// OIDC state
|
// OIDC state
|
||||||
|
const [oidcAutoProvision, setOidcAutoProvision] = useState(false);
|
||||||
const [oidcClientId, setOidcClientId] = useState("");
|
const [oidcClientId, setOidcClientId] = useState("");
|
||||||
const [oidcClientSecret, setOidcClientSecret] = useState("");
|
const [oidcClientSecret, setOidcClientSecret] = useState("");
|
||||||
const [oidcAuthUrl, setOidcAuthUrl] = useState("");
|
const [oidcAuthUrl, setOidcAuthUrl] = useState("");
|
||||||
@@ -238,7 +241,7 @@ export function AdminSettingsPanel() {
|
|||||||
|
|
||||||
async function loadGeneralSettings() {
|
async function loadGeneralSettings() {
|
||||||
try {
|
try {
|
||||||
const [reg, pwLogin, pwReset, timeout, monitoring, level, guac] =
|
const [reg, pwLogin, pwReset, timeout, monitoring, level, guac, oidcProv] =
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
getRegistrationAllowed(),
|
getRegistrationAllowed(),
|
||||||
getPasswordLoginAllowed(),
|
getPasswordLoginAllowed(),
|
||||||
@@ -247,11 +250,14 @@ export function AdminSettingsPanel() {
|
|||||||
getGlobalMonitoringSettings(),
|
getGlobalMonitoringSettings(),
|
||||||
getLogLevel(),
|
getLogLevel(),
|
||||||
getGuacamoleSettings(),
|
getGuacamoleSettings(),
|
||||||
|
getOidcAutoProvision(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (reg.status === "fulfilled") setAllowRegistration(reg.value.allowed);
|
if (reg.status === "fulfilled") setAllowRegistration(reg.value.allowed);
|
||||||
if (pwLogin.status === "fulfilled")
|
if (pwLogin.status === "fulfilled")
|
||||||
setAllowPasswordLogin(pwLogin.value.allowed);
|
setAllowPasswordLogin(pwLogin.value.allowed);
|
||||||
|
if (oidcProv.status === "fulfilled")
|
||||||
|
setOidcAutoProvision(oidcProv.value.enabled);
|
||||||
if (pwReset.status === "fulfilled") setAllowPasswordReset(pwReset.value);
|
if (pwReset.status === "fulfilled") setAllowPasswordReset(pwReset.value);
|
||||||
if (timeout.status === "fulfilled")
|
if (timeout.status === "fulfilled")
|
||||||
setSessionTimeout(String(timeout.value.timeoutHours));
|
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() {
|
async function handleTogglePasswordReset() {
|
||||||
const newVal = !allowPasswordReset;
|
const newVal = !allowPasswordReset;
|
||||||
setAllowPasswordReset(newVal);
|
setAllowPasswordReset(newVal);
|
||||||
@@ -700,6 +717,15 @@ export function AdminSettingsPanel() {
|
|||||||
onToggle={handleTogglePasswordLogin}
|
onToggle={handleTogglePasswordLogin}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
label="OIDC Auto-Provision"
|
||||||
|
description="Auto-create accounts for OIDC users even when registration is disabled"
|
||||||
|
>
|
||||||
|
<AdminToggle
|
||||||
|
on={oidcAutoProvision}
|
||||||
|
onToggle={handleToggleOidcAutoProvision}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
label="Allow Password Reset"
|
label="Allow Password Reset"
|
||||||
description="Reset code via Docker logs"
|
description="Reset code via Docker logs"
|
||||||
|
|||||||
Reference in New Issue
Block a user