next-sanity
Version:
Sanity.io toolkit for Next.js
68 lines (67 loc) • 3.7 kB
JavaScript
var WebhookSignatureValueError = class extends Error {
type = "WebhookSignatureValueError";
statusCode = 401;
};
var WebhookSignatureFormatError = class extends Error {
type = "WebhookSignatureFormatError";
statusCode = 400;
};
function isSignatureError(error) {
return typeof error == "object" && error !== null && "type" in error && ["WebhookSignatureValueError", "WebhookSignatureFormatError"].includes(error.type);
}
const MINIMUM_TIMESTAMP = 16094592e5, SIGNATURE_HEADER_REGEX = /^t=(\d+)[, ]+v1=([^, ]+)$/, SIGNATURE_HEADER_NAME = "sanity-webhook-signature";
async function assertValidSignature(stringifiedPayload, signature, secret) {
const { timestamp } = decodeSignatureHeader(signature);
if (signature !== await encodeSignatureHeader(stringifiedPayload, timestamp, secret)) throw new WebhookSignatureValueError("Signature is invalid");
}
async function isValidSignature(stringifiedPayload, signature, secret) {
try {
return await assertValidSignature(stringifiedPayload, signature, secret), !0;
} catch (err) {
if (isSignatureError(err)) return !1;
throw err;
}
}
async function encodeSignatureHeader(stringifiedPayload, timestamp, secret) {
return `t=${timestamp},v1=${await createHS256Signature(stringifiedPayload, timestamp, secret)}`;
}
function decodeSignatureHeader(signaturePayload) {
if (!signaturePayload) throw new WebhookSignatureFormatError("Missing or empty signature header");
const [, timestamp, hashedPayload] = signaturePayload.trim().match(SIGNATURE_HEADER_REGEX) || [];
if (!timestamp || !hashedPayload) throw new WebhookSignatureFormatError("Invalid signature payload format");
return {
timestamp: parseInt(timestamp, 10),
hashedPayload
};
}
async function createHS256Signature(stringifiedPayload, timestamp, secret) {
if (typeof crypto > "u") throw new TypeError("The Web Crypto API is not available in this environment, either polyfill `globalThis.crypto` or downgrade to `@sanity/webhook@3` which uses the Node.js `crypto` module.");
if (!secret || typeof secret != "string") throw new WebhookSignatureFormatError("Invalid secret provided");
if (!stringifiedPayload) throw new WebhookSignatureFormatError("Can not create signature for empty payload");
if (typeof stringifiedPayload != "string") throw new WebhookSignatureFormatError("Payload must be a JSON-encoded string");
if (typeof timestamp != "number" || isNaN(timestamp) || timestamp < MINIMUM_TIMESTAMP) throw new WebhookSignatureFormatError("Invalid signature timestamp, must be a unix timestamp with millisecond precision");
const enc = new TextEncoder(), key = await crypto.subtle.importKey("raw", enc.encode(secret), {
name: "HMAC",
hash: "SHA-256"
}, !1, ["sign"]), signaturePayload = `${timestamp}.${stringifiedPayload}`, signature = await crypto.subtle.sign("HMAC", key, enc.encode(signaturePayload)), signatureArray = Array.from(new Uint8Array(signature));
return btoa(String.fromCharCode.apply(null, signatureArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function parseBody(req, secret, waitForContentLakeEventualConsistency = true) {
const signature = req.headers.get(SIGNATURE_HEADER_NAME);
if (!signature) {
console.error("Missing signature header");
return {
body: null,
isValidSignature: null
};
}
const body = await req.text();
const validSignature = secret ? await isValidSignature(body, signature, secret.trim()) : null;
if (validSignature !== false && waitForContentLakeEventualConsistency) await new Promise((resolve) => setTimeout(resolve, 3e3));
return {
body: body.trim() ? JSON.parse(body) : null,
isValidSignature: validSignature
};
}
export { parseBody };
//# sourceMappingURL=index.js.map