Summary
Add an opt-in mode where CloudCLI trusts an authenticated-username header injected by an upstream reverse proxy doing forward-auth (Authelia, oauth2-proxy, Authentik, Cloudflare Access, …), so users behind SSO don't have to log in a second time.
Problem
When CloudCLI runs behind an authenticating reverse proxy using forward-auth, the proxy already verifies the user and passes the identity downstream as a request header (e.g. Remote-User). Today CloudCLI ignores it and still shows its own bcrypt/JWT login, so the user authenticates twice (SSO challenge + CloudCLI password). There is currently no inbound SSO / reverse-proxy auth mode. (#744 is the opposite direction — host→plugin identity via HMAC — not this.)
Proposed behavior
Mirror the well-established pattern from Gitea (ENABLE_REVERSE_PROXY_AUTHENTICATION + REVERSE_PROXY_TRUSTED_PROXIES) and Grafana ([auth.proxy] + whitelist):
TRUSTED_PROXY_AUTH=true — opt-in; off by default → zero behavior change.
TRUSTED_PROXY_USER_HEADER (default Remote-User) — header carrying the username.
TRUSTED_PROXY_CIDRS (default 127.0.0.0/8,::1/128, loopback-only) — the identity header is honored only when the request's direct socket peer is within these ranges. We check socket.remoteAddress, not X-Forwarded-For, so a client reaching the app directly cannot spoof the header.
- Single-user invariant preserved: if an account already exists, the header is accepted only if it matches that user; otherwise it is refused (no silent multi-user creation — consistent with the
/register guard). On a fresh instance the proxy identity provisions the single user.
- REST (
authenticateToken), the /api/auth/status gate (so the SPA skips the login form) and the WebSocket upgrade (verifyClient) are all covered.
Security notes (for reviewers)
Open questions
- CIDR matching — I kept a small dependency-free matcher +
node:test unit tests (IPv6 currently loopback/exact-match only). Would you prefer pulling in ipaddr.js for full IPv6 prefix support? It's already transitively present via express → proxy-addr, but I didn't want to add an undeclared/lockfile dependency without your call.
- Multi-user — is this the seam where you'd eventually want optional multi-user, or keep it strictly single-user? (I kept it single-user.)
- Header naming —
Remote-User (Authelia/Caddy convention) vs Gitea/Grafana's X-WEBAUTH-USER. Configurable either way; just confirming the doc default.
I have a working implementation running in my homelab (Caddy + Authelia forward-auth) and am happy to open a PR — following the "discuss first" note in CONTRIBUTING before doing so. Implementation is AGPL, same as the project.
Summary
Add an opt-in mode where CloudCLI trusts an authenticated-username header injected by an upstream reverse proxy doing forward-auth (Authelia, oauth2-proxy, Authentik, Cloudflare Access, …), so users behind SSO don't have to log in a second time.
Problem
When CloudCLI runs behind an authenticating reverse proxy using forward-auth, the proxy already verifies the user and passes the identity downstream as a request header (e.g.
Remote-User). Today CloudCLI ignores it and still shows its own bcrypt/JWT login, so the user authenticates twice (SSO challenge + CloudCLI password). There is currently no inbound SSO / reverse-proxy auth mode. (#744 is the opposite direction — host→plugin identity via HMAC — not this.)Proposed behavior
Mirror the well-established pattern from Gitea (
ENABLE_REVERSE_PROXY_AUTHENTICATION+REVERSE_PROXY_TRUSTED_PROXIES) and Grafana ([auth.proxy]+whitelist):TRUSTED_PROXY_AUTH=true— opt-in; off by default → zero behavior change.TRUSTED_PROXY_USER_HEADER(defaultRemote-User) — header carrying the username.TRUSTED_PROXY_CIDRS(default127.0.0.0/8,::1/128, loopback-only) — the identity header is honored only when the request's direct socket peer is within these ranges. We checksocket.remoteAddress, notX-Forwarded-For, so a client reaching the app directly cannot spoof the header./registerguard). On a fresh instance the proxy identity provisions the single user.authenticateToken), the/api/auth/statusgate (so the SPA skips the login form) and the WebSocket upgrade (verifyClient) are all covered.Security notes (for reviewers)
Open questions
node:testunit tests (IPv6 currently loopback/exact-match only). Would you prefer pulling inipaddr.jsfor full IPv6 prefix support? It's already transitively present viaexpress→proxy-addr, but I didn't want to add an undeclared/lockfile dependency without your call.Remote-User(Authelia/Caddy convention) vs Gitea/Grafana'sX-WEBAUTH-USER. Configurable either way; just confirming the doc default.I have a working implementation running in my homelab (Caddy + Authelia forward-auth) and am happy to open a PR — following the "discuss first" note in CONTRIBUTING before doing so. Implementation is AGPL, same as the project.