Skip to content

feat(platform): implement trusted headers authentication middleware#1252

Merged
yannickmonney merged 2 commits into
mainfrom
feat/trusted-headers-middleware
Apr 9, 2026
Merged

feat(platform): implement trusted headers authentication middleware#1252
yannickmonney merged 2 commits into
mainfrom
feat/trusted-headers-middleware

Conversation

@yannickmonney
Copy link
Copy Markdown
Contributor

@yannickmonney yannickmonney commented Apr 9, 2026

Summary

  • Implement the missing HTTP middleware that wires up the existing trusted headers business logic to an actual endpoint (/api/trusted-headers/authenticate)
  • Header names are configurable via TRUSTED_*_HEADER env vars (defaults: Remote-Email, Remote-Name, Remote-Role, Remote-Teams) for compatibility with Authelia, Authentik, oauth2-proxy, etc.
  • Login page auto-redirects when TRUSTED_HEADERS_ENABLED=true — users behind a proxy never see the login form
  • Rewrite authentication docs to accurately describe the offline-first auth model, the three auth methods, and proxy configuration

Details

New files:

  • convex/trusted_headers_auth/internal_mutations.ts — Convex internalMutation wrapping the existing trustedHeadersAuthenticate business logic
  • convex/trusted_headers_auth/authenticate_handler.ts — HTTP handler that reads proxy headers, calls the mutation, signs session cookies (mirrors the SSO set-session pattern), and redirects to the app
  • convex/trusted_headers_auth/http_handlers.ts — httpAction wrapper for route registration

Auth flow:

  1. Browser → External Proxy (adds Remote-Email etc.) → Caddy → /api/trusted-headers/authenticate
  2. Convex HTTP action reads headers, finds/creates user + session
  3. Signs session cookie + generates JWT via Better Auth
  4. Returns HTML with Set-Cookie + redirect to dashboard

Test plan

  • Verify typecheck and lint pass (npm run typecheck --workspace=@tale/platform, npm run lint --workspace=@tale/platform)
  • Deploy with TRUSTED_HEADERS_ENABLED=true behind an authenticating proxy (e.g. Authelia) and verify auto-login
  • Verify configurable header names work (set TRUSTED_EMAIL_HEADER to proxy-specific header)
  • Verify first user auto-provisioning works via trusted headers (no manual sign-up needed)
  • Verify password login still works when trusted headers is disabled (default)
  • Verify SSO and trusted headers can coexist

Summary by CodeRabbit

  • New Features

    • Added trusted headers authentication support for reverse proxy deployments, enabling automatic user provisioning on first sign-in without self-service registration.
  • Documentation

    • Updated authentication documentation to clarify the offline-first model: first user creates the owner account, subsequent users are provisioned by admin settings. Added configuration options for trusted header names and internal secret validation.

Wire up the existing trusted headers business logic to an HTTP endpoint
so reverse proxies (Authelia, Authentik, oauth2-proxy) can authenticate
users via identity headers.

- Add Convex HTTP action at /api/trusted-headers/authenticate that reads
  proxy headers, finds/creates users, and sets session cookies
- Header names default to Remote-Email/Name/Role/Teams and are
  configurable via TRUSTED_*_HEADER env vars for proxy compatibility
- Login page auto-redirects to the auth endpoint when
  TRUSTED_HEADERS_ENABLED=true (user never sees the login form)
- Expose TRUSTED_HEADERS_ENABLED to frontend via server.ts env config
- Add TRUSTED_TEAMS_HEADER to docker-entrypoint env passthrough
- Rewrite authentication docs to accurately describe offline-first auth
  model, configurable header names, and proxy compatibility table
- Add clarifying comments to auth-related UI components
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR implements trusted headers authentication for Tale. It updates authentication documentation to describe an offline-first model where the first user creates an owner account and admins provision subsequent users. The implementation introduces a new HTTP endpoint (/api/trusted-headers/authenticate) that extracts identity claims from configurable proxy headers (email, name, role, teams), validates feature flags and internal secrets, manages session tokens with Better Auth integration, and sets secure cookies before redirecting users. Frontend routes are modified to conditionally route through this endpoint when enabled, while environment variables are added to configure header names. Documentation and code comments clarify the authentication flow and provisioning models.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The primary complexity stems from the new trustedHeadersAuthenticateHandler (265 lines) which implements HTTP handling, session token signing, cookie manipulation, Better Auth integration, and error recovery. While supporting changes (documentation, comments, environment configuration, route registration) are largely homogeneous and straightforward, the handler requires careful review for session security, cookie handling correctness, and authentication flow integrity.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main feature being implemented: a trusted headers authentication middleware system that processes identity headers from reverse proxies and authenticates users.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/trusted-headers-middleware

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.

Copy link
Copy Markdown

@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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/platform/docker-entrypoint.sh (1)

624-655: ⚠️ Potential issue | 🟠 Major

Sync TRUSTED_HEADERS_INTERNAL_SECRET to Convex env as well.

The trusted-headers flow validates this secret in Convex, but it is not included in the env sync list. That can silently disable the defense-in-depth check even when operators set it in container env.

🔧 Proposed fix
   ENV_VARS_TO_SYNC=(
     "SITE_URL"
     "BASE_PATH"
     "ENCRYPTION_SECRET_HEX"
     "BETTER_AUTH_SECRET"
@@
     "RAG_DATABASE_URL"
     "TRUSTED_HEADERS_ENABLED"
+    "TRUSTED_HEADERS_INTERNAL_SECRET"
     "TRUSTED_EMAIL_HEADER"
     "TRUSTED_NAME_HEADER"
     "TRUSTED_ROLE_HEADER"
     "TRUSTED_TEAMS_HEADER"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/docker-entrypoint.sh` around lines 624 - 655,
ENV_VARS_TO_SYNC array is missing TRUSTED_HEADERS_INTERNAL_SECRET so Convex
won't receive the runtime secret; add "TRUSTED_HEADERS_INTERNAL_SECRET" to the
ENV_VARS_TO_SYNC list in the docker-entrypoint.sh snippet (the ENV_VARS_TO_SYNC
variable) so the entrypoint will export/sync that environment variable into the
Convex environment alongside TRUSTED_HEADERS_ENABLED and related TRUSTED_*
entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/authentication.md`:
- Around line 80-87: Update the wording in the "How it works" section so Step 2
clearly states that the login page JavaScript performs a client-side navigation
using window.location.href to go to /api/trusted-headers/authenticate (instead
of implying an HTTP redirect); keep the rest of the flow unchanged and ensure
the phrase references the exact endpoint "/api/trusted-headers/authenticate" so
readers understand it's client-side navigation.

In `@services/platform/app/routes/_auth/log-in.tsx`:
- Around line 65-72: The current useEffect (checking trustedHeadersEnabled,
redirectTo, getEnv/siteUrl/basePath) unconditionally redirects to the
trusted-headers authenticate endpoint and can cause an infinite loop if that
endpoint redirects back to /log-in with an error; modify the effect to first
parse window.location.search (URLSearchParams) and skip the redirect when a
trusted_headers_error param is present (e.g., trusted_headers_error=1), and
ensure the authenticate endpoint includes that query param on error so the login
page can detect it and not re-trigger useEffect redirection.

In `@services/platform/convex/trusted_headers_auth/authenticate_handler.ts`:
- Around line 239-241: The escapeJs function currently only escapes backslashes
and single quotes; update escapeJs to also escape control and line-separator
characters that can break out of a JS string (at minimum \n, \r, \u2028, \u2029,
and optionally \t, \b, \f, \v) by replacing them with their JavaScript escape
sequences so the resulting single-quoted literal is safe; locate the escapeJs
function in authenticate_handler.ts and add replacements for these characters
(in addition to the existing backslash and single-quote handling) so any
redirectTo value cannot inject or break the generated JS string.

---

Outside diff comments:
In `@services/platform/docker-entrypoint.sh`:
- Around line 624-655: ENV_VARS_TO_SYNC array is missing
TRUSTED_HEADERS_INTERNAL_SECRET so Convex won't receive the runtime secret; add
"TRUSTED_HEADERS_INTERNAL_SECRET" to the ENV_VARS_TO_SYNC list in the
docker-entrypoint.sh snippet (the ENV_VARS_TO_SYNC variable) so the entrypoint
will export/sync that environment variable into the Convex environment alongside
TRUSTED_HEADERS_ENABLED and related TRUSTED_* entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fccc150b-6b52-4deb-abcd-ef4c5563f7ca

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf429a and c2c14d1.

📒 Files selected for processing (14)
  • README.md
  • docs/authentication.md
  • docs/environment-reference.md
  • services/platform/app/features/auth/hooks/queries.ts
  • services/platform/app/features/settings/organization/components/member-add-dialog.tsx
  • services/platform/app/routes/_auth/log-in.tsx
  • services/platform/app/routes/_auth/sign-up.tsx
  • services/platform/convex/http.ts
  • services/platform/convex/trusted_headers_auth/authenticate_handler.ts
  • services/platform/convex/trusted_headers_auth/http_handlers.ts
  • services/platform/convex/trusted_headers_auth/internal_mutations.ts
  • services/platform/docker-entrypoint.sh
  • services/platform/lib/env.ts
  • services/platform/server.ts

Comment thread docs/authentication.md
Comment thread services/platform/app/routes/_auth/log-in.tsx Outdated
- Break infinite redirect loop: login page checks for
  ?trusted_headers_error=1 param before redirecting to the auth
  endpoint; auth endpoint error responses now redirect back to
  /log-in?trusted_headers_error=1
- Harden escapeJs to escape \n, \r, \t, \f, \v, U+2028, U+2029 so
  redirectTo values cannot break the generated JS string literal
- Add TRUSTED_HEADERS_INTERNAL_SECRET to ENV_VARS_TO_SYNC so Convex
  receives the runtime secret
- Clarify docs: step 2 describes client-side navigation via
  window.location.href, not an HTTP redirect
@yannickmonney yannickmonney merged commit 751f4d9 into main Apr 9, 2026
24 checks passed
@yannickmonney yannickmonney deleted the feat/trusted-headers-middleware branch April 9, 2026 12:28
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.

1 participant