fix: prevent race conditions in token operations and auth flows

Backend:
- Refresh token rotation: use atomic UPDATE...WHERE revoked_at IS NULL
  to prevent concurrent refresh requests from both succeeding
- Account invite codes: SELECT FOR UPDATE to prevent double-spend
- Platform invite codes: SELECT FOR UPDATE to prevent double-spend
- Password reset tokens: SELECT FOR UPDATE to prevent double-use
- Email verification tokens: SELECT FOR UPDATE to prevent double-use

Frontend:
- Token refresh subscriber arrays: swap before iterating so a throwing
  callback doesn't leave the queue in a dirty state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-09 17:20:38 -04:00
parent 5095b0d8df
commit c724ad8062
2 changed files with 40 additions and 22 deletions

View File

@@ -75,15 +75,19 @@ let refreshSubscribers: ((token: string) => void)[] = []
let refreshFailSubscribers: ((error: unknown) => void)[] = []
function onRefreshed(token: string) {
refreshSubscribers.forEach(cb => cb(token))
// Swap arrays before iterating — if a callback throws, the arrays
// are already cleared so the next refresh cycle starts clean.
const subscribers = refreshSubscribers
refreshSubscribers = []
refreshFailSubscribers = []
subscribers.forEach(cb => cb(token))
}
function onRefreshFailed(error: unknown) {
refreshFailSubscribers.forEach(cb => cb(error))
const failSubscribers = refreshFailSubscribers
refreshSubscribers = []
refreshFailSubscribers = []
failSubscribers.forEach(cb => cb(error))
}
// Response interceptor - handle token refresh