import { createHmac, timingSafeEqual } from "node:crypto";

/** Pass the original request bytes, before JSON parsing. Keep the secret server-side. */
export function verifyWebhook(
	rawBody: Buffer,
	headers: Headers,
	secret: string,
	nowSeconds = Math.floor(Date.now() / 1000),
): string {
	const id = headers.get("webhook-id") ?? "";
	const timestamp = headers.get("webhook-timestamp") ?? "";
	const signatures = headers.get("webhook-signature") ?? "";
	if (
		!/^[A-Za-z0-9_-]{1,100}$/.test(id) ||
		!/^\d{1,12}$/.test(timestamp) ||
		Math.abs(nowSeconds - Number(timestamp)) > 300 ||
		!/^whsec_[A-Za-z0-9+/]{43}=$/.test(secret)
	)
		throw new Error("Invalid webhook");
	const expected = createHmac("sha256", Buffer.from(secret.slice(6), "base64"))
		.update(`${id}.${timestamp}.`)
		.update(rawBody)
		.digest();
	const valid = signatures.split(" ").some((signature) => {
		const [version, encoded] = signature.split(",");
		if (version !== "v1" || !encoded || !/^[A-Za-z0-9+/]{43}=$/.test(encoded))
			return false;
		const received = Buffer.from(encoded, "base64");
		return (
			received.length === expected.length && timingSafeEqual(received, expected)
		);
	});
	if (!valid) throw new Error("Invalid webhook");
	return id;
}
