import { createHash, timingSafeEqual } from "node:crypto"; import { publishDeploymentStatus } from "@/lib/deployment/repository"; import { isDeploymentId, isDeploymentStatus, } from "@/lib/deployment/status"; import { errorResponse, HttpError, readJson, } from "@/lib/security/http"; function authorized(request: Request): boolean { const secret = process.env.DEPLOYMENT_WEBHOOK_SECRET; const authorization = request.headers.get("authorization"); if (!secret) throw new HttpError(503, "deployment-webhook-not-configured"); if (!authorization?.startsWith("Bearer ")) return false; const supplied = authorization.slice("Bearer ".length); const expectedDigest = createHash("sha256").update(secret).digest(); const suppliedDigest = createHash("sha256").update(supplied).digest(); return timingSafeEqual(expectedDigest, suppliedDigest); } export async function POST(request: Request) { try { if (!authorized(request)) throw new HttpError(401, "unauthorized"); const body = await readJson(request, 1_024); if ( typeof body !== "object" || body === null || !isDeploymentId((body as Record).deploymentId) || !isDeploymentStatus((body as Record).status) ) { throw new HttpError(400, "invalid-deployment-status"); } const deployment = body as { deploymentId: string; status: "deploying" | "ready" | "failed"; }; const result = await publishDeploymentStatus( deployment.deploymentId, deployment.status, ); return Response.json(result, { status: 202, headers: { "Cache-Control": "no-store" }, }); } catch (cause) { return errorResponse(cause); } }