62 lines
5.0 KiB
Markdown
62 lines
5.0 KiB
Markdown
# API Contract (auth-enabled)
|
||
|
||
All endpoints are JSON. Most routes require the HttpOnly `player` cookie issued after register/login. Admin access is granted only via an authenticated admin user session (`IsAdmin=true` on the account).
|
||
Auth and admin-sensitive routes are rate-limited and return HTTP `429` on excessive requests.
|
||
|
||
## Auth
|
||
POST /api/auth/register — accepts optional `adminKey` to set `IsAdmin=true` only for bootstrap of the first admin account
|
||
GET /api/auth/options — `{ ownerExists }` for registration UX (hide admin-key input after owner bootstrap)
|
||
POST /api/auth/login
|
||
POST /api/auth/logout
|
||
Display names are set during registration and are immutable afterward.
|
||
Passwords must be 8-128 chars and contain uppercase, lowercase and number.
|
||
The first account created with a valid `adminKey` becomes both `IsAdmin=true` and `IsOwner=true`.
|
||
Owner bootstrap is also enforced by a database uniqueness constraint (`IsOwner=true` can only exist once), so concurrent owner registration races fail safely with `400`.
|
||
|
||
## State (requires auth)
|
||
GET /api/state — returns caller identity (`id`, `username`, `displayName`, `isAdmin`, `isOwner`) plus currentPhase, votesFinal, resultsOpen, updatedAt, counts (players/suggestions/votes). Supports conditional reads with `ETag`/`If-None-Match`; unchanged state returns HTTP `304`.
|
||
GET /api/events/state — server-sent events stream for state invalidation (`ready` and `state` events with monotonic version payload) for event-driven client refresh.
|
||
GET /api/me — id, displayName, username, isAdmin, isOwner, currentPhase, votesFinal
|
||
|
||
## Player (requires auth)
|
||
POST /api/me/phase/next — advance caller to next phase (Suggest→Vote requires at least one own suggestion; Vote→Results is gated by resultsOpen)
|
||
POST /api/me/phase/prev — admin-only move caller backward (Results→Vote→Suggest)
|
||
|
||
## Suggestions (requires auth + phase gating)
|
||
GET /api/suggestions/mine — own suggestions (Suggest phase)
|
||
POST /api/suggestions — create (name required ≤100; max 5 per player; validates screenshot URL)
|
||
PUT /api/suggestions/{id} — update (non-admin: own suggestion; title locked after Suggest)
|
||
DELETE /api/suggestions/{id} — delete (non-admin only in Suggest; admin any time)
|
||
GET /api/suggestions/all — all suggestions (from Vote onward), includes author, link metadata
|
||
Suggestion limit is enforced in both app logic and DB trigger; concurrent writes that exceed limit return `400`.
|
||
|
||
## Votes (requires auth + Vote phase)
|
||
GET /api/votes/mine
|
||
POST /api/votes — upsert vote; if suggestion is in a linked group, applies the same score to all linked siblings
|
||
POST /api/votes/finalize — `{ final: bool }` toggles caller’s finalized status (blocks further vote edits when true)
|
||
Vote upsert includes conflict handling for concurrent writes against the unique `(PlayerId, SuggestionId)` index.
|
||
|
||
## Results (requires auth + Results phase + resultsOpen)
|
||
GET /api/results — leaderboard with totals, counts, averages, vote values, alphabetically sorted `voterNames`, caller’s vote, media/links, link metadata
|
||
|
||
## Admin (requires authenticated admin user)
|
||
POST /api/admin/results — `{ resultsOpen: bool }` locks/unlocks results and aligns player phases
|
||
GET /api/admin/vote-status — readiness overview (who finalized)
|
||
POST /api/admin/joker — `{ playerId }` grants a vote-phase joker to the target player
|
||
POST /api/admin/player-phase — `{ playerId, phase }`; currently supports Vote→Suggest transitions only
|
||
POST /api/admin/player-admin — `{ playerId, isAdmin }`; grant/revoke admin role for non-owner accounts
|
||
DELETE /api/admin/players/{playerId} — `{ password }`; deletes player account plus their suggestions/votes
|
||
POST /api/admin/link-suggestions — `{ sourceSuggestionId, targetSuggestionId }`; merges vote groups during Vote, clears votes in the linked group, unfinalizes **all** players
|
||
POST /api/admin/unlink-suggestions — `{ suggestionId }`; breaks links, clears votes for that group, unfinalizes **all** players
|
||
POST /api/admin/reset — `{ password }`; clear suggestions/votes, keep players, reset phases/vote-final flags
|
||
POST /api/admin/factory-reset — `{ password }`; wipe players, suggestions, votes, state
|
||
Owner restrictions: owner role/admin status cannot be changed, and owner account cannot be deleted.
|
||
|
||
## Security Defaults
|
||
- Mutating authenticated API requests (`POST`/`PUT`/`DELETE`/`PATCH`) enforce same-origin CSRF checks via `Origin`/`Referer`; cross-origin or missing-origin authenticated writes are rejected with `400`.
|
||
- Security headers are set on all responses (`CSP`, `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`).
|
||
- CSP is tightened to disallow inline styles and insecure image origins (`img-src` excludes `http:`).
|
||
- In production, HTTPS redirection and HSTS are enabled.
|
||
- Screenshot URL validation rejects private/reserved address ranges and pins outbound connections to validated public IPs.
|
||
- Password hashing is versioned with Argon2id as current; legacy hashes are transparently upgraded on successful login/admin password confirmation.
|