--- id: session-security title: A session is only as alive as the grant behind it status: open repos: [headquarters] dependsOn: [sign-in] exitCriterion: > Revoking lance.blue's grant from the player's own PDS signs them out here, and the login path cannot be used to make someone else's server work for us. --- # session-security These were written down as requirements before sign-in was built, so that [sign-in](complete/sign-in.md) was built against them rather than rediscovering them. None of them were answered. Two related items from elsewhere in the old TODO join them. The shape of the problem is that a session cookie here and a grant on the player's PDS are two different facts, and only the second one is authoritative. - [ ] **A revoked grant must not look signed-in forever.** Decide where the authoritative check belongs — probably the first call that actually uses the token, since that is the call that finds out anyway. - [ ] **Rate-limit login.** Each attempt resolves a handle and makes a pushed authorization request against someone else's server. That is somebody else's infrastructure being used by an unauthenticated caller. - [ ] **Garbage-collect sessions.** Abandoned authorization state and never-returning accounts both. - [ ] **An OAuth-exchange test.** Needs a fake authorization server; without one the exchange is only ever tested by hand. This is the largest single piece of untested code in the service. - [ ] **CSRF beyond CORS.** `/api/login` is JSON-only with a strict origin allow-list, which forces a preflight and is currently the whole defence. If any endpoint ever accepts a form post, that stops being enough. - [ ] **Rate-limit `POST /api/flare`.** Nothing stops a signed-in player filling the board. Tracked in [flare](flare.md) too; whichever epic gets a rate limiter first should give the other one the same seam. ## Already answered, in sign-in Key rotation and handle re-verification were part of the same list and are done. Both are described in [sign-in](complete/sign-in.md), and neither has been exercised against the real event it exists for. ## Done Nothing in this epic's own list. Two items from the same original requirements set were answered in [sign-in](complete/sign-in.md) and are recorded there: - [x] **Key rotation**, as a keyset in `PRIVATE_KEY_JWK` with every public half served from `/.well-known/jwks.json`. Never exercised against a real rotation. - [x] **Re-verifying handles**, behind the answer `/api/session` already gave, claimed with a compare-and-swap so only one request does it. Never exercised against a real rename.