feat(web): add Hub OAuth client, signed session, and allowlist gate #11

Merged
David merged 1 commit from feat/hub-oauth-login-allowlist into main 2026-06-14 21:49:13 +02:00
Owner

Implements step 3 of the multi-user remote MCP epic: the upstream YouTrack Hub OAuth client and the user login / allowlist gate. No downstream authorization-server endpoints or HTTP wiring yet (those land in later steps), so the new web module is allow(dead_code) scaffolding the binary does not yet call, mirroring the crypto and db steps.

src/web/oauth_hub.rs: builds the Hub authorize URL with PKCE S256 and the offline scope, exchanges the callback code for Hub access + refresh tokens via the oauth2 crate (rustls-only reqwest), fetches identity from Hub users/me, and refreshes an expired Hub token on demand (retaining the prior refresh token when Hub omits a new one). complete_login applies the allowlist gate: a non-allowlisted identity is denied and no user row is created, while an allowlisted identity is upserted (with first-admin bootstrap via YT_MCP_ADMIN_HUB_LOGIN) and its Hub tokens stored encrypted.

src/web/session.rs: a short-lived HMAC-SHA256 signed-cookie browser session that carries the CSRF state and PKCE verifier across the Hub round-trip. HMAC is computed over the existing sha2 0.10 primitive to avoid a hmac crate that tracks a newer digest.

Adds the oauth2 (default features off, reqwest + rustls-tls) and reqwest (rustls-only) dependencies. Bootstrap config reads YT_MCP_HUB_ISSUER, YT_MCP_HUB_CLIENT_ID, YT_MCP_HUB_CLIENT_SECRET, YT_MCP_PUBLIC_BASE_URL, YT_MCP_ADMIN_HUB_LOGIN, and YT_MCP_HUB_SCOPE. Tests drive the code/refresh/identity exchanges against an in-process mock HTTP server and the gate against an in-memory SQLite database.

#YTMCP-5

Implements step 3 of the multi-user remote MCP epic: the upstream YouTrack Hub OAuth client and the user login / allowlist gate. No downstream authorization-server endpoints or HTTP wiring yet (those land in later steps), so the new `web` module is `allow(dead_code)` scaffolding the binary does not yet call, mirroring the crypto and db steps. `src/web/oauth_hub.rs`: builds the Hub authorize URL with PKCE S256 and the offline scope, exchanges the callback code for Hub access + refresh tokens via the `oauth2` crate (rustls-only reqwest), fetches identity from Hub `users/me`, and refreshes an expired Hub token on demand (retaining the prior refresh token when Hub omits a new one). `complete_login` applies the allowlist gate: a non-allowlisted identity is denied and no user row is created, while an allowlisted identity is upserted (with first-admin bootstrap via `YT_MCP_ADMIN_HUB_LOGIN`) and its Hub tokens stored encrypted. `src/web/session.rs`: a short-lived HMAC-SHA256 signed-cookie browser session that carries the CSRF state and PKCE verifier across the Hub round-trip. HMAC is computed over the existing `sha2` 0.10 primitive to avoid a `hmac` crate that tracks a newer `digest`. Adds the `oauth2` (default features off, `reqwest` + `rustls-tls`) and `reqwest` (rustls-only) dependencies. Bootstrap config reads `YT_MCP_HUB_ISSUER`, `YT_MCP_HUB_CLIENT_ID`, `YT_MCP_HUB_CLIENT_SECRET`, `YT_MCP_PUBLIC_BASE_URL`, `YT_MCP_ADMIN_HUB_LOGIN`, and `YT_MCP_HUB_SCOPE`. Tests drive the code/refresh/identity exchanges against an in-process mock HTTP server and the gate against an in-memory SQLite database. #YTMCP-5
feat(web): add Hub OAuth client, signed session, and allowlist gate
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 23s
de4ac6dad6
Implements step 3 of the multi-user remote MCP epic: the upstream YouTrack Hub OAuth client and the user login / allowlist gate. No downstream authorization-server endpoints or HTTP wiring yet (those land in later steps), so the new `web` module is `allow(dead_code)` scaffolding the binary does not yet call, mirroring the crypto and db steps.

`src/web/oauth_hub.rs`: builds the Hub authorize URL with PKCE S256 and the offline scope, exchanges the callback code for Hub access + refresh tokens via the `oauth2` crate (rustls-only reqwest), fetches identity from Hub `users/me`, and refreshes an expired Hub token on demand (retaining the prior refresh token when Hub omits a new one). `complete_login` applies the allowlist gate: a non-allowlisted identity is denied and no user row is created, while an allowlisted identity is upserted (with first-admin bootstrap via `YT_MCP_ADMIN_HUB_LOGIN`) and its Hub tokens stored encrypted.

`src/web/session.rs`: a short-lived HMAC-SHA256 signed-cookie browser session that carries the CSRF state and PKCE verifier across the Hub round-trip. HMAC is computed over the existing `sha2` 0.10 primitive to avoid a `hmac` crate that tracks a newer `digest`.

Adds the `oauth2` (default features off, `reqwest` + `rustls-tls`) and `reqwest` (rustls-only) dependencies. Bootstrap config reads `YT_MCP_HUB_ISSUER`, `YT_MCP_HUB_CLIENT_ID`, `YT_MCP_HUB_CLIENT_SECRET`, `YT_MCP_PUBLIC_BASE_URL`, `YT_MCP_ADMIN_HUB_LOGIN`, and `YT_MCP_HUB_SCOPE`. Tests drive the code/refresh/identity exchanges against an in-process mock HTTP server and the gate against an in-memory SQLite database.

#YTMCP-5
David merged commit 63d9a91c23 into main 2026-06-14 21:49:13 +02:00
David deleted branch feat/hub-oauth-login-allowlist 2026-06-14 21:49:13 +02:00
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-mcp!11
No description provided.