Skip to content

Commit

Permalink
move code to wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
KeithRyanWong committed Feb 6, 2025
1 parent 99005db commit 10717e1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 63 deletions.
63 changes: 0 additions & 63 deletions src/api/resources/webhooks/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,75 +25,13 @@ export declare namespace Webhooks {
/** Additional headers to include in the request. */
headers?: Record<string, string>;
}

interface RequestSignatureDetails {
/** The headers of the incoming webhook request as a record-like object */
headers: Record<string, string>;
/** 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;
}
}

/**
* Webhooks are the webhooks in your Webflow site.
*/
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<boolean> {
// 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
*
Expand Down
8 changes: 8 additions & 0 deletions src/wrapper/WebflowClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions src/wrapper/WebhooksClient.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>;
/** 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<boolean> {
// 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;
}
}

0 comments on commit 10717e1

Please sign in to comment.