# Migration: git-on-cloudflare → tessera **Apply this in a separate PR against `~/code/git-on-cloudflare` after tessera is live and validated.** --- ## Scope: web admin only git-on-cloudflare has two auth systems. This migration only touches the second. | System | What it gates | Stays unchanged? | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | **A — git Basic-auth** | `git push` over HTTPS via `POST /:owner/:repo/git-receive-pack`. Token-based machine credentials in `AuthDurableObject`, PBKDF2-hashed. | **Yes — entirely as-is.** The `git push` flow continues to use Basic-auth tokens; tessera is irrelevant to it. | | **B — Web admin UI** | `/:owner/:repo/admin*`, `/:owner/admin/registry*`, `/auth/api/users`, plus repo-admin operations: `refs`, `head`, `debug-*`, `pack/*`, `purge`. | **No — moves behind tessera OIDC.** | There is no D1 user table in git-on-cloudflare today, so there is no per-user data to migrate. Identity becomes "the tessera `sub` from a verified ID token cookie or bearer." --- ## Code changes ### 1. New env / secrets `wrangler.jsonc` `vars`: ```jsonc "TESSERA_OIDC_ISSUER": "https://auth.limic.dev", "TESSERA_OIDC_CLIENT_ID": "", "OPERATOR_SUB": "" ``` `wrangler secret put TESSERA_OIDC_CLIENT_SECRET`. `AUTH_ADMIN_TOKEN` (the existing wrangler secret backing the `/auth/api/users` Bearer path) is **removed** at the end of this migration. ### 2. New `requireOidcSession` middleware Create `src/auth/oidc.ts`: ```ts import { jwtVerify, createRemoteJWKSet } from "jose"; let cachedJwks: ReturnType | null = null; const getJwks = (env: Env) => { if (!cachedJwks) cachedJwks = createRemoteJWKSet(new URL(`${env.TESSERA_OIDC_ISSUER}/api/auth/jwks`)); return cachedJwks; }; export const requireOidcSession = async (request: Request, env: Env): Promise<{ sub: string }> => { const cookie = request.headers.get("cookie") ?? ""; const idToken = parseCookie(cookie, "tessera_id_token") ?? bearerToken(request); if (!idToken) throw redirectToTesseraSignIn(request, env); const { payload } = await jwtVerify(idToken, getJwks(env), { issuer: env.TESSERA_OIDC_ISSUER }); if (payload.sub !== env.OPERATOR_SUB) throw new Response("forbidden", { status: 403 }); return { sub: payload.sub }; }; ``` ### 3. Replace the existing admin auth check Every call site that currently does `verifyAuth(env, owner, request, true)` for admin routes: | File | Line | Change | | ---------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `src/routes/ui/adminPage.ts` | `:27` | `await requireOidcSession(request, env)` | | `src/routes/admin.ts` | `:36-344` (each handler) | replace with `await requireOidcSession(request, env)` | | `src/routes/auth.ts` | `:30, :50, :84` (the `/auth/api/users` Bearer-admin handlers) | replace `getBearerToken` + `stub.adminAuthorizeOrRateLimit()` with `await requireOidcSession(request, env)` | The git Basic-auth path (`src/auth/verify.ts`) is unchanged. ### 4. Add OIDC callback route The web admin UI needs a place to land after tessera signs the user in. Add: - `GET /auth/start` — redirects to tessera's `/api/auth/oauth2/authorize` (PKCE). - `GET /auth/callback` — exchanges the code for an ID token, sets a `tessera_id_token` httpOnly cookie, redirects to the original `?next=` parameter. Sketch is identical to the anvil migration's § 2 — see [`anvil.md`](./anvil.md). ### 5. Remove `AUTH_ADMIN_TOKEN` After the cutover: - `wrangler secret delete AUTH_ADMIN_TOKEN`. - Remove the `adminAuthorizeOrRateLimit` path from `src/do/auth/authDO.ts:274-323` and the corresponding wrangler secret reference. --- ## What stays unchanged - All git Basic-auth routes (System A) and the entire `AuthDurableObject` (token storage, PBKDF2 hashing, `/auth/api/users` token-list operation — though the gate moves from Bearer to OIDC). - Public read routes (`/`, `/:owner`, `/:owner/:repo`, `/tree`, `/blob`, `/commits`, `/commit/:oid`). - Repo metadata SQLite schema in `src/do/repo/db/schema.ts`. --- ## No D1 migration git-on-cloudflare has no users table. There is nothing to add a `tessera_sub` column to, and nothing to backfill. The first sign-in via tessera is also the first authenticated admin session — there is no historical row to bind.