70 lines
3.2 KiB
Markdown
70 lines
3.2 KiB
Markdown
# intervalsicu-mcp-auth
|
|
|
|
The **OAuth 2.1 / OIDC authorization server** (identity provider) for the self-hosted, multi-tenant
|
|
Intervals.icu MCP product. Built on [Better Auth](https://better-auth.com) (TypeScript, plain Node
|
|
HTTP server), served at `https://intervalsicu.farhoodlabs.com/api/auth`.
|
|
|
|
## Role in the system
|
|
|
|
Three services share one host and one CloudNativePG Postgres database:
|
|
|
|
- **intervalsicu-mcp** — Python/FastMCP MCP server at `/mcp`. Verifies this server's EdDSA-signed
|
|
JWT access tokens against its JWKS, then resolves per-user Intervals.icu credentials by the token
|
|
`sub`.
|
|
- **intervalsicu-mcp-ui** — Python portal at `/portal`. An OIDC client of this auth server.
|
|
- **intervalsicu-mcp-auth** (this repo) — the authorization server both depend on.
|
|
|
|
Users sign in with **Google** (Apple optional). This server:
|
|
|
|
- Provides **Dynamic Client Registration** (RFC 7591) so Claude's MCP connector self-registers.
|
|
- Uses **PKCE**.
|
|
- Issues **EdDSA-signed JWT access tokens** with a JWKS endpoint, so the MCP server verifies them
|
|
RFC 9068 style (with `aud` = the requested `resource`).
|
|
- The token `sub` is this service's user id — the shared identity the MCP server and portal both key
|
|
per-user Intervals credentials on.
|
|
|
|
## Layout
|
|
|
|
- `src/auth.ts` — Better Auth config: `jwt()` + `oauthProvider()` plugins, Google/Apple social
|
|
providers (env-gated), shared `pg.Pool`.
|
|
- `src/server.ts` — Node HTTP server; custom `/login`, `/consent`, `/healthz` routes, everything
|
|
else delegated to Better Auth.
|
|
- `src/migrate.ts` — applies Better Auth's schema and seeds the portal OIDC client (run as a k8s
|
|
initContainer).
|
|
|
|
See [`CLAUDE.md`](./CLAUDE.md) for the detailed architecture (why `jwt()` comes before
|
|
`oauthProvider()`, why `validAudiences` matters, and why `/login` and `/consent` are custom routes).
|
|
|
|
## Development
|
|
|
|
```bash
|
|
npm ci # install
|
|
npm run typecheck # tsc --noEmit (the CI gate; there are no tests)
|
|
npm run build # tsc -> dist/
|
|
npm run dev # tsx watch src/server.ts (hot reload)
|
|
npm run migrate # apply schema + seed the portal client
|
|
npm start # node dist/server.js
|
|
```
|
|
|
|
## Key environment variables
|
|
|
|
| Var | Notes |
|
|
| --- | --- |
|
|
| `BETTER_AUTH_URL` | **required** — public base URL. |
|
|
| `BETTER_AUTH_SECRET` | **required** — signing secret. |
|
|
| `AUTH_DATABASE_URL` | **required** — Postgres (shared CNPG DB). |
|
|
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | enable Google login. |
|
|
| `APPLE_CLIENT_ID` / `APPLE_CLIENT_SECRET` | enable Apple login (optional). |
|
|
| `VALID_AUDIENCES` | comma-separated allowed token audiences / RFC 8707 resources (has default). |
|
|
| `TRUSTED_ORIGINS` | comma-separated trusted origins. |
|
|
| `LOG_LEVEL` | `debug` / `info` (default `info`). |
|
|
| `PORT` | HTTP port (default `8080`). |
|
|
| `PORTAL_CLIENT_ID` / `PORTAL_REDIRECT_URI` | portal client seeded by `migrate.ts` (have defaults). |
|
|
|
|
## Deployment
|
|
|
|
Push to `main` triggers Gitea CI (`git.farh.net`): typecheck, then build and push the Docker image
|
|
to `git.farh.net/farhoodlabs/intervalsicu-mcp-auth`. Flux (GitOps) deploys it on Kubernetes, running
|
|
`node dist/migrate.js` as an initContainer. Served under `/api/auth` (+ `/login`, `/consent`) behind
|
|
the cluster gateway.
|