Deactivated users keep working until JWT expiry (24h) #34

Closed
opened 2026-08-23 05:42:04 +00:00 by nasandre · 2 comments
Owner

Medium

`userService.loginUser` correctly rejects deactivated accounts at login time, but the `authenticate` middleware only verifies the JWT signature — it never rechecks `users.is_active`. After an admin deactivates an account (Admin panel DEL button sets `is_active=false`), the user's existing token keeps full access for up to 24 hours.

`backend/src/middleware/auth.ts` — `authenticate()` decodes and trusts `req.user` with no DB lookup.

Also relevant: JWT claims include `isGM`, so demoting a GM also takes up to 24h to take effect.

Suggested fix

On `authenticate`, cache-and-check the user row (short TTL, e.g. 60s) or at minimum verify `is_active`; optionally bump a `token_version` column embedded in the JWT to invalidate instantly.

## Medium \`userService.loginUser\` correctly rejects deactivated accounts at login time, but the \`authenticate\` middleware only verifies the JWT signature — it never rechecks \`users.is_active\`. After an admin deactivates an account (Admin panel DEL button sets \`is_active=false\`), the user's existing token keeps full access for up to 24 hours. \`backend/src/middleware/auth.ts\` — \`authenticate()\` decodes and trusts \`req.user\` with no DB lookup. Also relevant: JWT claims include \`isGM\`, so demoting a GM also takes up to 24h to take effect. ## Suggested fix On \`authenticate\`, cache-and-check the user row (short TTL, e.g. 60s) or at minimum verify \`is_active\`; optionally bump a \`token_version\` column embedded in the JWT to invalidate instantly.
Author
Owner

✅ Fixed:

  • authenticate middleware now loads fresh account state from DB (60s TTL cache) and rejects deactivated accounts immediately
  • Fresh values also override stale JWT claims (email, isGM), so GM demotion takes effect within ~60s too
  • DB-unavailable falls back to cached state; no cache + no DB → 403 (fail closed)
✅ **Fixed**: - `authenticate` middleware now loads fresh account state from DB (60s TTL cache) and **rejects deactivated accounts immediately** - Fresh values also override stale JWT claims (`email`, `isGM`), so GM demotion takes effect within ~60s too - DB-unavailable falls back to cached state; no cache + no DB → 403 (fail closed)
Author
Owner

✅ Fixed & verified live (commit 05b5453):

  • Test: deactivated a user in DB, kept their valid JWT
  • Within 60s cache window → 200 (by design, TTL = 60s)
  • After TTL expired → 403 Account deactivated ✅

Fresh claims also mean GM demotion propagates within the same window. User re-activated after test.

✅ **Fixed & verified live** (commit `05b5453`): - Test: deactivated a user in DB, kept their valid JWT - Within 60s cache window → 200 (by design, TTL = 60s) - After TTL expired → **403 `Account deactivated`** ✅ Fresh claims also mean GM demotion propagates within the same window. User re-activated after test.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nasandre/wh40-rogue-trader#34
No description provided.