import { beforeEach, describe, expect, mock, test } from "bun:test"; import type { RepoRow, Viewer } from "~/lib/types"; import type { RequestCredential } from "../token-scope"; class StubEntrypoint {} mock.module("cloudflare:workers", () => ({ env: { PUBLIC_URL: "https://forge.example.com" }, waitUntil: () => undefined, WorkflowEntrypoint: StubEntrypoint, DurableObject: StubEntrypoint, WorkerEntrypoint: StubEntrypoint, })); mock.module("../db", () => ({ first: async () => ({ ok: 1 }), all: async () => [], run: async () => ({ meta: { changes: 0 } }), batch: async () => [], stmt: () => ({}), chunks: (items: T[]) => [items], placeholders: (count: number) => Array(count).fill("?").join(", "), MAX_IN_LIST: 90, })); const consulted: string[] = []; let credential: RequestCredential | null = null; let scratchResult: string | null = "write"; const actualJob = await import("../ci/job-token"); mock.module("../ci/job-token", () => ({ ...actualJob, jobTokenAccess: async () => { consulted.push("job"); return { repoId: "repo", permissions: { contents: "write" } }; }, })); const actualScratch = await import("../sites/scratch-git"); mock.module("../sites/scratch-git", () => ({ ...actualScratch, scratchGitPermission: async () => { consulted.push("scratch"); return scratchResult; }, })); const actualScope = await import("../token-scope"); mock.module("../token-scope", () => ({ ...actualScope, tokenCredential: async () => credential })); const { scopedRepoPermission } = await import("../token-scope"); const { authorize } = await import("./proxy"); const live = { id: "repo", owner_id: "evil", visibility: "public", owner_disabled_at: null, owner_disabled_reason: null, hidden_at: null, } as unknown as RepoRow; const down = { ...live, owner_disabled_at: 5, owner_disabled_reason: "admin" } as RepoRow; const hidden = { ...live, hidden_at: 5 } as unknown as RepoRow; const bob: Viewer = { id: "bob", name: "bob", displayName: null, email: "b@x", isAdmin: false, emailVerified: true }; const deploy: RequestCredential = { viewer: null, grant: { kind: "deploy", tokenId: "d", repoId: "repo", permission: "write" }, }; const fine: RequestCredential = { viewer: bob, grant: { kind: "fine", tokenId: "f", allRepos: true, permissions: { contents: "write" } }, }; const request = new Request("https://forge.example.com/evil/app.git/info/refs?service=git-upload-pack"); beforeEach(() => { consulted.length = 0; credential = null; scratchResult = "write"; }); describe("taken-down and hidden repositories refuse every credential", () => { test("deploy tokens", async () => { expect(await scopedRepoPermission(live, deploy, "contents")).toBe("write"); expect(await scopedRepoPermission(down, deploy, "contents")).toBe("none"); expect(await scopedRepoPermission(hidden, deploy, "contents")).toBe("none"); }); test("fine-grained tokens, while site admins keep read", async () => { expect(await scopedRepoPermission(down, fine, "contents")).toBe("none"); expect( await scopedRepoPermission(down, { ...fine, viewer: { ...bob, isAdmin: true, emailVerified: true } }, "contents"), ).toBe("read"); }); test("CI job tokens and scratch push tokens are not even consulted", async () => { for (const repo of [down, hidden]) { const result = await authorize(request, repo, "read"); expect(result).toBeInstanceOf(Response); expect((result as Response).status).toBe(401); } expect(consulted).toEqual([]); }); test("control: a live repository accepts them", async () => { expect(await authorize(request, live, "write")).toMatchObject({ permission: "write" }); expect(consulted).toEqual(["scratch"]); scratchResult = null; expect(await authorize(request, live, "write")).toMatchObject({ permission: "write", jobToken: true }); expect(consulted).toEqual(["scratch", "scratch", "job"]); }); test("a deploy token over git is refused on a taken-down repository", async () => { credential = deploy; const result = await authorize(request, down, "read"); expect((result as Response).status).toBe(404); }); });