stack/apps/backend
Mantra a132dd23f9
fix: refresh-token P2025 race with concurrent sign-out (#1372)
## Summary
- Fixes Sentry
[STACK-BACKEND-146](https://stackframe-pw.sentry.io/issues/7377768662/):
`PrismaClientKnownRequestError` P2025 on
`projectUserRefreshToken.update()` during token refresh.
- Root cause: `generateAccessTokenFromRefreshTokenIfValid`
(`apps/backend/src/lib/tokens.tsx`) reads the refresh-token row
upstream, then issues `.update(...)` on it (and on `projectUser`) inside
a `Promise.all`. If a concurrent sign-out (`DELETE
/auth/sessions/current`), session revoke, password change, or user
deletion removes the row between the read and the update, Prisma throws
P2025 and the refresh endpoint 500s.

## Changes
- `apps/backend/src/lib/tokens.tsx` — swap the two `.update(...)`s for
`.updateMany(...)` so a missing row is a no-op, then re-check the
refresh token still exists; return `null` if it doesn't. The refresh
route already maps `null` -> `KnownErrors.RefreshTokenNotFoundOrExpired`
(401), which is the correct user-facing behavior for a just-revoked
session.
- `apps/backend/src/oauth/model.tsx` — in `generateAccessToken`, replace
the "ultra-rare race condition" `throwErr` fallback with `throw new
KnownErrors.RefreshTokenNotFoundOrExpired()` so concurrent sign-out
during an OAuth `refresh_token` grant returns a clean 401 instead of
500.
-
`apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/current/refresh-race.test.ts`
— new regression test that fires `POST /auth/sessions/current/refresh`
and `DELETE /auth/sessions/current` concurrently with the same refresh
token. Before the fix it 500s on the first iteration; after, it passes
in ~12s.

## Test plan
- [x] New regression test passes locally.
- [x] Existing `auth/sessions/**` + `auth/oauth/token.test.ts` still
pass (27 tests, 3 todo, 0 failed).
- [ ] CI green.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Refresh flows now detect a revoked or removed refresh token during
concurrent operations and stop cleanly, preventing issuance of an access
token from stale data.
* A specific refresh-token-not-found/expired error is returned instead
of a generic failure when refresh cannot proceed.

* **Tests**
* Added E2E tests exercising concurrent refresh vs sign-out to prevent
race-condition crashes and validate safe handling of competing requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 18:44:39 +00:00
..
prisma Speed up seed script by a lot 2026-04-18 17:29:21 -07:00
scripts Fix ClickHouse OOM in MAU query + optimize /internal/metrics route (#1344) 2026-04-19 22:57:46 -07:00
src fix: refresh-token P2025 race with concurrent sign-out (#1372) 2026-04-24 18:44:39 +00:00
.env LLM MCP Flow (#1321) 2026-04-15 17:57:08 +00:00
.env.development LLM MCP Flow (#1321) 2026-04-15 17:57:08 +00:00
.eslintrc.cjs tsup for stack-shared (#647) 2025-04-28 21:26:52 -07:00
.gitignore private files n sm build shit (#1276) 2026-03-23 12:31:36 -07:00
instrumentation-client.ts Upgrade backend to Next.js 16 2025-12-12 16:59:07 -08:00
LICENSE Split backend and dashboard (#83) 2024-06-18 15:49:31 +02:00
next.config.mjs private files n sm build shit (#1276) 2026-03-23 12:31:36 -07:00
package.json chore: update package versions 2026-04-20 19:06:56 -07:00
prisma.config.ts [Fix]: Assortment of Bugs with Timefold Table and Payments (#1348) 2026-04-18 14:17:24 -07:00
tsconfig.json Fix lint 2026-02-27 09:59:26 -08:00
vercel.json External db sync (#1036) 2026-02-05 12:04:31 -08:00
vitest.config.ts Customizable ports (#962) 2025-10-20 15:24:47 -07:00
vitest.setup.ts Customizable ports (#962) 2025-10-20 15:24:47 -07:00