Improve printful secret hash
This commit is contained in:
@@ -21,8 +21,9 @@ function printError(err: unknown): never {
|
|||||||
try {
|
try {
|
||||||
console.log("Registering printful webhook...");
|
console.log("Registering printful webhook...");
|
||||||
const printful = new PrintfulClient({
|
const printful = new PrintfulClient({
|
||||||
token: env.PRINTFUL_AUTH,
|
|
||||||
storeId: env.PRINTFUL_STORE_ID,
|
storeId: env.PRINTFUL_STORE_ID,
|
||||||
|
token: env.PRINTFUL_AUTH,
|
||||||
|
webhookSecret: env.PRINTFUL_WEBHOOK_SECRET
|
||||||
});
|
});
|
||||||
|
|
||||||
await printful.Webhooks.create(PRINTFUL_WEBHOOK_URL, [
|
await printful.Webhooks.create(PRINTFUL_WEBHOOK_URL, [
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ const validateEquality = (wValue: any, pValue: any): Validation => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const printful = new PrintfulClient({
|
const printful = new PrintfulClient({
|
||||||
token: env.PRINTFUL_AUTH,
|
|
||||||
storeId: env.PRINTFUL_STORE_ID,
|
storeId: env.PRINTFUL_STORE_ID,
|
||||||
|
token: env.PRINTFUL_AUTH,
|
||||||
|
webhookSecret: env.PRINTFUL_WEBHOOK_SECRET
|
||||||
});
|
});
|
||||||
|
|
||||||
const webflow = new WebflowClient({
|
const webflow = new WebflowClient({
|
||||||
@@ -70,11 +71,6 @@ for (const wProduct of wProducts) {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// if (!validateEquality(sku.fieldData.name, pVariant.name).isValid) {
|
|
||||||
// sku.fieldData.name = pVariant.name;
|
|
||||||
// await webflow.Products.Skus.update(wProduct.product.id, sku.id, sku);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const errors = validations.filter((v) => !v.isValid);
|
const errors = validations.filter((v) => !v.isValid);
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
++invalidCount;
|
++invalidCount;
|
||||||
|
|||||||
@@ -324,8 +324,8 @@ export const app = new Elysia()
|
|||||||
// WEBHOOKS
|
// WEBHOOKS
|
||||||
.post("/webhooks/printful", async ({ body, query }) => {
|
.post("/webhooks/printful", async ({ body, query }) => {
|
||||||
// https://webflow.com/integrations/printful
|
// https://webflow.com/integrations/printful
|
||||||
const verified = crypto.timingSafeEqual(Buffer.from(query.secret, "hex"), Buffer.from(env.PRINTFUL_WEBHOOK_SECRET, "hex"));
|
if (!s.Commerce.Printful.Util.verifySecret(query.secret))
|
||||||
if (!verified) throw status(400, "Invalid secret");
|
throw status(400, "Invalid secret");
|
||||||
|
|
||||||
const payload = body as Printful.Webhook.EventPayload;
|
const payload = body as Printful.Webhook.EventPayload;
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ export const app = new Elysia()
|
|||||||
}
|
}
|
||||||
}, { query: t.Object({ secret: t.String() }) })
|
}, { query: t.Object({ secret: t.String() }) })
|
||||||
.post("/webhooks/webflow", async ({ request, body }) => {
|
.post("/webhooks/webflow", async ({ request, body }) => {
|
||||||
if (!s.Commerce.Webflow.Util.verifyWebflowSignature(request, body))
|
if (!s.Commerce.Webflow.Util.verifySecret(request, body))
|
||||||
throw status(400, "Invalid signature");
|
throw status(400, "Invalid signature");
|
||||||
|
|
||||||
const payload = body as Webflow.Webhook.EventPayload;
|
const payload = body as Webflow.Webhook.EventPayload;
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ export const WOrderStatusSchema = t.Union([
|
|||||||
|
|
||||||
export class CommerceService {
|
export class CommerceService {
|
||||||
readonly Printful = new PrintfulClient({
|
readonly Printful = new PrintfulClient({
|
||||||
token: env.PRINTFUL_AUTH,
|
|
||||||
storeId: env.PRINTFUL_STORE_ID,
|
storeId: env.PRINTFUL_STORE_ID,
|
||||||
|
token: env.PRINTFUL_AUTH,
|
||||||
|
webhookSecret: env.PRINTFUL_WEBHOOK_SECRET
|
||||||
});
|
});
|
||||||
readonly Webflow = new WebflowClient({
|
readonly Webflow = new WebflowClient({
|
||||||
siteId: env.WEBFLOW_SITE_ID,
|
siteId: env.WEBFLOW_SITE_ID,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Printful } from "./util/types";
|
import type { Printful } from "./util/types";
|
||||||
import { type DeepPartial, type PagingOptions } from "./util/misc";
|
import { type DeepPartial, type PagingOptions } from "./util/misc";
|
||||||
import { RateLimitError, parseRetryAfterMs } from "@blade-and-brawn/domain";
|
import { RateLimitError, parseRetryAfterMs } from "@blade-and-brawn/domain";
|
||||||
|
import { timingSafeEqual } from "crypto";
|
||||||
|
|
||||||
const parsePayload = (res: Response): Promise<unknown> =>
|
const parsePayload = (res: Response): Promise<unknown> =>
|
||||||
res.json().catch(() => res.text().catch(() => undefined));
|
res.json().catch(() => res.text().catch(() => undefined));
|
||||||
@@ -8,6 +9,7 @@ const parsePayload = (res: Response): Promise<unknown> =>
|
|||||||
type ClientOptions = {
|
type ClientOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
storeId: string;
|
storeId: string;
|
||||||
|
webhookSecret: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Credentials = {
|
type Credentials = {
|
||||||
@@ -186,10 +188,24 @@ class WebhooksClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UtilClient {
|
||||||
|
constructor(private printfulSecret: string) { }
|
||||||
|
|
||||||
|
verifySecret(secret: string): boolean {
|
||||||
|
try {
|
||||||
|
return timingSafeEqual(Buffer.from(secret, "hex"), Buffer.from(this.printfulSecret, "hex"));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PrintfulClient {
|
export class PrintfulClient {
|
||||||
readonly Products: ProductsClient;
|
readonly Products: ProductsClient;
|
||||||
readonly Orders: OrdersClient;
|
readonly Orders: OrdersClient;
|
||||||
readonly Webhooks: WebhooksClient;
|
readonly Webhooks: WebhooksClient;
|
||||||
|
readonly Util: UtilClient;
|
||||||
|
|
||||||
constructor(options: ClientOptions) {
|
constructor(options: ClientOptions) {
|
||||||
const creds: Credentials = {
|
const creds: Credentials = {
|
||||||
@@ -202,6 +218,7 @@ export class PrintfulClient {
|
|||||||
this.Products = new ProductsClient(creds);
|
this.Products = new ProductsClient(creds);
|
||||||
this.Orders = new OrdersClient(creds);
|
this.Orders = new OrdersClient(creds);
|
||||||
this.Webhooks = new WebhooksClient(creds);
|
this.Webhooks = new WebhooksClient(creds);
|
||||||
|
this.Util = new UtilClient(options.webhookSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Util = {
|
static Util = {
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ class CollectionsClient {
|
|||||||
class UtilClient {
|
class UtilClient {
|
||||||
constructor(private webhookSecret: string) { }
|
constructor(private webhookSecret: string) { }
|
||||||
|
|
||||||
verifyWebflowSignature(request: Request, body: unknown): boolean {
|
verifySecret(request: Request, body: unknown): boolean {
|
||||||
const timestamp = request.headers.get("x-webflow-timestamp");
|
const timestamp = request.headers.get("x-webflow-timestamp");
|
||||||
if (!timestamp) return false;
|
if (!timestamp) return false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user