lib/auth.ts: shared guards — requireAuth(req) (X-Flight-Session header) and requireAuthSse(req) (header OR ?session=, since EventSource cannot set headers). Returns {ok, inst, user, player} or a ready-made 401/500; joins players on username (seeded admin → local row), missing players row → 500.
Guarded: /api/state, all eight action routes via one performAction(req, fn) call site in lib/game/actions.ts, and the SSE stream (connect-time validation).
Exempt by design: version/openapi/airports/login/register/logout/me, admin CRUD (own guard), all /api/test/* hooks.
New POST /api/register: strict body validation, username lowercased + [a-z0-9_-]{3,20}, password ≥8, 409 taken; creates users+players rows sharing one UUID with the starter economy and mints a session immediately (no second round-trip).
UI: new components/SignInScreen.tsx (sign in / create account); app/page.tsx gates the shell on it — unauthenticated browsers never poll /api/state. New client module lib/authClient.ts owns the localStorage key + header; every app fetch carries X-Flight-Session. Admin logout drops back to sign-in via a new onLogout prop.
E2E: new test-only POST /api/test/mint-player-session (FLIGHT_TEST_HOOKS=1, idempotent); e2e/auth.ts mints+applies per test; all guarded-route specs authenticate in beforeEach; events.spec uses the real ?session= path; api/openapi tests track the 18th endpoint; admin spec reworked for the sign-in gate.
Tests: new lib/auth.test.ts, register + mint-hook route tests; existing state/events/action route tests mint a session in beforeAll. OpenAPI documents /api/register (RegisterRequest schema).
Local verification: tsc --noEmit clean · vitest run 426/426 · playwright test --list 64 specs compile.
Multiplayer step 3/12 (ISS-107).
- `lib/auth.ts`: shared guards — `requireAuth(req)` (X-Flight-Session header) and `requireAuthSse(req)` (header OR ?session=, since EventSource cannot set headers). Returns `{ok, inst, user, player}` or a ready-made 401/500; joins players on username (seeded admin → local row), missing players row → 500.
- Guarded: `/api/state`, all eight action routes via one `performAction(req, fn)` call site in `lib/game/actions.ts`, and the SSE stream (connect-time validation).
- Exempt by design: version/openapi/airports/login/register/logout/me, admin CRUD (own guard), all /api/test/* hooks.
- New POST `/api/register`: strict body validation, username lowercased + `[a-z0-9_-]{3,20}`, password ≥8, 409 taken; creates users+players rows sharing one UUID with the starter economy and mints a session immediately (no second round-trip).
- UI: new `components/SignInScreen.tsx` (sign in / create account); `app/page.tsx` gates the shell on it — unauthenticated browsers never poll `/api/state`. New client module `lib/authClient.ts` owns the localStorage key + header; every app fetch carries X-Flight-Session. Admin logout drops back to sign-in via a new onLogout prop.
- E2E: new test-only POST /api/test/mint-player-session (FLIGHT_TEST_HOOKS=1, idempotent); `e2e/auth.ts` mints+applies per test; all guarded-route specs authenticate in beforeEach; events.spec uses the real ?session= path; api/openapi tests track the 18th endpoint; admin spec reworked for the sign-in gate.
- Tests: new lib/auth.test.ts, register + mint-hook route tests; existing state/events/action route tests mint a session in beforeAll. OpenAPI documents /api/register (RegisterRequest schema).
Local verification: `tsc --noEmit` clean · `vitest run` 426/426 · `playwright test --list` 64 specs compile.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Multiplayer step 3/12 (ISS-107).
lib/auth.ts: shared guards —requireAuth(req)(X-Flight-Session header) andrequireAuthSse(req)(header OR ?session=, since EventSource cannot set headers). Returns{ok, inst, user, player}or a ready-made 401/500; joins players on username (seeded admin → local row), missing players row → 500./api/state, all eight action routes via oneperformAction(req, fn)call site inlib/game/actions.ts, and the SSE stream (connect-time validation)./api/register: strict body validation, username lowercased +[a-z0-9_-]{3,20}, password ≥8, 409 taken; creates users+players rows sharing one UUID with the starter economy and mints a session immediately (no second round-trip).components/SignInScreen.tsx(sign in / create account);app/page.tsxgates the shell on it — unauthenticated browsers never poll/api/state. New client modulelib/authClient.tsowns the localStorage key + header; every app fetch carries X-Flight-Session. Admin logout drops back to sign-in via a new onLogout prop.e2e/auth.tsmints+applies per test; all guarded-route specs authenticate in beforeEach; events.spec uses the real ?session= path; api/openapi tests track the 18th endpoint; admin spec reworked for the sign-in gate.Local verification:
tsc --noEmitclean ·vitest run426/426 ·playwright test --list64 specs compile.