forgejo-mcp-broker/internal
Ole-Morten Duesund d16b18ea38 feat(oauth): authorization-server endpoints (forgejo-mcp-broker-pur)
Implements internal/oauth, the broker's OAuth 2.1 AS surface that
Claude.ai (and other MCP clients) talk to. User authentication is
delegated to upstream Forgejo via internal/forgejo.

Endpoints:
  POST /oauth/register   — RFC 7591 dynamic client registration
  GET  /oauth/authorize  — RFC 6749 + 7636 PKCE (S256 only)
  GET  /oauth/callback   — Forgejo redirects back here after consent
  POST /oauth/token      — authorization_code + refresh_token grants
  POST /oauth/revoke     — RFC 7009

Security model:
- PKCE required, S256 only — plain method rejected per OAuth 2.1
- Every broker-issued access/refresh token stored as hex(sha256(plain));
  plaintext leaves the broker exactly once in the /token response body
- Refresh-token rotation: each refresh issues a new token pair and
  revokes the old refresh (RFC 6749 §10.4)
- Auth-code single-use enforced atomically via UPDATE...WHERE used_at IS
  NULL with rows-affected check, blocking the concurrent-replay race
- Issuer URL sourced from cfg.Issuer at construction time, never from
  inbound headers — prevents host-header injection on /.well-known
  metadata that ships in 2d
- redirect_uri must match a registered URI exactly (no prefix/wildcard)

Pending-authorization state (between /authorize and /callback) lives in
an in-memory sync.Map with a 1-minute reaper goroutine. A broker restart
drops them, forcing the user to re-authorize — acceptable trade-off
versus introducing a fifth table.

Tests: 81.0% coverage with ~20 cases across happy paths, every required-
field error, PKCE failure, code-replay, refresh expiry/revocation,
client-id and redirect-uri mismatches, Forgejo-side errors, and the
reaper logic itself (internal test).

Closes forgejo-mcp-broker-pur. The OAuth keystone is in place; phase 2c
unblocks discovery (2d) and security review (2e), and combined with the
existing supervisor + bridge it unblocks the session glue work in
phase 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:04:34 +02:00
..
bridge test(bridge): integration test against real forgejo-mcp (forgejo-mcp-broker-xot) 2026-04-27 16:28:32 +02:00
buildinfo feat: bootstrap Go project layout (forgejo-mcp-broker-n84) 2026-04-24 16:54:27 +02:00
config feat(config): flag + env parsing with validation (forgejo-mcp-broker-9nq) 2026-04-24 17:10:18 +02:00
forgejo feat(forgejo): upstream OAuth client (forgejo-mcp-broker-b9i) 2026-04-27 13:31:19 +02:00
httpserver feat(httpserver,log): /healthz, graceful shutdown, slog constructor 2026-04-24 17:26:32 +02:00
log feat(httpserver,log): /healthz, graceful shutdown, slog constructor 2026-04-24 17:26:32 +02:00
oauth feat(oauth): authorization-server endpoints (forgejo-mcp-broker-pur) 2026-04-27 17:04:34 +02:00
store feat(store): OAuth tables migration (forgejo-mcp-broker-cpb) 2026-04-27 13:28:12 +02:00
supervisor test(supervisor): stress tests for FD/goroutine/zombie leaks (forgejo-mcp-broker-31t) 2026-04-27 16:04:34 +02:00