T20: connectors screen (services, disconnect, MCP clients) #19

Merged
dzenda merged 3 commits from t20-connectors into t19-app-shell 2026-11-14 17:49:56 +01:00
Owner

T20: Connectors screen

Stacked on #18 (t19-app-shell) → … → #1. Merge those first.

What

  • FIT ČVUT card:
    • Connection status with account and kind. Reconnect re-runs the FIT sign-in and returns to /connectors. Disconnect deletes the encrypted tokens after a confirm prompt.
    • Per-service availability (Usermap, Sirius, Grades, Marast), derived from the token's aud via check_token (servicesFromAudience). A service that's off shows "not enabled for this FIT app" with the fix: an admin enables it at auth.fit.cvut.cz/manager, then the user signs in again. Live on the dev account: Usermap ✔, Sirius ✔, Marast ✔, Grades ✘, which matches the 403 we saw in T7.
  • GitLab card: status, "Connect / Reconnect GitLab" when OAuth is configured, the PAT form, and Disconnect. OAuth tokens are also revoked at GitLab (/oauth/revoke, best effort). For a PAT, the page says to delete it in GitLab too.
  • AI assistants with access: the user's authorized MCP clients from workers-oauth-provider grants (name and approval date, with all pages followed), each with a Revoke button. A loading failure shows an error, never "none connected".
  • API (src/server/connections-manage.ts), both with session + same-origin Origin check:
    • POST /api/connections/disconnect (provider=fit|gitlab).
    • POST /api/mcp-clients/revoke (grantId). Only the user's own grants can be revoked: ownership is checked across all pages, and revokeGrant(grantId, userId) is keyed by user.
  • Status banners after each action, plus the GitLab connect results from T17. Full en/cs strings. Dates are formatted in Europe/Prague so server and browser render the same.
  • checkToken now exposes audience. revokeGitlabToken was added.
  • Loader data is token-free: account, kind, scopes, service flags and client names only.

Verified

  • bun run lint && bun run typecheck && bun run test: 241 tests, 10 of them new for management. They cover the service map (live aud shape, unknown → null); disconnect for FIT, GitLab OAuth (revoke called, even when the network fails) and PAT (no GitLab call); cross-origin, anonymous and invalid provider; listing MCP clients; revoking only your own grants; pagination for list and ownership; bad or cross-origin revokes.
  • Live: servicesFromAudience on your real token gives { usermap: true, sirius: true, grades: false, marast: true }.
  • bun run dev + curl: anonymous /connectors → 307 to login; anonymous disconnect and revoke → login redirect.

Astra review (gpt-6-astra)

It confirmed that session-derived ownership, the Origin checks and token-free loader data are sound.

Fixed:

  1. (P2) Only the first page of grants was listed and checked for ownership → follows the cursor (test with one grant per page).
  2. (P2) A grant-loading failure looked like "no assistants" → localised error state.

🤖 Generated with Claude Code

## T20: Connectors screen **Stacked on #18 (`t19-app-shell`) → … → #1.** Merge those first. ### What - **FIT ČVUT card:** - Connection status with account and kind. **Reconnect** re-runs the FIT sign-in and returns to `/connectors`. **Disconnect** deletes the encrypted tokens after a confirm prompt. - **Per-service availability** (Usermap, Sirius, Grades, Marast), derived from the token's `aud` via `check_token` (`servicesFromAudience`). A service that's off shows "not enabled for this FIT app" with the fix: an admin enables it at `auth.fit.cvut.cz/manager`, then the user signs in again. **Live on the dev account:** Usermap ✔, Sirius ✔, Marast ✔, Grades ✘, which matches the 403 we saw in T7. - **GitLab card:** status, "Connect / Reconnect GitLab" when OAuth is configured, the PAT form, and Disconnect. OAuth tokens are also **revoked at GitLab** (`/oauth/revoke`, best effort). For a PAT, the page says to delete it in GitLab too. - **AI assistants with access:** the user's authorized MCP clients from `workers-oauth-provider` grants (name and approval date, with all pages followed), each with a **Revoke** button. A loading failure shows an error, never "none connected". - **API** (`src/server/connections-manage.ts`), both with session + same-origin `Origin` check: - `POST /api/connections/disconnect` (`provider=fit|gitlab`). - `POST /api/mcp-clients/revoke` (`grantId`). **Only the user's own grants** can be revoked: ownership is checked across all pages, and `revokeGrant(grantId, userId)` is keyed by user. - Status banners after each action, plus the GitLab connect results from T17. Full en/cs strings. Dates are formatted in Europe/Prague so server and browser render the same. - `checkToken` now exposes `audience`. `revokeGitlabToken` was added. - **Loader data is token-free:** account, kind, scopes, service flags and client names only. ### Verified - `bun run lint && bun run typecheck && bun run test`: 241 tests, 10 of them new for management. They cover the service map (live `aud` shape, unknown → null); disconnect for FIT, GitLab OAuth (revoke called, even when the network fails) and PAT (no GitLab call); cross-origin, anonymous and invalid provider; listing MCP clients; **revoking only your own grants**; pagination for list and ownership; bad or cross-origin revokes. - Live: `servicesFromAudience` on your real token gives `{ usermap: true, sirius: true, grades: false, marast: true }`. - `bun run dev` + curl: anonymous `/connectors` → 307 to login; anonymous disconnect and revoke → login redirect. ### Astra review (gpt-6-astra) It confirmed that session-derived ownership, the Origin checks and token-free loader data are sound. **Fixed:** 1. *(P2)* Only the first page of grants was listed and checked for ownership → follows the cursor (test with one grant per page). 2. *(P2)* A grant-loading failure looked like "no assistants" → localised error state. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dzenda merged commit 140abf13b7 into t19-app-shell 2026-11-14 17:49:56 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
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
dzenda/os!19
No description provided.