feat: Add Twake Token Manager — inter-service token broker#9
Open
feat: Add Twake Token Manager — inter-service token broker#9
Conversation
- Rewrite README with project description, features, table of contents, quick start guide, and troubleshooting section - Add CONTRIBUTING.md with guidelines for issues, PRs, and code style - Add AGPL-3.0 LICENSE file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Credit LINAGORA as Twake.ai developer in introduction - Replace " -- " dashes with colons throughout - Add ~20 GB disk space requirement in prerequisites Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- cozy_stack → drive_app - meet_app → visio_app - tmail_app → mail_app Update all references in wrapper.sh, docker-compose.yaml, .gitignore, README.md, and CLAUDE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Group DNS entries by service with comments for clarity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each component now uses a descriptive project name visible in Docker Desktop: twake-db, twake-auth, twake-drive, twake-visio, twake-calendar, twake-chat, twake-mail, twake-onlyoffice, twake-linshare. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace declare -A associative arrays with case-based lookup functions. macOS ships with Bash 3.2 which does not support associative arrays. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generate homeserver.yaml directly instead of homeserver-postgres.yaml and remove the redundant file-level volume mount. The directory mount (./synapse:/data) combined with the file mount was causing Docker to create an empty homeserver.yaml in the source directory, which then took precedence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design spec for the inter-service token broker covering v0.1 (MVP) and v0.2 (Token Umbrella). Defines architecture, data model, service connectors, API routes, BullMQ refresh, SDK/CLI, Next.js admin frontend, and Docker integration into the existing twake-workplace infrastructure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
24-task plan covering scaffolding, Prisma schema, crypto, 4 service connectors (Cozy PKCE, OIDC, Matrix), Fastify API with token/umbrella/proxy routes, BullMQ refresh worker, TypeScript SDK, CLI, Next.js admin frontend, and Docker Compose integration into the existing infrastructure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the ServiceConnector interface for Cozy Drive using OAuth2 PKCE. Registers per-user OAuth2 apps on Cozy instances, handles the full authorize/callback/refresh lifecycle, and includes unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements OidcBaseConnector (abstract) that uses the LemonLDAP OIDC token directly for authentication, with refresh/revoke via the OIDC issuer endpoints. TmailConnector and CalendarConnector extend it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements MatrixConnector for the twake-chat service, supporting token-based login via m.login.token, native Matrix refresh/logout endpoints, and full unit test coverage (9 tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements validateOidcToken and authHook with injectable verify function for testing. Extracts user info (sub, email, groups, isAdmin) from Bearer tokens and returns 401 on invalid/missing tokens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…logic Implements TokenService as core business logic between API routes and connectors/Prisma layer, with full TDD test coverage (5 test cases). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements opaque umbrella token lifecycle: raw token generation with twt_ prefix, SHA-256 hash for storage, DB-backed introspection with active/revoked/expired status, and hash-based revocation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 13: creates server.ts (buildApp export) that wires all
connectors, services, middleware hooks, and health route into a Fastify
app, plus the health.ts route returning {status, service}.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements all token management REST endpoints under /api/v1 in a protected Fastify plugin: create, refresh, list, detail, single and bulk revoke, plus admin routes for tenant token listing, config view/update, and paginated audit log querying. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements POST/DELETE umbrella token endpoints (protected scope), a transparent proxy route using umbrella token auth, and the public OAuth callback for Cozy Drive consent flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements background token auto-refresh using BullMQ + Valkey: getTokensNeedingRefresh queries expiring active tokens, processRefreshJob decrypts/refreshes/re-encrypts per token with audit logging, and startRefreshScheduler wires a cron-driven Queue+Worker into the Fastify server bootstrap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… found Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements TwakeTokenManager SDK class with thin fetch-based wrappers for all REST API endpoints, ConsentRequiredError / TwakeTokenManagerError classes, and full unit test coverage (8 tests) using vi.stubGlobal fetch mocking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements a thin CLI wrapper over the SDK with global options (--api-url, --token, --tenant, --format), token commands (create, list, status, refresh, revoke), umbrella subcommands, and admin subcommands (list-tokens, audit via direct fetch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Next.js App Router project skeleton: next.config.js (standalone output), tailwind.config.ts, tsconfig.json, postcss.config.js, globals.css, lib/api.ts (typed apiFetch wrapper), and lib/auth.ts (OIDC token store). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add root layout with sidebar nav, root redirect to /admin, StatsBar component (active/expired/umbrella counts), TokenTable with status badges and revoke/refresh actions, and the admin dashboard page that polls the API every 30s and drives both components. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…consent flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove old admin/user pages and legacy components (stats-bar, token-table, refresh-config, user-access-list). Add app/tokens/page.tsx with fetch, revoke, and refresh handlers wired to TokenList and CreateTokenDialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ivity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…API endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ulk revoke Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…client in LemonLDAP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matrix authenticate tests now expect the SSO redirect flow (type: 'redirect') instead of a direct login call. Tmail revoke test now expects no throw since revoke is best-effort (errors are silently caught). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Map access_token/umbrella_token from API to normalized token field for display. Handle 202 consent_required from service token endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r (removed in v2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add GET /umbrella-tokens endpoint to list user's umbrella tokens - My Tokens page fetches both service + umbrella tokens and merges them - Revoke handles umbrella tokens via DELETE /umbrella-token/:id - TokenItem now includes id field for umbrella revocation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The frontend sends the database ID (cuid) but the API expected the raw token (twt_...). Now tries revoke by ID first, falls back to raw token. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ce labels Umbrella tokens now show human-readable names (e.g., "TMail JMAP, Calendar CalDAV") instead of raw service IDs or blank. Also fix React key uniqueness for token rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…admin umbrella listing - Fix: dialog sends 'scopes' (not 'services') for umbrella creation - Add 'name' field to UmbrellaToken Prisma model — stored and displayed - Token list shows custom name when set, falls back to scope labels - Remove dark mode toggle from sidebar (light only) - Admin Users page: fetch and display umbrella tokens in expanded view Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ish Service/Umbrella Also add name, type, scopes fields to TokenData interface for umbrella display. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ella count, button styles - Admin: hide Refresh for umbrella tokens, revoke by ID with umbrella: prefix - Admin: expand button with Show/Hide label + arrow icon (more visible) - Admin: confirmation dialog before revoke - Dashboard: fetch umbrella tokens for correct Umbrella Tokens count - My Tokens: replace underlined links with bordered buttons for Refresh/Revoke Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rror message - New admin page /admin/audit with all users' audit entries and email filter - Add "Global Audit Log" to admin sidebar navigation - Improve refresh error message to explain re-authorization needed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d dialog Shows service endpoint, OIDC scope, and a ready-to-use curl command with the actual token. Dark terminal-style code block with copy button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…D in curl - Modal widened to 680px - Two tabs: Token (token + copy) and Usage (endpoint, scope, curl example) - YOUR_ACCOUNT_ID replaced with hex-encoded user email (JMAP accountId) - Curl example uses result.service to survive consent flow redirect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TMail JMAP uses SHA-256(email) as accountId, not hex-encoded email. Computed via Web Crypto API when entering display step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Token tab: "Done — I've copied it" (closes dialog) Usage tab: "Copy curl" → "✓ Curl copied!" on click (copies curl to clipboard) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…PI curl
Umbrella tokens cannot be used directly against service APIs — they must
go through the Token Manager proxy. The Usage tab now shows:
- Info banner explaining proxy requirement
- Curl example via /api/v1/proxy/{service}/... endpoint
- Lists other available services for multi-scope umbrellas
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Returns 409 Conflict if an active umbrella token with the same name already exists for the user. Only non-revoked tokens are checked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r type - Detect umbrella by twt_ prefix in token (survives state reset) - Use result.scopes instead of selectedScopes (survives consent flow) - Proxy URL correctly generated for all umbrella service types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each service gets a complete curl example via the proxy, including method, headers, and body (JMAP POST, CalDAV PROPFIND, etc.) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows 'An active umbrella token named "All" already exists' instead of raw JSON error response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Twake Token Manager — inter-service token broker for Twake Workplace. Centralizes OAuth2/OIDC token lifecycle management for all services behind a unified API with console-style token management UI.
What's included
Backend API (
token-manager-api.twake.local):4 Service Connectors (E2E tested with real tokens + browser consent):
Frontend v2 (
token-manager.twake.local) — cozy-ui inspired design:SDK + CLI included.
E2E Curl Validation
Known Limitations (next sprint)
Test plan
./wrapper.sh up -dcd token_manager && docker compose --env-file ../.env build && docker compose --env-file ../.env up -d/etc/hosts:127.0.0.1 token-manager.twake.local token-manager-api.twake.localcurl -sk https://token-manager-api.twake.local/healthhttps://token-manager.twake.local/tokens?dev_user=user1cd token_manager && npm test(81 tests)🤖 Generated with Claude Code