53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
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<string, unknown>).deploymentId) ||
|
|
!isDeploymentStatus((body as Record<string, unknown>).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);
|
|
}
|
|
}
|