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.
This commit is contained in:
courtmanr@gmail.com 2025-11-24 17:40:43 +00:00
parent 9caba86389
commit 6b84b9a2bf
3 changed files with 173 additions and 149 deletions

View file

@ -20,6 +20,7 @@ interface SecurityStatus {
deprecatedDisableAuth?: boolean; deprecatedDisableAuth?: boolean;
message?: string; message?: string;
apiTokenConfigured?: boolean; apiTokenConfigured?: boolean;
hideLocalLogin?: boolean;
} }
export const Login: Component<LoginProps> = (props) => { export const Login: Component<LoginProps> = (props) => {
@ -275,6 +276,12 @@ export const Login: Component<LoginProps> = (props) => {
const showFirstRunSetup = () => const showFirstRunSetup = () =>
authStatus()?.hasAuthentication === false || legacyDisableAuth(); 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 ( return (
<Show <Show
when={!loadingAuth()} when={!loadingAuth()}
@ -306,6 +313,7 @@ export const Login: Component<LoginProps> = (props) => {
oidcLoading, oidcLoading,
oidcError, oidcError,
oidcMessage, oidcMessage,
showLocalLogin: shouldShowLocalLogin(),
}} }}
/> />
} }
@ -346,6 +354,7 @@ const LoginForm: Component<{
oidcLoading: () => boolean; oidcLoading: () => boolean;
oidcError: () => string; oidcError: () => string;
oidcMessage: () => string; oidcMessage: () => string;
showLocalLogin: boolean;
}> = (props) => { }> = (props) => {
const { const {
username, username,
@ -362,6 +371,7 @@ const LoginForm: Component<{
oidcLoading, oidcLoading,
oidcError, oidcError,
oidcMessage, oidcMessage,
showLocalLogin,
} = props; } = props;
return ( return (
@ -443,6 +453,7 @@ const LoginForm: Component<{
</p> </p>
</div> </div>
</Show> </Show>
<Show when={showLocalLogin}>
<div class="space-y-4"> <div class="space-y-4">
<div class="relative"> <div class="relative">
<label for="username" class="sr-only"> <label for="username" class="sr-only">
@ -606,6 +617,7 @@ const LoginForm: Component<{
</Show> </Show>
</button> </button>
</div> </div>
</Show>
</form> </form>
</div> </div>
</div> </div>

View file

@ -486,6 +486,7 @@ func (r *Router) setupRoutes() {
"authUsername": "", "authUsername": "",
"authLastModified": "", "authLastModified": "",
"oidcUsername": oidcUsername, "oidcUsername": oidcUsername,
"hideLocalLogin": r.config.HideLocalLogin,
} }
if isAuthenticated { if isAuthenticated {

View file

@ -129,6 +129,7 @@ type Config struct {
DemoMode bool `envconfig:"DEMO_MODE" default:"false"` // Read-only demo mode DemoMode bool `envconfig:"DEMO_MODE" default:"false"` // Read-only demo mode
AllowedOrigins string `envconfig:"ALLOWED_ORIGINS" default:"*"` AllowedOrigins string `envconfig:"ALLOWED_ORIGINS" default:"*"`
IframeEmbeddingAllow string `envconfig:"IFRAME_EMBEDDING_ALLOW" default:"SAMEORIGIN"` IframeEmbeddingAllow string `envconfig:"IFRAME_EMBEDDING_ALLOW" default:"SAMEORIGIN"`
HideLocalLogin bool `envconfig:"PULSE_AUTH_HIDE_LOCAL_LOGIN" default:"false"`
// Proxy authentication settings // Proxy authentication settings
ProxyAuthSecret string `envconfig:"PROXY_AUTH_SECRET"` 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 != "" { if enabledStr := utils.GetenvTrim("ENABLE_BACKUP_POLLING"); enabledStr != "" {
switch strings.ToLower(enabledStr) { switch strings.ToLower(enabledStr) {
case "0", "false", "no", "off": case "0", "false", "no", "off":