fix(auth): don't treat infra outages as "not authenticated" - #14
Merged
Merged
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
isAuthenticated()wrapped token verification and the Privy/DB user lookup in onetry/catchthat returned{ authenticated: false }on any error. So a transient Railway Postgres or Privy blip made every data route report "not authenticated":/api/user/statereturns 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 shareddev == proddata, 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'):{ 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.getUser+ DB lookup: an outage throws a typedAuthInfrastructureError(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
AuthInfrastructureError+authInfraResponse()helper + a reusablewithAuthInfra()wrapper.user/state,feed,notifications— now return a retryable 503 (Retry-After) on infra failure instead of a false 401 / silent guest.auth_infra_errorPostHog event to separate outages from token failures..gitignorehardened against accidental credential-file commits.Verification
npm run typecheck✅ andnpm run build✅ (both CI hard gates).isAuthenticatedcallers: 0 routes re-mask the new throw as a 401/guest-200 (user/statewas 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 (theincludelookup) is outside the auth gate, so a DB blip there → 500 not 503. Honest and non-demoting.SupercastUserStateProvider, and an axiosRetry-Afterinterceptor, to fully exploit the 503s.