55 lines · 2.3 KB
import { verifyMirrorHook } from "./hook-auth";
import { enforceRateLimit, requestIp } from "../limits";
import { env } from "cloudflare:workers";
import { mirrorFlagKey, mirrorStub } from "./mirror-do";

export const MIRROR_HOOK_PATH = /^mirror\/([A-Za-z0-9_-]{1,64})$/;

const MAX_HOOK_BYTES = 1024 * 1024;
const HTTP_ACCEPTED = 202;
const HTTP_UNAUTHORIZED = 401;
const HTTP_TOO_LARGE = 413;

async function readCapped(request: Request): Promise<string | null> {
  if (Number(request.headers.get("content-length") ?? "0") > MAX_HOOK_BYTES) return null;
  if (!request.body) return "";
  const reader = request.body.getReader();
  const chunks: Uint8Array[] = [];
  let size = 0;
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    size += value.byteLength;
    if (size > MAX_HOOK_BYTES) {
      await reader.cancel().catch(() => undefined);
      return null;
    }
    chunks.push(value);
  }
  const merged = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) {
    merged.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return new TextDecoder().decode(merged);
}

/** `POST /integrations/hooks/mirror/<repoId>`: a push on the remote queues a coalesced mirror sync. */
export async function handleMirrorHook(request: Request, repoId: string): Promise<Response> {
  await enforceRateLimit("GIT_LIMITER", `mirror-hook:${repoId}:${requestIp(request)}`);