New game_admin_game_new route/form (reuses AdminGameType, same pattern
as editing a game's total time) so games - including the 3 upcoming
Easy/Medium/Hard difficulty options - can be created through the admin
panel instead of needing a direct DB insert.
Also show each game's total time (in minutes) next to the player
count in the "Create New Session" dropdown, since that's the actual
difficulty signal players are choosing between - number of players
alone didn't convey it.
Difficulty itself needs no new session-level concept: each difficulty
is just a separate Game row with its own TOTAL_TIME setting, which
checkAllPlayersReady() already reads when starting the session's
timer. Hint timing depending on difficulty is separate, upcoming work.
The admin panel already checked CSRF tokens on destructive actions,
but the player-facing raw HTML forms (create/join/leave/start
session, toggle ready, lobby chat, feedback) had none - cookie
SameSite=Lax blunts classic cross-site auto-submit attacks but isn't
a substitute for real tokens. Adds matching csrf_token()/
isCsrfTokenValid() checks to all of them.
Also adds login_throttling (5 attempts/15 min) to stop unlimited
password guessing, and a per-user rate limiter (10/min) on the
invite-code join endpoint, since invite codes are only 32-bit and
had no protection against brute-forcing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Players saw the raw enum value (e.g. "created") in the sessions
table, which doesn't mean anything to them. Adds SessionStatus::label()
mapping each status to a player-facing description like "Waiting for
players".