fix: gate second factors after SSO/LDAP behind an admin setting

This commit is contained in:
LukeGus committed 2026-09-23 07:17:03 -05:00
1 parent f217d8db89
commit 95abe24798
13 files changed
+307 -5

No files matched your search

+27
View File
@@ -256,6 +256,33 @@ export async function updateOidcAutoProvision(
}
}
export async function getSecondFactorAfterExternalLogin(): Promise<{
enabled: boolean;
}> {
try {
const response = await authApi.get(
"/users/second-factor-after-external-login",
);
return response.data;
} catch (error) {
handleApiError(error, "check second factor after external login status");
}
}
export async function updateSecondFactorAfterExternalLogin(
enabled: boolean,
): Promise<Record<string, unknown>> {
try {
const response = await authApi.patch(
"/users/second-factor-after-external-login",
{ enabled },
);
return response.data;
} catch (error) {
handleApiError(error, "update second factor after external login");
}
}
export async function getOidcSilentLoginDefault(): Promise<{
enabled: boolean;
locked?: boolean;
+3
View File
@@ -2884,6 +2884,8 @@
"allowPasswordLoginDesc": "Username/password login",
"oidcAutoProvision": "OIDC Auto-Provision",
"oidcAutoProvisionDesc": "Auto-create accounts for OIDC/SSO users on first login (independent of the registration toggle)",
"secondFactorAfterExternalLogin": "Ask for a second factor after external logins (SSO, GitHub, Google, LDAP)",
"secondFactorAfterExternalLoginDesc": "Off, an enrolled second factor is only asked for after password and other local logins, same as before. On, it is asked for after every login method, including SSO and LDAP.",
"oidcSilentLoginDefault": "Silent OIDC Login by Default",
"oidcSilentLoginDefaultDesc": "Automatically redirect to OIDC login on every visit, skipping the login form entirely",
"oidcSilentLoginDefaultLockedDesc": "This setting is locked by the OIDC_SILENT_LOGIN_DEFAULT environment variable and cannot be changed here.",
@@ -3054,6 +3056,7 @@
"updatePasswordLoginFailed": "Failed to update password login setting",
"cannotDisablePasswordLoginWithTotp": "Cannot disable password login while 2FA is enabled for one or more users. Disable 2FA first.",
"updateOidcAutoProvisionFailed": "Failed to update OIDC auto-provision setting",
"updateSecondFactorAfterExternalLoginFailed": "Failed to update second factor setting",
"updateOidcSilentLoginDefaultFailed": "Failed to update silent OIDC login setting",
"updatePasswordResetFailed": "Failed to update password reset setting",
"sessionTimeoutRange2": "Session timeout must be between 1 and 720 hours",
+2
View File
@@ -2054,6 +2054,8 @@ export {
updateRegistrationAllowed,
getOidcAutoProvision,
updateOidcAutoProvision,
getSecondFactorAfterExternalLogin,
updateSecondFactorAfterExternalLogin,
getOidcSilentLoginDefault,
updateOidcSilentLoginDefault,
updatePasswordLoginAllowed,
+23
View File
@@ -37,6 +37,8 @@ import {
updateGuacamoleSettings,
getOidcAutoProvision,
updateOidcAutoProvision,
getSecondFactorAfterExternalLogin,
updateSecondFactorAfterExternalLogin,
getOidcSilentLoginDefault,
updateOidcSilentLoginDefault,
getCommandHistoryEnabled,
@@ -223,6 +225,8 @@ export function AdminSettingsPanel({
// SSO / auto-provision state
const [oidcAutoProvision, setOidcAutoProvision] = useState(false);
const [secondFactorAfterExternalLogin, setSecondFactorAfterExternalLogin] =
useState(false);
const [oidcSilentLoginDefault, setOidcSilentLoginDefault] = useState(false);
const [ssoProviders, setSsoProviders] = useState<SSOProvider[]>([]);
const [ssoDialogOpen, setSsoDialogOpen] = useState(false);
@@ -394,6 +398,7 @@ export function AdminSettingsPanel({
level,
guac,
oidcProv,
secondFactorExternal,
oidcSilent,
cmdHistory,
analytics,
@@ -413,6 +418,7 @@ export function AdminSettingsPanel({
getLogLevel(),
getGuacamoleSettings(),
getOidcAutoProvision(),
getSecondFactorAfterExternalLogin(),
getOidcSilentLoginDefault(),
getCommandHistoryEnabled(),
getAnalyticsEnabled(),
@@ -435,6 +441,8 @@ export function AdminSettingsPanel({
}
if (oidcProv.status === "fulfilled")
setOidcAutoProvision(oidcProv.value.enabled);
if (secondFactorExternal.status === "fulfilled")
setSecondFactorAfterExternalLogin(secondFactorExternal.value.enabled);
if (oidcSilent.status === "fulfilled") {
setOidcSilentLoginDefault(oidcSilent.value.enabled);
setOidcSilentLoginDefaultLocked(oidcSilent.value.locked ?? false);
@@ -555,6 +563,17 @@ export function AdminSettingsPanel({
}
}
async function handleToggleSecondFactorAfterExternalLogin() {
const newVal = !secondFactorAfterExternalLogin;
setSecondFactorAfterExternalLogin(newVal);
try {
await updateSecondFactorAfterExternalLogin(newVal);
} catch {
setSecondFactorAfterExternalLogin(!newVal);
toast.error(t("admin.updateSecondFactorAfterExternalLoginFailed"));
}
}
async function handleToggleOidcSilentLoginDefault() {
if (oidcSilentLoginDefaultLocked) return;
const newVal = !oidcSilentLoginDefault;
@@ -1233,6 +1252,10 @@ export function AdminSettingsPanel({
handleTogglePasswordLogin={handleTogglePasswordLogin}
oidcAutoProvision={oidcAutoProvision}
handleToggleOidcAutoProvision={handleToggleOidcAutoProvision}
secondFactorAfterExternalLogin={secondFactorAfterExternalLogin}
handleToggleSecondFactorAfterExternalLogin={
handleToggleSecondFactorAfterExternalLogin
}
oidcSilentLoginDefault={oidcSilentLoginDefault}
oidcSilentLoginDefaultLocked={oidcSilentLoginDefaultLocked}
handleToggleOidcSilentLoginDefault={handleToggleOidcSilentLoginDefault}
+13
View File
@@ -47,6 +47,8 @@ type GeneralSettingsSectionProps = {
handleTogglePasswordLogin: () => void;
oidcAutoProvision: boolean;
handleToggleOidcAutoProvision: () => void;
secondFactorAfterExternalLogin: boolean;
handleToggleSecondFactorAfterExternalLogin: () => void;
oidcSilentLoginDefault: boolean;
oidcSilentLoginDefaultLocked: boolean;
handleToggleOidcSilentLoginDefault: () => void;
@@ -98,6 +100,8 @@ export function AdminGeneralSettingsSection({
handleTogglePasswordLogin,
oidcAutoProvision,
handleToggleOidcAutoProvision,
secondFactorAfterExternalLogin,
handleToggleSecondFactorAfterExternalLogin,
oidcSilentLoginDefault,
oidcSilentLoginDefaultLocked,
handleToggleOidcSilentLoginDefault,
@@ -301,6 +305,15 @@ export function AdminGeneralSettingsSection({
onToggle={handleToggleOidcAutoProvision}
/>
</SettingRow>
<SettingRow
label={t("admin.secondFactorAfterExternalLogin")}
description={t("admin.secondFactorAfterExternalLoginDesc")}
>
<AdminToggle
on={secondFactorAfterExternalLogin}
onToggle={handleToggleSecondFactorAfterExternalLogin}
/>
</SettingRow>
<SettingRow
label={t("admin.oidcSilentLoginDefault")}
description={