Skip to content

feat(auth-jwt-substrate): JWT session machinery, middleware, revocation#20

Merged
themightychris merged 5 commits into
mainfrom
feat/auth-jwt-substrate
May 16, 2026
Merged

feat(auth-jwt-substrate): JWT session machinery, middleware, revocation#20
themightychris merged 5 commits into
mainfrom
feat/auth-jwt-substrate

Conversation

@themightychris
Copy link
Copy Markdown
Member

Summary

  • Implements JWT issuance, validation, refresh, and revocation for stateless session management per specs/behaviors/authorization.md and specs/api/auth.md
  • Adds session middleware that decorates every request with request.session (person, accountLevel, personId, jti) via onRequest hook
  • Implements GET /api/auth/me, POST /api/auth/refresh, POST /api/auth/logout, GET /api/auth/sessions, POST /api/auth/sessions/:jti/revoke
  • OAuth endpoints (/api/auth/github/start, /api/auth/github/callback) return 501 oauth_not_yet_wired for the github-oauth plan to implement
  • Revocation is two-layer: in-memory Set<jti> (O(1) hot path) + persisted revocations gitsheet (survives restarts); periodic sweeper clears expired records
  • Session metadata (UA + IP + timestamps) stored in private bucket session-metadata.json — never in the public commit log
  • Rate-limit plugin updated to use account-based keys when session is present (300/min reads, 30/min writes per account)
  • Extends private store interface with readBlob/writeBlob for non-record arbitrary storage

Test plan

  • mintSessionFor(personId) issues valid access + refresh JWTs that the verifier accepts
  • cfp_claim token not accepted by verifyAccess (scope enforcement)
  • GET /api/auth/me with valid cfp_session returns person + accountLevel
  • GET /api/auth/me with no cookie returns {person:null, accountLevel:'anonymous'} 200
  • Expired access JWT → anonymous (ME never 401s)
  • POST /api/auth/refresh with valid refresh JWT returns new pair; old jti revoked
  • POST /api/auth/refresh with revoked refresh JWT → 401 refresh_token_revoked
  • POST /api/auth/logout clears cookies; subsequent /api/auth/me returns anonymous
  • GET /api/auth/sessions lists non-revoked sessions; current marked current:true
  • POST /api/auth/sessions/:jti/revoke with current session → 409 cannot_revoke_current_session
  • POST /api/auth/sessions/:jti/revoke with unknown jti → 404
  • OAuth endpoints return 501 oauth_not_yet_wired
  • Authenticated reads key on account bucket (300/min), separate from IP bucket

🤖 Generated with Claude Code

JWT primitives (issue/verify for access, refresh, claim-pending tokens),
cookie helpers, in-memory revocation store + gitsheets persistence,
session metadata in private bucket, session middleware (onRequest hook),
requireAuth guards, and auth routes (me, refresh, logout, sessions,
sessions/:jti/revoke, OAuth stubs returning 501).

Also updates rate-limit plugin to use account-based keys when
request.session.person is set (300/min reads, 30/min writes per account),
and extends the private store interface with readBlob/writeBlob for
non-record arbitrary blob storage.
Tests cover all 12 validation criteria from the plan:
- mintSessionFor issues valid access + refresh JWTs
- cfp_claim token rejected by verifyAccess (scope enforcement)
- GET /api/auth/me anonymous and authenticated paths
- Expired access JWT → anonymous (not 401)
- POST /api/auth/refresh: no-cookie, expired, revoked, and valid cases
- POST /api/auth/logout: cookie clearing and subsequent-me-returns-anonymous
- GET /api/auth/sessions: 401 unauthenticated, session list with current:true
- POST /api/auth/sessions/:jti/revoke: 409 and 404 cases
- OAuth stubs return 501 oauth_not_yet_wired
- Account-based rate limit keys separate from IP bucket
@themightychris themightychris force-pushed the feat/auth-jwt-substrate branch from 5a555dd to 653e3fb Compare May 16, 2026 21:03
@themightychris themightychris merged commit 4fac008 into main May 16, 2026
1 check passed
@themightychris themightychris deleted the feat/auth-jwt-substrate branch May 16, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant