Skip to content

Backend auth, authz & abuse defenses (Phase 2 server hardening) #66

Description

@Sootopolis

Context

Today CcasServer has no authentication, authorization, CORS, or rate-limiting layer — routes are concatenated bare and served directly, with no middleware seam (CcasServer.scala). That is fine while CCAS is single-user on loopback, but a future frontend makes the server multi-user and publicly reachable, at which point two properties are required:

  1. Users must not read or modify data for clubs they do not manage.
  2. Even for clubs they manage, a user must not be able to overwhelm the server (or, transitively, Chess.com).

This issue tracks that work. The design is already laid out in the CLI roadmap (ultra-sparkling-unicorn.md, "Phase 2 — Chess.com OAuth" + "Abuse defenses"); this issue makes it a tracked epic.

Not a priority yet — gated behind Phase 1 CLI work and the hosting decision (#60). Filed now so the design is recorded.

Approach (anchored on the roadmap)

  • Identity = Chess.com OAuth (Authorization Code + PKCE). The only flow Chess.com supports. Server holds the PKCE verifier; a pseudo-device-flow lets the CLI authenticate without binding a local port.
  • Authorization collapses to a join. Chess.com username (from the id_token) is exactly the key ClubAdmin rows use. Per request: resolve <club-slug>ClubId, then SELECT 1 FROM club_admin WHERE club_id = ? AND username = ?; 403 if absent. ClubAdmin (already populated by ClubDataApp) stays the source of truth, so there is no separate ownership state to drift.
  • Opaque, DB-backed session tokens (hashed in user_session_token) — instantly revocable.
  • Maintainer powers via CCAS_MAINTAINERS env var, fail-closed at startup (server-side, unforgeable).
  • Same-origin serving (SPA + API on one host).

Checklist

Auth & authz:

  • New ccas.server.auth package: ChessComOAuthClient (PKCE, code exchange, JWKS verify), AuthMiddleware, session-token model
  • Tables: user_account, user_chess_com_oauth (tokens encrypted at rest), user_session_token, cli_pending_login (schemas in roadmap Phase 2 §"DB schema")
  • Server auth endpoints: /api/auth/cli/{start,poll}, GET /login, GET /auth/chess-com/callback, /api/auth/{logout,me}
  • AuthMiddleware wired around JobRoutes/ScheduleRoutes/BlacklistRoutes/RecruitmentCriteriaRoutes; HealthRoutes + auth endpoints stay public. Auth-required is the default in route registration so a new route can't accidentally ship ungated
  • clubAdminGuard(clubId) helper + ForbiddenException added to the UserFacingError ADT
  • Opportunistic ClubAdmin refresh when cached admin list is stale (keeps authz current)

Abuse defenses / hardening (roadmap "Abuse defenses" section):

  • Auth wall by default; return 404 (not 401) on gated routes when token absent (reduce surface enumeration)
  • Per-IP rate limit on pre-auth endpoints (sliding-window Ref[Map[Ip,Counter]]); 429 + Retry-After
  • Per-user quotas on authed routes: concurrency cap (extend the existing (kind,club_id) WHERE status='Running' index with a per-user COUNT) + daily job budget
  • Body-size limits + read/idle timeouts (zio-http config; slowloris + memory-bomb defense)
  • Sign the OAuth state param (HMAC-SHA256) to prevent replay/tamper
  • Cleanup daemons (expired cli_pending_login, old user_session_token/job_run, old job logs)
  • Append-only audit_event table (login, logout, token revoke, job submit, 403, quota-exceeded)
  • Least-privilege DB role split (ccas_app runtime vs ccas_admin for DDL)
  • CCAS_READ_ONLY kill-switch + monitoring/alerts (429/5xx spikes, per-user submit rate)

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:serverBackend HTTP server, jobs, schedulerenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions