352 lines · 16.7 KB
import { env } from "cloudflare:workers";
import { safeDecode } from "~/lib/safe-decode";
import { requestCredential, scopedRepoPermission } from "../token-scope";
import type { RequestCredential, TokenGrant } from "../token-scope";
import { loadActiveUserById, toViewer } from "../auth";
import { accountDb, repoDb } from "../scoped-db";
import { randomToken, sha256Hex } from "../crypto";
import { HttpError } from "../errors";
import { callerKey, enforceRateLimit, limitWrite, requestIp } from "../limits";
import { assertVerifiedForWrites } from "../verified-writes";
import { hasPermission, loadRepo, loadRepoFor, repoReadOnlyReason } from "../repos";
import { clampLimit, createLock, deleteLock, listLocks, lockJson } from "./locks";
import { findObject, findObjects, linkObjects, openObject, storeObject } from "./objects";
import { isValidOid } from "./pointer";
import { assertStorageAvailable, billableBytes, chargeBandwidth, maxObjectBytes } from "./quota";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

const LFS_PATH = /^\/([^/]+)\/([^/]+?)(?:\.git)?\/info\/lfs\/(.+)$/;
const OBJECT_PATH = /^objects\/([0-9a-f]{64})(\/verify)?$/;
const UNLOCK_PATH = /^locks\/([a-z0-9]+)\/unlock$/;
const LFS_JSON = "application/vnd.git-lfs+json";
const MAX_JSON_BYTES = 1024 * 1024;
const MAX_BATCH_OBJECTS = 100;
const TRANSFER_TTL_SECONDS = 600;
const TRANSFER_HEADER = "x-gitfor-lfs-transfer";
const HTTP_UNAUTHORIZED = 401;
const BASIC_CHALLENGE = 'Basic realm="gitfor"';

type LfsRoute = { owner: string; repo: string; rest: string };
type Operation = "upload" | "download";
/**
 * Who the grant was issued to: the user id plus the token's scope descriptor (never token or password
 * material). Re-resolved on every transfer, so removed access stops working before the grant expires.
 */
type GrantIdentity = { userId: string | null; grant: TokenGrant | null };
type TransferGrant = { repoId: string; op: Operation; objects: Record<string, number>; identity: GrantIdentity };
type Caller = { viewer: Viewer | null; permission: Permission; repo: RepoRow; credential: RequestCredential };
type BatchObject = { oid: string; size: number };

export function matchLfsRoute(url: URL): LfsRoute | null {