Skip to content

fix(matchmaking): self-heal a missing session instead of 401#33

Open
ChronoFinale wants to merge 1 commit into
Balatro-Multiplayer:mqttfrom
ChronoFinale:fix/matchmaking-ensure-session
Open

fix(matchmaking): self-heal a missing session instead of 401#33
ChronoFinale wants to merge 1 commit into
Balatro-Multiplayer:mqttfrom
ChronoFinale:fix/matchmaking-ensure-session

Conversation

@ChronoFinale

Copy link
Copy Markdown

Summary

Fixes the "Session not found" (401) that strands an authenticated player in matchmaking (#32).

A player's session is an in-memory cache of their players row. It's wiped on a server restart and reaped by the EMQX disconnect webhook for non-lobby players — but the player's JWT is stateless and outlives it. The matchmaking routes looked the session up with a strict getSession() and threw Session not found when it was absent, with no recovery path (the client never re-auths on a still-valid token).

This routes the lookup through a shared ensureSession() that rebuilds the session from the durable player record on a miss — the same self-healing pattern /auth/refresh and the auth endpoints already use. A player who genuinely doesn't exist in the DB is still rejected (404).

Changes

  • add ensure-session.ts (reuses the existing dbPlayerToSessionInit mapper — no duplicated logic)
  • use ensureSession() in /queue, /matches/:id/start, /matches/:id/result
  • export dbPlayerToSessionInit so the helper can share it
  • regression tests: heals a known player to 200, rejects an unknown player with 404

Why this is safe / cheap

  • Not a security change — the JWT already proves auth; ban and ToS checks are unaffected (bans are read fresh from the DB, not the session).
  • Cheap — the warm path is still an in-memory map lookup; the DB is only touched on a cache-miss (one primary-key read), after which it's cached again.
  • Consistent — 5 auth endpoints and /auth/refresh already self-heal this exact way; matchmaking was the inconsistent gate.

Scope

This is the minimal, tested fix for the reported symptom. The same hasActiveSession-or-deny pattern also exists in the EMQX auth webhook (so MQTT reconnection can't self-heal either) — that, plus client-side re-auth on a 401, is the deeper root fix. Those are noted in #32 as proposed follow-ups since they touch a broader boundary.

Refs #32

🤖 Generated with Claude Code

The matchmaking routes looked up the player's in-memory session with a
strict getSession() and threw "Session not found" (401) when it was
absent. Sessions are in-memory only, so they are lost on a server
restart and reaped by the EMQX disconnect webhook for non-lobby players,
while the player's JWT is stateless and outlives them. The result: a
still-authenticated player is permanently stranded with no recovery path
(the client never re-auths on a valid token).

Route the lookup through a shared ensureSession(), which rebuilds the
session from the durable player record when it is missing — the same
self-healing pattern the auth endpoints already use. A player who does
not exist in the DB at all is genuinely unknown and still rejected (404).

- add ensure-session.ts (reuses the existing dbPlayerToSessionInit mapper)
- use ensureSession() in /queue, /matches/:id/start, /matches/:id/result
- export dbPlayerToSessionInit so the helper can share it (no duplication)
- add regression tests: heals a known player to 200, rejects unknown 404

Refs Balatro-Multiplayer#32

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant