From 6b84b9a2bf666316e8335803e789c6a3f9e6a126 Mon Sep 17 00:00:00 2001
From: "courtmanr@gmail.com"
Date: Mon, 24 Nov 2025 17:40:43 +0000
Subject: [PATCH] Add PULSE_AUTH_HIDE_LOCAL_LOGIN option to hide password form
Implements #750 - allows hiding the username/password login form when
using OIDC SSO to avoid user confusion, while maintaining security.
- Added HideLocalLogin config option (env: PULSE_AUTH_HIDE_LOCAL_LOGIN)
- Exposed hideLocalLogin in /api/security/status endpoint
- Updated Login.tsx to conditionally hide local login form
- Added escape hatch via ?show_local=true URL parameter
This approach avoids the security and upgrade issues that led to
DISABLE_AUTH being removed (see #707, #678), while solving the UX
problem of users being confused by multiple login options.
---
frontend-modern/src/components/Login.tsx | 310 ++++++++++++-----------
internal/api/router.go | 1 +
internal/config/config.go | 11 +
3 files changed, 173 insertions(+), 149 deletions(-)
diff --git a/frontend-modern/src/components/Login.tsx b/frontend-modern/src/components/Login.tsx
index 98e9766..4eb775e 100644
--- a/frontend-modern/src/components/Login.tsx
+++ b/frontend-modern/src/components/Login.tsx
@@ -20,6 +20,7 @@ interface SecurityStatus {
deprecatedDisableAuth?: boolean;
message?: string;
apiTokenConfigured?: boolean;
+ hideLocalLogin?: boolean;
}
export const Login: Component = (props) => {
@@ -275,6 +276,12 @@ export const Login: Component = (props) => {
const showFirstRunSetup = () =>
authStatus()?.hasAuthentication === false || legacyDisableAuth();
+ const shouldShowLocalLogin = () => {
+ const params = new URLSearchParams(window.location.search);
+ if (params.get('show_local') === 'true') return true;
+ return !authStatus()?.hideLocalLogin;
+ };
+
return (
= (props) => {
oidcLoading,
oidcError,
oidcMessage,
+ showLocalLogin: shouldShowLocalLogin(),
}}
/>
}
@@ -346,6 +354,7 @@ const LoginForm: Component<{
oidcLoading: () => boolean;
oidcError: () => string;
oidcMessage: () => string;
+ showLocalLogin: boolean;
}> = (props) => {
const {
username,
@@ -362,6 +371,7 @@ const LoginForm: Component<{
oidcLoading,
oidcError,
oidcMessage,
+ showLocalLogin,
} = props;
return (
@@ -443,169 +453,171 @@ const LoginForm: Component<{
-
-
-
-
-
+
+
+
+
+
+
setUsername(e.currentTarget.value)}
+ />
-
setUsername(e.currentTarget.value)}
- />
-
-
-
-
-
+
+
+
+
setPassword(e.currentTarget.value)}
+ />
+
+
+ setRememberMe(e.currentTarget.checked)}
+ class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded cursor-pointer dark:border-gray-600 dark:bg-gray-700"
+ />
+
-
setPassword(e.currentTarget.value)}
- />
-
- setRememberMe(e.currentTarget.checked)}
- class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded cursor-pointer dark:border-gray-600 dark:bg-gray-700"
- />
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ }
+ >
+
- }
- >
-
-
-
-
-
- {error()}
-
-
-
- Lockouts automatically expire after the specified time. If you need immediate
- access, contact your administrator.
+
+
+
+
+ {error()}
-
+
+
+ Lockouts automatically expire after the specified time. If you need immediate
+ access, contact your administrator.
+
+
+
+
+
+
+
-
-
-
-
diff --git a/internal/api/router.go b/internal/api/router.go
index 2ddc1d0..81312e6 100644
--- a/internal/api/router.go
+++ b/internal/api/router.go
@@ -486,6 +486,7 @@ func (r *Router) setupRoutes() {
"authUsername": "",
"authLastModified": "",
"oidcUsername": oidcUsername,
+ "hideLocalLogin": r.config.HideLocalLogin,
}
if isAuthenticated {
diff --git a/internal/config/config.go b/internal/config/config.go
index f9d41ba..834875a 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -129,6 +129,7 @@ type Config struct {
DemoMode bool `envconfig:"DEMO_MODE" default:"false"` // Read-only demo mode
AllowedOrigins string `envconfig:"ALLOWED_ORIGINS" default:"*"`
IframeEmbeddingAllow string `envconfig:"IFRAME_EMBEDDING_ALLOW" default:"SAMEORIGIN"`
+ HideLocalLogin bool `envconfig:"PULSE_AUTH_HIDE_LOCAL_LOGIN" default:"false"`
// Proxy authentication settings
ProxyAuthSecret string `envconfig:"PROXY_AUTH_SECRET"`
@@ -774,6 +775,16 @@ func Load() (*Config, error) {
}
}
+ if hideLocalLoginStr := utils.GetenvTrim("PULSE_AUTH_HIDE_LOCAL_LOGIN"); hideLocalLoginStr != "" {
+ if hide, err := strconv.ParseBool(hideLocalLoginStr); err == nil {
+ cfg.HideLocalLogin = hide
+ cfg.EnvOverrides["PULSE_AUTH_HIDE_LOCAL_LOGIN"] = true
+ log.Info().Bool("hide", hide).Msg("Overriding hide local login setting from environment")
+ } else {
+ log.Warn().Str("value", hideLocalLoginStr).Msg("Invalid PULSE_AUTH_HIDE_LOCAL_LOGIN value, ignoring")
+ }
+ }
+
if enabledStr := utils.GetenvTrim("ENABLE_BACKUP_POLLING"); enabledStr != "" {
switch strings.ToLower(enabledStr) {
case "0", "false", "no", "off":