Blob: docs/migrations/git-on-cloudflare.md
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:
"TESSERA_OIDC_ISSUER": "https://auth.limic.dev",
"TESSERA_OIDC_CLIENT_ID": "<minted in tessera /admin/clients>",
"OPERATOR_SUB": "<the tessera UUID for the limic operator — visible at /account in tessera>"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:
import { jwtVerify, createRemoteJWKSet } from "jose";
let cachedJwks: ReturnType<typeof createRemoteJWKSet> | 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 atessera_id_tokenhttpOnly cookie, redirects to the original?next=parameter.
Sketch is identical to the anvil migration's § 2 — see anvil.md.
5. Remove AUTH_ADMIN_TOKEN
After the cutover:
wrangler secret delete AUTH_ADMIN_TOKEN.- Remove the
adminAuthorizeOrRateLimitpath fromsrc/do/auth/authDO.ts:274-323and 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/userstoken-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.