From df0fd5fa062bcb326f922b219ca8248d26349e93 Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Thu, 27 Feb 2025 13:19:11 +0100 Subject: [PATCH] wip change schema version to tuple --- .../saleor-sync-webhook.test.ts | 2 +- .../saleor-async-webhook.test.ts | 4 +-- .../saleor-sync-webhook.test.ts | 2 +- .../saleor-webhooks/saleor-webhook.ts | 15 +++++----- .../saleor-async-webhook.test.ts | 4 +-- .../saleor-sync-webhook.test.ts | 2 +- .../shared/saleor-webhook-validator.test.ts | 3 +- .../shared/saleor-webhook-validator.ts | 26 ++++++++--------- src/handlers/shared/saleor-webhook.ts | 6 ++-- src/types.ts | 5 ++++ src/util/schema-version.ts | 28 ++++++++++++------- 11 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts index f7fa38dd..4d1eeb8b 100644 --- a/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts @@ -74,7 +74,7 @@ describe("AWS Lambda SaleorSyncWebhook", () => { baseUrl: "example.com", event: "CHECKOUT_CALCULATE_TAXES", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: webhookConfig.apl.mockToken, jwks: webhookConfig.apl.mockJwks, diff --git a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts index ac6c0f6e..10bf85e2 100644 --- a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts +++ b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts @@ -33,7 +33,7 @@ describe("Web API SaleorAsyncWebhook", () => { baseUrl: "example.com", event: "product_updated", payload: { data: "test_payload" }, - schemaVersion: 3.2, + schemaVersion: [3, 20], authData: { saleorApiUrl: mockAPL.workingSaleorApiUrl, token: mockAPL.mockToken, @@ -62,7 +62,7 @@ describe("Web API SaleorAsyncWebhook", () => { authData: expect.objectContaining({ saleorApiUrl: mockAPL.workingSaleorApiUrl, }), - }) + }), ); }); diff --git a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts index 9cf9c741..bf0c29ee 100644 --- a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts @@ -31,7 +31,7 @@ describe("Web API SaleorSyncWebhook", () => { baseUrl: "example.com", event: "checkout_calculate_taxes", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: webhookConfiguration.apl.mockToken, jwks: webhookConfiguration.apl.mockJwks, diff --git a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-webhook.ts b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-webhook.ts index f1d9ac5a..fbbedcce 100644 --- a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-webhook.ts +++ b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-webhook.ts @@ -14,16 +14,16 @@ export type WebhookConfig GenericWebhookConfig; /** Function type provided by consumer in `SaleorWebApiWebhook.createHandler` */ -export type WebApiWebhookHandler = ( +export type WebApiWebhookHandler = ( req: Request, - ctx: WebhookContext & TExtras + ctx: WebhookContext, ) => Response | Promise; -export abstract class SaleorWebApiWebhook< - TPayload = unknown, - TExtras extends Record = {} -> extends GenericSaleorWebhook { - createHandler(handlerFn: WebApiWebhookHandler): WebApiHandler { +export abstract class SaleorWebApiWebhook extends GenericSaleorWebhook< + WebApiHandlerInput, + TPayload +> { + createHandler(handlerFn: WebApiWebhookHandler): WebApiHandler { return async (req) => { const adapter = new WebApiAdapter(req); const prepareRequestResult = await super.prepareRequest(adapter); @@ -34,7 +34,6 @@ export abstract class SaleorWebApiWebhook< debug("Incoming request validated. Call handlerFn"); return handlerFn(req, { - ...(this.extraContext ?? ({} as TExtras)), ...prepareRequestResult.context, }); }; diff --git a/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts b/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts index 637f3f35..346ebad2 100644 --- a/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts +++ b/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts @@ -33,7 +33,7 @@ describe("Next.js SaleorAsyncWebhook", () => { expect(saleorAsyncWebhook.getWebhookManifest(baseUrl)).toEqual( expect.objectContaining({ targetUrl: `${baseUrl}/${webhookPath}`, - }) + }), ); }); @@ -56,7 +56,7 @@ describe("Next.js SaleorAsyncWebhook", () => { baseUrl: "example.com", event: "product_updated", payload: { data: "test_payload" }, - schemaVersion: 3.2, + schemaVersion: [3, 20], authData: { saleorApiUrl: mockAPL.workingSaleorApiUrl, token: mockAPL.mockToken, diff --git a/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts index 7c75f6fa..5f1ffb14 100644 --- a/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts @@ -55,7 +55,7 @@ describe("Next.js SaleorSyncWebhook", () => { baseUrl: "example.com", event: "checkout_calculate_taxes", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: validSyncWebhookConfiguration.apl.mockToken, jwks: validSyncWebhookConfiguration.apl.mockJwks, diff --git a/src/handlers/shared/saleor-webhook-validator.test.ts b/src/handlers/shared/saleor-webhook-validator.test.ts index 45fa112a..5a28931e 100644 --- a/src/handlers/shared/saleor-webhook-validator.test.ts +++ b/src/handlers/shared/saleor-webhook-validator.test.ts @@ -237,8 +237,7 @@ describe("SaleorWebhookValidator", () => { }); }); - // TODO: This should be required - it("Fallbacks to null if version is missing in payload", async () => { + it("Throws if version header is missing", async () => { vi.spyOn(adapter, "getRawBody").mockResolvedValue(JSON.stringify({})); vi.spyOn(requestProcessor, "getSaleorHeaders").mockReturnValue(validHeaders); diff --git a/src/handlers/shared/saleor-webhook-validator.ts b/src/handlers/shared/saleor-webhook-validator.ts index e223f6e1..6273892e 100644 --- a/src/handlers/shared/saleor-webhook-validator.ts +++ b/src/handlers/shared/saleor-webhook-validator.ts @@ -4,6 +4,7 @@ import { APL } from "@/APL"; import { createDebug } from "@/debug"; import { fetchRemoteJwks } from "@/fetch-remote-jwks"; import { getOtelTracer } from "@/open-telemetry"; +import { SaleorSchemaVersion } from "@/types"; import { parseSchemaVersion } from "@/util"; import { verifySignatureWithJwks } from "@/verify-signature"; @@ -69,7 +70,8 @@ export class SaleorWebhookValidator { throw new WebhookError("Wrong request method, only POST allowed", "WRONG_METHOD"); } - const { event, signature, saleorApiUrl } = requestProcessor.getSaleorHeaders(); + const { event, signature, saleorApiUrl, schemaVersion } = + requestProcessor.getSaleorHeaders(); const baseUrl = adapter.getBaseUrl(); if (!baseUrl) { @@ -94,7 +96,7 @@ export class SaleorWebhookValidator { throw new WebhookError( `Wrong incoming request event: ${event}. Expected: ${expected}`, - "WRONG_EVENT" + "WRONG_EVENT", ); } @@ -111,7 +113,7 @@ export class SaleorWebhookValidator { throw new WebhookError("Missing request body", "MISSING_REQUEST_BODY"); } - let parsedBody: unknown & { version?: string | null }; + let parsedBody: unknown; try { parsedBody = JSON.parse(rawBody); @@ -121,13 +123,7 @@ export class SaleorWebhookValidator { throw new WebhookError("Request body can't be parsed", "CANT_BE_PARSED"); } - let parsedSchemaVersion: number | null = null; - - try { - parsedSchemaVersion = parseSchemaVersion(parsedBody.version); - } catch { - this.debug("Schema version cannot be parsed"); - } + const parsedSchemaVersion = parseSchemaVersion(schemaVersion); /** * Verify if the app is properly installed for given Saleor API URL @@ -139,7 +135,7 @@ export class SaleorWebhookValidator { throw new WebhookError( `Can't find auth data for ${saleorApiUrl}. Please register the application`, - "NOT_REGISTERED" + "NOT_REGISTERED", ); } @@ -162,7 +158,7 @@ export class SaleorWebhookValidator { throw new WebhookError( "Fetching remote JWKS failed", - "SIGNATURE_VERIFICATION_FAILED" + "SIGNATURE_VERIFICATION_FAILED", ); }); @@ -170,7 +166,7 @@ export class SaleorWebhookValidator { try { this.debug( - "Second attempt to validate the signature JWKS, using fresh tokens from the API" + "Second attempt to validate the signature JWKS, using fresh tokens from the API", ); await verifySignatureWithJwks(newJwks, signature, rawBody); @@ -183,7 +179,7 @@ export class SaleorWebhookValidator { throw new WebhookError( "Request signature check failed", - "SIGNATURE_VERIFICATION_FAILED" + "SIGNATURE_VERIFICATION_FAILED", ); } } @@ -211,7 +207,7 @@ export class SaleorWebhookValidator { } finally { span.end(); } - } + }, ); } } diff --git a/src/handlers/shared/saleor-webhook.ts b/src/handlers/shared/saleor-webhook.ts index 053de609..cf5f4b66 100644 --- a/src/handlers/shared/saleor-webhook.ts +++ b/src/handlers/shared/saleor-webhook.ts @@ -1,3 +1,5 @@ +import { SaleorSchemaVersion } from "@/types"; + import { AuthData } from "../../APL"; export const WebhookErrorCodeMap: Record = { @@ -50,9 +52,7 @@ export type WebhookContext = { event: string; payload: TPayload; authData: AuthData; - // TODO: Make this required - /** Added in Saleor 3.15 */ - schemaVersion: number | null; + schemaVersion: SaleorSchemaVersion; }; export type FormatWebhookErrorResult = { diff --git a/src/types.ts b/src/types.ts index 3e52e0d4..85528e8b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -320,3 +320,8 @@ export interface AppManifest { }; }; } + +/** + * Tuple representing public schema version. Patch is omitted - it doesn't change the schema. + */ +export type SaleorSchemaVersion = [major: number, minor: number]; diff --git a/src/util/schema-version.ts b/src/util/schema-version.ts index d1201c30..61c9cb28 100644 --- a/src/util/schema-version.ts +++ b/src/util/schema-version.ts @@ -1,15 +1,23 @@ -export const parseSchemaVersion = (rawVersion: string | undefined | null): number | null => { - if (!rawVersion) { - return null; - } +import { SaleorSchemaVersion } from "@/types"; + +const cantParseError = new Error("Cant parse Saleor schema version"); + +export const parseSchemaVersion = (rawVersion: string | undefined | null): SaleorSchemaVersion => { + try { + if (!rawVersion) { + throw cantParseError; + } - const [majorString, minorString] = rawVersion.split("."); - const major = parseInt(majorString, 10); - const minor = parseInt(minorString, 10); + const [majorString, minorString] = rawVersion.split("."); + const major = parseInt(majorString, 10); + const minor = parseInt(minorString, 10); - if (major && minor) { - return parseFloat(`${major}.${minor}`); + if (major && minor) { + return [major, minor]; + } + } catch (e) { + throw cantParseError; } - return null; + throw cantParseError; };