UNPKG

standardwebhooks

Version:
106 lines 4.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Webhook = exports.WebhookVerificationError = void 0; const timing_safe_equal_1 = require("./timing_safe_equal"); const base64 = require("@stablelib/base64"); const sha256 = require("fast-sha256"); const WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; class ExtendableError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, ExtendableError.prototype); this.name = "ExtendableError"; this.stack = new Error(message).stack; } } class WebhookVerificationError extends ExtendableError { constructor(message) { super(message); Object.setPrototypeOf(this, WebhookVerificationError.prototype); this.name = "WebhookVerificationError"; } } exports.WebhookVerificationError = WebhookVerificationError; class Webhook { constructor(secret, options) { if (!secret) { throw new Error("Secret can't be empty."); } if ((options === null || options === void 0 ? void 0 : options.format) === "raw") { if (secret instanceof Uint8Array) { this.key = secret; } else { this.key = Uint8Array.from(secret, (c) => c.charCodeAt(0)); } } else { if (typeof secret !== "string") { throw new Error("Expected secret to be of type string"); } if (secret.startsWith(Webhook.prefix)) { secret = secret.substring(Webhook.prefix.length); } this.key = base64.decode(secret); } } verify(payload, headers_) { const headers = {}; for (const key of Object.keys(headers_)) { headers[key.toLowerCase()] = headers_[key]; } const msgId = headers["webhook-id"]; const msgSignature = headers["webhook-signature"]; const msgTimestamp = headers["webhook-timestamp"]; if (!msgSignature || !msgId || !msgTimestamp) { throw new WebhookVerificationError("Missing required headers"); } const timestamp = this.verifyTimestamp(msgTimestamp); const computedSignature = this.sign(msgId, timestamp, payload); const expectedSignature = computedSignature.split(",")[1]; const passedSignatures = msgSignature.split(" "); const encoder = new globalThis.TextEncoder(); for (const versionedSignature of passedSignatures) { const [version, signature] = versionedSignature.split(","); if (version !== "v1") { continue; } if ((0, timing_safe_equal_1.timingSafeEqual)(encoder.encode(signature), encoder.encode(expectedSignature))) { return JSON.parse(payload.toString()); } } throw new WebhookVerificationError("No matching signature found"); } sign(msgId, timestamp, payload) { if (typeof payload === "string") { } else if (payload.constructor.name === "Buffer") { payload = payload.toString(); } else { throw new Error("Expected payload to be of type string or Buffer."); } const encoder = new TextEncoder(); const timestampNumber = Math.floor(timestamp.getTime() / 1000); const toSign = encoder.encode(`${msgId}.${timestampNumber}.${payload}`); const expectedSignature = base64.encode(sha256.hmac(this.key, toSign)); return `v1,${expectedSignature}`; } verifyTimestamp(timestampHeader) { const now = Math.floor(Date.now() / 1000); const timestamp = parseInt(timestampHeader, 10); if (isNaN(timestamp)) { throw new WebhookVerificationError("Invalid Signature Headers"); } if (now - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) { throw new WebhookVerificationError("Message timestamp too old"); } if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) { throw new WebhookVerificationError("Message timestamp too new"); } return new Date(timestamp * 1000); } } exports.Webhook = Webhook; Webhook.prefix = "whsec_"; //# sourceMappingURL=index.js.map