feat(web): add OAuth 2.1 AS, RS bearer middleware, per-user client #13
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/downstream-oauth-as-rs-middleware"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implement the downstream OAuth 2.1 Authorization Server, the Resource-Server bearer gate, and the per-request per-user YouTrack client so Claude can complete OAuth against this binary and call tools as the authenticated user. This is step 4 of the multi-user remote MCP server epic and consumes the step 1-3 scaffolding (transport flag + ClientSource seam, SQLite/crypto layer, Hub OAuth login + allowlist + signed session).
New src/web/oauth_as.rs hand-rolls the AS endpoints: the RFC 9728 protected-resource and RFC 8414 authorization-server discovery documents (S256-only, opaque bearer); RFC 7591 dynamic client registration; GET /authorize, which validates the client and enforces an exact registered + allowlisted redirect-URI match, drives the upstream Hub login when the browser is unauthenticated (resuming the in-flight request on return), and issues a single-use PKCE authorization code; GET /oauth/hub/callback, which completes the allowlist-gated Hub round-trip and resumes /authorize; and POST /token, which implements the authorization_code grant (PKCE verified, code burned atomically against replay) and the refresh_token grant (rotating refresh tokens, old token revoked). Issued access/refresh tokens are opaque, stored only as their SHA-256 hash, and bound to the issuing client and user; the audience is this resource by construction because the AS and RS are the same process and tokens are validated by local lookup, so Hub tokens are never forwarded to Claude.
New src/web/auth_mw.rs is the Resource-Server middleware: it strips and hashes the bearer, looks it up in access_tokens, and on a miss returns 401 with a WWW-Authenticate header carrying resource_metadata and the offline_access scope; on a hit it injects a Principal into the request extensions, which the rmcp Streamable HTTP service copies into each tool call's RequestContext.
src/web/mod.rs adds AppState and build_router, which mounts the rmcp StreamableHttpService at /mcp behind the bearer gate (DNS-rebinding guard anchored on the public host), exposes the OAuth and /healthz routes, and applies CORS, tracing, and a body limit; client_for_user loads the user's Hub token (refreshing if expired) and builds that user's YouTrack client. src/server.rs adds ClientSource::PerUser and an async resolve_client that reads the Principal from the request context, so every tool resolves the per-user client and two users act with their own YouTrack permissions through one server. src/main.rs boots the axum app for the http transport, and src/config.rs adds HttpConfig. Cargo.toml enables rmcp's transport-streamable-http-server feature and adds axum, tower, and tower-http.
#YTMCP-6
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com