Skip to content

fix(auth): don't treat infra outages as "not authenticated" - #14

Merged
arcabotai merged 1 commit into
mainfrom
fix/isauthenticated-infra-errors
Jun 22, 2026
Merged

arcabotai merged 1 commit into
mainfrom
fix/isauthenticated-infra-errors

Conversation

@felirami

Copy link
Copy Markdown
Collaborator

Problem

isAuthenticated() wrapped token verification and the Privy/DB user lookup in one try/catch that returned { authenticated: false } on any error. So a transient Railway Postgres or Privy blip made every data route report "not authenticated":

  • the timeline goes empty, and
  • worse, /api/user/state returns a 200 guest payload on !authenticated, so the client successfully receives a "you're a guest" response and silently demotes a signed-in user into guest / connect-account onboarding — which, on shared dev == prod data, can write duplicate records to production.

This was the #1 item on the hardening roadmap (root cause of the 2026-05-30 "timeline not showing" incident).

Fix

Split the check into two stages with a shared error classifier ('auth' | 'infra' | 'unknown'):

  • Stage 1 — token verification: a bad/expired/missing token still returns { authenticated: false } → real 401, unchanged. A recognised infra error (network / JWKS-timeout / 5xx while the SDK fetches the signing key on a cold instance) is surfaced instead.
  • Stage 2 — Privy getUser + DB lookup: an outage throws a typed AuthInfrastructureError (HTTP 503) instead of masquerading as "not authenticated". Only a Privy 4xx rejection returns { authenticated: false }.

Per-stage defaults match each stage's common failure (bad token vs. dependency down), so genuine auth failures are never turned into 503s and vice-versa.

Surfacing it

  • New AuthInfrastructureError + authInfraResponse() helper + a reusable withAuthInfra() wrapper.
  • The timeline-critical read routes — user/state, feed, notifications — now return a retryable 503 (Retry-After) on infra failure instead of a false 401 / silent guest.
  • The remaining ~110 callers need no edit: a transient outage now surfaces as an honest 500 (retryable, never a false 401). A shared wrapper is in place to widen 503 coverage later.
  • New auth_infra_error PostHog event to separate outages from token failures.
  • .gitignore hardened against accidental credential-file commits.

Verification

  • npm run typecheck ✅ and npm run build ✅ (both CI hard gates).
  • Adversarial review across all 114 isAuthenticated callers: 0 routes re-mask the new throw as a 401/guest-200 (user/state was the only guest-emitter and is fixed); every other catch returns an error status.

Known follow-ups (not in this PR)

  • user/state's second Prisma query (the include lookup) is outside the auth gate, so a DB blip there → 500 not 503. Honest and non-demoting.
  • Optional: a client-side degraded/retry state in SupercastUserStateProvider, and an axios Retry-After interceptor, to fully exploit the 503s.
  • The infra-failure path can't be safely exercised on shared prod data, so this relies on the type/build gates + review rather than a live E2E.

isAuthenticated() wrapped token verification AND the Privy/DB user lookup
in one try/catch that returned { authenticated: false } on ANY error. A
transient Railway Postgres or Privy blip therefore made every data route
report "not authenticated" — emptying the timeline and, via
/api/user/state's 200 guest payload, silently demoting signed-in users to
guest / connect-account onboarding (dangerous on shared dev==prod data).

Split the check into two stages with a shared error classifier:
- token verification failures (missing/expired/invalid token) keep
  returning { authenticated: false } -> real 401, unchanged
- Privy API / database failures throw AuthInfrastructureError so routes
  can return a retryable 503 instead of a false 401

Timeline-critical routes (user/state, feed, notifications) return 503 on
infra failure via a reusable withAuthInfra() wrapper; the remaining ~110
callers surface a transient outage as an honest 500 (never a false 401).
Adds an auth_infra_error analytics event to separate outages from token
failures. Also hardens .gitignore against credential-file commits.

Verified: typecheck + build green.
@vercel

vercel Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
castora Ready Ready Preview, Comment Jun 22, 2026 10:18am

@arcabotai
arcabotai merged commit c127243 into main Jun 22, 2026
6 checks passed

This branch was successfully deployed

1 active deployment
Preview — 5ce922aa Deployed Jun 22, 2026 by vercel[bot]
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