Skip to content

feat(auth): opt-in trusted reverse-proxy header authentication#799

Open
sntgl wants to merge 2 commits into
siteboon:mainfrom
sntgl:feat/trusted-proxy-auth
Open

feat(auth): opt-in trusted reverse-proxy header authentication#799
sntgl wants to merge 2 commits into
siteboon:mainfrom
sntgl:feat/trusted-proxy-auth

Conversation

@sntgl
Copy link
Copy Markdown

@sntgl sntgl commented May 27, 2026

Implements the RFC in #798. Opt-in, off by default — when TRUSTED_PROXY_AUTH is unset, behavior is identical to upstream.

What

Behind an authenticating reverse proxy (Authelia / oauth2-proxy / Authentik / Cloudflare Access) doing forward-auth, CloudCLI trusts the proxy-vouched username header instead of requiring a second login.

How

  • server/middleware/auth.jsresolveTrustedProxyUser(req) honors TRUSTED_PROXY_USER_HEADER (default Remote-User) only when the request's direct socket peer is within TRUSTED_PROXY_CIDRS (default loopback-only). Anti-spoof anchored on socket.remoteAddress, never X-Forwarded-For. Single-user invariant preserved (match existing user or provision the first; never silently multi-create). Wired into authenticateToken.
  • server/routes/auth.js/api/auth/status issues a JWT when a trusted identity is present, so the SPA skips the login screen.
  • server/modules/websocket/services/websocket-auth.service.ts + server/index.js — WS upgrade auth honors the same header via the existing DI pattern (verifyClient already receives info.req).
  • src/components/auth/{context/AuthContext.tsx,types.ts} — frontend adopts the /status-issued token.
  • server/middleware/auth.trusted-proxy.test.jsnode:test units for the CIDR / anti-spoof boundary.

Config

Env Default Meaning
TRUSTED_PROXY_AUTH false master switch
TRUSTED_PROXY_USER_HEADER Remote-User identity header
TRUSTED_PROXY_CIDRS 127.0.0.0/8,::1/128 trusted direct-peer ranges

Test plan

  • node --import tsx --test server/middleware/auth.trusted-proxy.test.js
  • Manual: behind Caddy + Authelia forward-auth → no second login; direct request to :3001 without the header / from outside the CIDR → rejected (falls back to normal JWT login).

Opening as draft per the "discuss first" note in CONTRIBUTING (see #798) — happy to iterate on the open questions there or hold until you've weighed in.

Summary by CodeRabbit

  • New Features

    • Trusted reverse-proxy authentication: accept and provision users based on proxy identity headers, enable proxy-based auto-login, and return session tokens when applicable.
    • WebSocket upgrades now accept proxy-authenticated connections in addition to existing token flows.
  • Tests

    • Added tests covering CIDR/address validation and trusted-proxy user provisioning and enforcement.

When deployed behind an authenticating reverse proxy doing forward-auth
(Authelia, oauth2-proxy, Authentik, Cloudflare Access, ...), CloudCLI can
trust the proxy-vouched username header instead of requiring a second login.
Opt-in via TRUSTED_PROXY_AUTH; off by default => behavior is unchanged.

- server/middleware/auth.js: resolveTrustedProxyUser(req) honors
  TRUSTED_PROXY_USER_HEADER (default Remote-User) only when the request's
  direct socket peer is within TRUSTED_PROXY_CIDRS (default loopback-only).
  Anti-spoof is anchored on socket.remoteAddress, never X-Forwarded-For.
  Single-user invariant preserved: match the existing user or provision the
  first one; never silently create additional users (mirrors /register).
- server/routes/auth.js: /api/auth/status issues a JWT when a trusted
  identity is present so the SPA skips the login screen.
- websocket-auth.service.ts + server/index.js: WS upgrade auth honors the
  same header via the existing dependency-injection seam (verifyClient).
- AuthContext/types: frontend adopts the status-issued token.
- node:test units for the CIDR / anti-spoof boundary.

Mirrors the established pattern in Gitea (ENABLE_REVERSE_PROXY_AUTHENTICATION
+ REVERSE_PROXY_TRUSTED_PROXIES) and Grafana ([auth.proxy] + whitelist).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 65e8e708-8402-49ed-8d3e-2fdb71264f68

📥 Commits

Reviewing files that changed from the base of the PR and between 4619926 and e5a1f28.

📒 Files selected for processing (1)
  • server/middleware/auth.trusted-proxy.test.js

📝 Walkthrough

Walkthrough

This PR adds opt-in trusted reverse-proxy header authentication to both HTTP and WebSocket. Incoming requests from allowed CIDR ranges can authenticate via a configured user identity header; the first such user is auto-provisioned with a secure bcrypt hash. The /status endpoint and WebSocket upgrades both check proxy authentication before falling back to standard JWT validation. The frontend automatically adopts returned tokens and user sessions.

Changes

Trusted Reverse-Proxy Header Authentication

Layer / File(s) Summary
Type contracts for proxy authentication
src/components/auth/types.ts
AuthStatusPayload extends to include optional isAuthenticated, token, and user fields for representing trusted-proxy-established sessions.
Trusted-proxy middleware implementation and helpers
server/middleware/auth.js, server/middleware/auth.trusted-proxy.test.js
Core middleware adds CIDR parsing, IPv4/IPv6 network matching, and lazy single-user provisioning via bcrypt. The authenticateToken middleware inserts proxy-auth check before JWT validation. Exports three helpers (resolveTrustedProxyUser, cidrMatch, isFromTrustedProxy) with comprehensive unit tests covering CIDR matching and anti-spoofing boundaries.
Server and HTTP auth route integration
server/index.js, server/routes/auth.js
Wires resolveTrustedProxyUser into the server's WebSocket verifyClient configuration. The /status endpoint checks for a proxy-resolved user and returns an authenticated response with generated token and minimal user info when found, bypassing the normal unauthenticated discovery flow.
WebSocket authentication with proxy support
server/modules/websocket/services/websocket-auth.service.ts
Introduces WebSocketUser shared type; adds optional resolveTrustedProxyUser callback to WebSocketAuthDependencies. The verifyWebSocketClient function checks proxy resolution first (when provided and returning a user) before falling back to OSS JWT token extraction from query or Authorization header.
Frontend auto-login from trusted proxy
src/components/auth/context/AuthContext.tsx
The checkAuthStatus method now detects when the status response includes both token and user, immediately establishes the session via setSession, refreshes onboarding status, and returns early without invoking the stored-token fallback path. Hook dependency array updated to include setSession.

Possibly Related Issues

  • #798 – Implements the trusted reverse-proxy RFC, covering proxy CIDR checks, trusted-user header resolution, single-user provisioning, and WebSocket/status integration.

Poem

🐰 A proxy whispers its secrets so true,
Headers held safe in the CIDR queue,
One user emerges from bcrypt's embrace,
Token and session find their place,
WebSockets and frontend dance in accord!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and concisely summarizes the main change: adding opt-in trusted reverse-proxy header authentication support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@blackmammoth
Copy link
Copy Markdown
Collaborator

Hey @sntgl, thanks for the pr. Is it ready for review or should we close this?

@sntgl sntgl marked this pull request as ready for review June 2, 2026 06:37
@sntgl
Copy link
Copy Markdown
Author

sntgl commented Jun 2, 2026

Hey @sntgl, thanks for the pr. Is it ready for review or should we close this?

Yes it is ready, you can read related issue #798 and review this pr.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
server/middleware/auth.trusted-proxy.test.js (1)

1-46: ⚡ Quick win

Consider adding tests for resolveTrustedProxyUser.

The helper tests cover CIDR matching and anti-spoofing boundaries well. However, resolveTrustedProxyUser (the main entry point used by authenticateToken and /status) lacks test coverage for key scenarios:

  • User lookup when header is present but user exists/doesn't exist
  • Single-user invariant enforcement (different proxy identity after first user)
  • Header absent or non-string values
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/middleware/auth.trusted-proxy.test.js` around lines 1 - 46, Add unit
tests for resolveTrustedProxyUser to cover: (1) when the trusted identity header
is present and maps to an existing user (mock or seed the user lookup to return
a user) and when it does not exist (ensure it returns null or creates/handles
missing user per implementation); (2) the single-user invariant by first
resolving one trusted identity then attempting to resolve a different identity
and asserting the function enforces the invariant (rejects or returns the
original user); and (3) header absent and non-string header values (undefined,
null, numeric) to confirm it returns null/does not throw. Use the same test
harness setup as the existing tests (process.env JWT_SECRET/TRUSTED_PROXY_*,
import resolveTrustedProxyUser from './auth.js') and reference
resolveTrustedProxyUser and authenticateToken behaviors to mirror real usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@server/middleware/auth.trusted-proxy.test.js`:
- Around line 1-46: Add unit tests for resolveTrustedProxyUser to cover: (1)
when the trusted identity header is present and maps to an existing user (mock
or seed the user lookup to return a user) and when it does not exist (ensure it
returns null or creates/handles missing user per implementation); (2) the
single-user invariant by first resolving one trusted identity then attempting to
resolve a different identity and asserting the function enforces the invariant
(rejects or returns the original user); and (3) header absent and non-string
header values (undefined, null, numeric) to confirm it returns null/does not
throw. Use the same test harness setup as the existing tests (process.env
JWT_SECRET/TRUSTED_PROXY_*, import resolveTrustedProxyUser from './auth.js') and
reference resolveTrustedProxyUser and authenticateToken behaviors to mirror real
usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1e982861-4c1b-481e-aa09-423f8a1da562

📥 Commits

Reviewing files that changed from the base of the PR and between 10f721c and 4619926.

📒 Files selected for processing (7)
  • server/index.js
  • server/middleware/auth.js
  • server/middleware/auth.trusted-proxy.test.js
  • server/modules/websocket/services/websocket-auth.service.ts
  • server/routes/auth.js
  • src/components/auth/context/AuthContext.tsx
  • src/components/auth/types.ts

Address CodeRabbit nitpick: the trusted-proxy tests covered the CIDR /
anti-spoofing boundary but not resolveTrustedProxyUser, the entry point used
by authenticateToken and /status. Add cases for first-user provisioning,
existing-user lookup (no duplicate), the single-user invariant against a
different proxy identity, absent / non-string identity headers, and a valid
header arriving from an untrusted source. Uses a throwaway SQLite database per
case (same isolation harness as the sessions.db integration test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants