diff --git a/src/api/resources/webhooks/client/Client.ts b/src/api/resources/webhooks/client/Client.ts index 647660cb..80c64d50 100644 --- a/src/api/resources/webhooks/client/Client.ts +++ b/src/api/resources/webhooks/client/Client.ts @@ -8,7 +8,6 @@ import * as Webflow from "../../../index"; import urlJoin from "url-join"; import * as serializers from "../../../../serialization/index"; import * as errors from "../../../../errors/index"; -import crypto from "crypto"; export declare namespace Webhooks { interface Options { @@ -26,15 +25,6 @@ export declare namespace Webhooks { /** Additional headers to include in the request. */ headers?: Record; } - - interface RequestSignatureDetails { - /** The headers of the incoming webhook request as a record-like object */ - headers: Record; - /** The body of the incoming webhook request as a string */ - body: string; - /** The secret key generated when creating the webhook or the OAuth client secret */ - secret: string; - } } /** @@ -42,59 +32,6 @@ export declare namespace Webhooks { */ export class Webhooks { constructor(protected readonly _options: Webhooks.Options) {} - - /** - * Verify that the signature on the webhook message is from Webflow - * @link https://developers.webflow.com/data/docs/working-with-webhooks#validating-request-signatures - * - * @param {Webhooks.RequestSignatureDetails.headers} requestSignatureDetails - details of the incoming webhook request - * @example - * function incomingWebhookRouteHandler(req, res) { - * const headers = req.headers; - * const body = JSON.stringify(req.body); - * const secret = getWebhookSecret(WEBHOOK_ID); - * const isAuthenticated = await client.webhooks.verifySignature({ headers, body, secret }); - * - * if (isAuthenticated) { - * // Process the webhook - * } else { - * // Alert the user that the webhook is not authenticated - * } - * res.sendStatus(200); - * } - * - */ - public async verifySignature({ headers, body, secret }: Webhooks.RequestSignatureDetails): Promise { - // Creates a HMAC signature following directions from https://developers.webflow.com/data/docs/working-with-webhooks#steps-to-validate-the-request-signature - const createHmac = async (signingSecret: string, message: string) => { - const encoder = new TextEncoder(); - - // Encode the signingSecret key - // @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'. - const key = await crypto.subtle.importKey( - "raw", - encoder.encode(signingSecret), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"] - ); - - // Encode the message and compute HMAC signature - // @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'. - const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message)); - - // Convert signature to hex string - return Array.from(new Uint8Array(signature)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - }; - - const message = `${headers["x-webflow-timestamp"]}:${body}`; - - const generatedSignature = await createHmac(secret, message); - return headers["x-webflow-signature"] === generatedSignature; - } - /** * List all App-created Webhooks registered for a given site * diff --git a/src/wrapper/WebflowClient.ts b/src/wrapper/WebflowClient.ts index 4a69818c..341e9162 100644 --- a/src/wrapper/WebflowClient.ts +++ b/src/wrapper/WebflowClient.ts @@ -4,12 +4,20 @@ import { OauthScope } from "../api/types/OAuthScope"; import * as core from "../core"; import * as errors from "../errors"; import { SDK_VERSION } from "../version"; +import { Client as Webhooks } from "./WebhooksClient"; export class WebflowClient extends FernClient { constructor(protected readonly _options: FernClient.Options) { super(_options); } + protected _webhooks: Webhooks | undefined; + + public get webhooks(): Webhooks { + return (this._webhooks ??= new Webhooks(this._options)); + } + + /** * @param clientId The OAuth client ID * @param state The state diff --git a/src/wrapper/WebhooksClient.ts b/src/wrapper/WebhooksClient.ts new file mode 100644 index 00000000..18097801 --- /dev/null +++ b/src/wrapper/WebhooksClient.ts @@ -0,0 +1,74 @@ +import { Webhooks } from "../api/resources/webhooks/client/Client"; +import crypto from "crypto"; + +// Extends the namespace declared in the Fern generated client +declare module "../api/resources/webhooks/client/Client" { + export namespace Webhooks { + interface RequestSignatureDetails { + /** The headers of the incoming webhook request as a record-like object */ + headers: Record; + /** The body of the incoming webhook request as a string */ + body: string; + /** The secret key generated when creating the webhook or the OAuth client secret */ + secret: string; + } + } +} + +export class Client extends Webhooks { + constructor(protected readonly _options: Webhooks.Options) { + super(_options); + } + + /** + * Verify that the signature on the webhook message is from Webflow + * @link https://developers.webflow.com/data/docs/working-with-webhooks#validating-request-signatures + * + * @param {Webhooks.RequestSignatureDetails.headers} requestSignatureDetails - details of the incoming webhook request + * @example + * function incomingWebhookRouteHandler(req, res) { + * const headers = req.headers; + * const body = JSON.stringify(req.body); + * const secret = getWebhookSecret(WEBHOOK_ID); + * const isAuthenticated = await client.webhooks.verifySignature({ headers, body, secret }); + * + * if (isAuthenticated) { + * // Process the webhook + * } else { + * // Alert the user that the webhook is not authenticated + * } + * res.sendStatus(200); + * } + * + */ + public async verifySignature({ headers, body, secret }: Webhooks.RequestSignatureDetails): Promise { + // Creates a HMAC signature following directions from https://developers.webflow.com/data/docs/working-with-webhooks#steps-to-validate-the-request-signature + const createHmac = async (signingSecret: string, message: string) => { + const encoder = new TextEncoder(); + + // Encode the signingSecret key + // @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'. + const key = await crypto.subtle.importKey( + "raw", + encoder.encode(signingSecret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + + // Encode the message and compute HMAC signature + // @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'. + const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message)); + + // Convert signature to hex string + return Array.from(new Uint8Array(signature)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + }; + + const message = `${headers["x-webflow-timestamp"]}:${body}`; + + const generatedSignature = await createHmac(secret, message); + return headers["x-webflow-signature"] === generatedSignature; + } +} \ No newline at end of file