Skip to content

Commit 5fb68e6

Browse files
authored
do not require schema version on manifest handler (#412)
* do not require schema version on manifest handler * fix case where saleor schema version was converted to float * fix case where saleor schema version was converted to float * changeset * fix buildResponse
1 parent 126493d commit 5fb68e6

11 files changed

+47
-133
lines changed

.changeset/hot-pens-attend.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@saleor/app-sdk": patch
3+
---
4+
5+
Schema version passed to manifest handler will be string, not float

.changeset/silent-walls-heal.md

-92
This file was deleted.

src/handlers/actions/manifest-action-handler.test.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe("ManifestActionHandler", () => {
3939
expect(manifestFactory).toHaveBeenCalledWith({
4040
appBaseUrl: "http://example.com",
4141
request: {},
42-
schemaVersion: 3.20,
42+
schemaVersion: "3.20",
4343
});
4444
});
4545

@@ -64,18 +64,21 @@ describe("ManifestActionHandler", () => {
6464
expect(result.status).toBe(405);
6565
expect(result.body).toBe("Method not allowed");
6666
expect(manifestFactory).not.toHaveBeenCalled();
67-
})
67+
});
6868

69-
it("should return 400 when receives null schema version header from unsupported legacy Saleor version", async () => {
69+
/**
70+
* api/manifest endpoint is GET and header should be optional. It can be used to install the app eventually,
71+
* but also to preview the manifest from the plain GET request
72+
*/
73+
it("should NOT return 400 when receives null schema version header from unsupported legacy Saleor version", async () => {
7074
adapter.getHeader = vi.fn().mockReturnValue(null);
7175
const handler = new ManifestActionHandler(adapter);
7276

7377
const manifestFactory = vi.fn().mockResolvedValue(mockManifest);
7478

7579
const result = await handler.handleAction({ manifestFactory });
7680

77-
expect(result.status).toBe(400);
78-
expect(result.body).toBe("Missing schema version header");
79-
expect(manifestFactory).not.toHaveBeenCalled();
81+
expect(result.status).toBe(200);
82+
expect(manifestFactory).toHaveBeenCalled();
8083
});
8184
});

src/handlers/actions/manifest-action-handler.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ export type CreateManifestHandlerOptions<T> = {
1414
manifestFactory(context: {
1515
appBaseUrl: string;
1616
request: T;
17-
/** Added in Saleor 3.15 */
18-
schemaVersion: number;
17+
/**
18+
* Schema version is optional. During installation, Saleor will send it,
19+
* so manifest can be generated according to the version. But it may
20+
* be also requested from plain GET from the browser, so it may not be available
21+
*/
22+
schemaVersion?: string;
1923
}): AppManifest | Promise<AppManifest>;
2024
};
2125

@@ -36,14 +40,6 @@ export class ManifestActionHandler<I> implements ActionHandlerInterface {
3640
return invalidMethodResponse;
3741
}
3842

39-
if (!schemaVersion) {
40-
return {
41-
status: 400,
42-
bodyType: "string",
43-
body: "Missing schema version header",
44-
};
45-
}
46-
4743
try {
4844
const manifest = await options.manifestFactory({
4945
appBaseUrl: baseURL,

src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ describe("AWS Lambda createManifestHandler", () => {
4343
expect.objectContaining({
4444
appBaseUrl: expectedBaseUrl,
4545
request: event,
46-
schemaVersion: 3.2,
47-
})
46+
schemaVersion: "3.20",
47+
}),
4848
);
4949
expect(response.statusCode).toBe(200);
5050
expect(JSON.parse(response.body!)).toStrictEqual({
@@ -98,8 +98,8 @@ describe("AWS Lambda createManifestHandler", () => {
9898
expect.objectContaining({
9999
appBaseUrl: expectedBaseUrl,
100100
request: event,
101-
schemaVersion: 3.2,
102-
})
101+
schemaVersion: "3.20",
102+
}),
103103
);
104104
expect(response.statusCode).toBe(200);
105105
expect(JSON.parse(response.body!)).toStrictEqual({

src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import { AwsLambdaWebhookHandler, SaleorWebApiWebhook, WebhookConfig } from "./s
77

88
export type AwsLambdaSyncWebhookHandler<
99
TPayload,
10-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
10+
TEvent extends SyncWebhookEventType = SyncWebhookEventType,
1111
> = AwsLambdaWebhookHandler<TPayload, SyncWebhookInjectedContext<TEvent>>;
1212

1313
export class SaleorSyncWebhook<
1414
TPayload = unknown,
15-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
15+
TEvent extends SyncWebhookEventType = SyncWebhookEventType,
1616
> extends SaleorWebApiWebhook<TPayload, SyncWebhookInjectedContext<TEvent>> {
1717
readonly event: TEvent;
1818

1919
protected readonly eventType = "sync" as const;
2020

2121
protected extraContext = {
22-
buildResponse: buildSyncWebhookResponsePayload,
22+
buildResponse: buildSyncWebhookResponsePayload<TEvent>,
2323
};
2424

2525
constructor(configuration: WebhookConfig<TEvent>) {

src/handlers/platforms/fetch-api/create-manifest-handler.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("Fetch API createManifestHandler", () => {
3636
expect(mockManifestFactory).toHaveBeenCalledWith({
3737
appBaseUrl: baseUrl,
3838
request,
39-
schemaVersion: 3.2,
39+
schemaVersion: "3.20",
4040
});
4141
expect(response.status).toBe(200);
4242
await expect(response.json()).resolves.toStrictEqual({

src/handlers/platforms/next/create-manifest-handler.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe("Next.js createManifestHandler", () => {
3838
expect(mockManifestFactory).toHaveBeenCalledWith({
3939
appBaseUrl: baseUrl,
4040
request: req,
41-
schemaVersion: 3.2,
41+
schemaVersion: "3.20",
4242
});
4343

4444
expect(res.statusCode).toBe(200);

src/handlers/shared/saleor-request-processor.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ describe("SaleorRequestProcessor", () => {
8989
signature: "signature-value",
9090
event: "event-name",
9191
saleorApiUrl: "https://api.saleor.io",
92-
schemaVersion: 3.2,
92+
schemaVersion: "3.20",
9393
});
9494
});
9595

src/handlers/shared/saleor-request-processor.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,20 @@ export class SaleorRequestProcessor<T> {
4040
private toStringOrUndefined = (value: string | string[] | undefined | null) =>
4141
value ? value.toString() : undefined;
4242

43-
private toFloatOrNull = (value: string | string[] | undefined | null) =>
44-
value ? parseFloat(value.toString()) : undefined;
45-
4643
getSaleorHeaders() {
4744
return {
4845
authorizationBearer: this.toStringOrUndefined(
49-
this.adapter.getHeader(SALEOR_AUTHORIZATION_BEARER_HEADER)
46+
this.adapter.getHeader(SALEOR_AUTHORIZATION_BEARER_HEADER),
5047
),
5148
signature: this.toStringOrUndefined(this.adapter.getHeader(SALEOR_SIGNATURE_HEADER)),
5249
event: this.toStringOrUndefined(this.adapter.getHeader(SALEOR_EVENT_HEADER)),
5350
saleorApiUrl: this.toStringOrUndefined(this.adapter.getHeader(SALEOR_API_URL_HEADER)),
54-
schemaVersion: this.toFloatOrNull(this.adapter.getHeader(SALEOR_SCHEMA_VERSION)),
51+
/**
52+
* Schema version must remain string. Since format is "x.x" like "3.20" for javascript it's a floating numer - so it's 3.2
53+
* Saleor version 3.20 != 3.2.
54+
* Semver must be compared as strings
55+
*/
56+
schemaVersion: this.toStringOrUndefined(this.adapter.getHeader(SALEOR_SCHEMA_VERSION)),
5557
};
5658
}
5759
}

src/handlers/shared/saleor-webhook-validator.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ vi.spyOn(verifySignatureModule, "verifySignatureFromApiUrl").mockImplementation(
1414
if (signature !== "mocked_signature") {
1515
throw new Error("Wrong signature");
1616
}
17-
}
17+
},
1818
);
1919
vi.spyOn(verifySignatureModule, "verifySignatureWithJwks").mockImplementation(
2020
async (domain, signature) => {
2121
if (signature !== "mocked_signature") {
2222
throw new Error("Wrong signature");
2323
}
24-
}
24+
},
2525
);
2626

2727
describe("SaleorWebhookValidator", () => {
@@ -33,7 +33,7 @@ describe("SaleorWebhookValidator", () => {
3333
const validHeaders = {
3434
saleorApiUrl: mockAPL.workingSaleorApiUrl,
3535
event: "product_updated",
36-
schemaVersion: 3.2,
36+
schemaVersion: "3.20",
3737
signature: "mocked_signature",
3838
authorizationBearer: "mocked_bearer",
3939
domain: "example.com",
@@ -316,10 +316,10 @@ describe("SaleorWebhookValidator", () => {
316316
expect(mockAPL.set).toHaveBeenCalledWith(
317317
expect.objectContaining({
318318
jwks: "new-jwks",
319-
})
319+
}),
320320
);
321321
expect(fetchRemoteJwksModule.fetchRemoteJwks).toHaveBeenCalledWith(
322-
authDataNoJwks.saleorApiUrl
322+
authDataNoJwks.saleorApiUrl,
323323
);
324324
// it's called only once because jwks was missing initially, so we skipped first validation
325325
expect(verifySignatureModule.verifySignatureWithJwks).toHaveBeenCalledTimes(1);
@@ -351,21 +351,21 @@ describe("SaleorWebhookValidator", () => {
351351
expect(mockAPL.set).toHaveBeenCalledWith(
352352
expect.objectContaining({
353353
jwks: "new-jwks",
354-
})
354+
}),
355355
);
356356
expect(fetchRemoteJwksModule.fetchRemoteJwks).toHaveBeenCalledWith(
357-
authDataNoJwks.saleorApiUrl
357+
authDataNoJwks.saleorApiUrl,
358358
);
359359
expect(verifySignatureModule.verifySignatureWithJwks).toHaveBeenCalledTimes(2);
360360
});
361361

362362
it("Returns an error when new JWKS cannot be fetched", async () => {
363363
vi.spyOn(mockAPL, "get").mockResolvedValue(authDataNoJwks);
364364
vi.spyOn(verifySignatureModule, "verifySignatureWithJwks").mockRejectedValue(
365-
new Error("Initial verification failed")
365+
new Error("Initial verification failed"),
366366
);
367367
vi.spyOn(fetchRemoteJwksModule, "fetchRemoteJwks").mockRejectedValue(
368-
new Error("JWKS fetch failed")
368+
new Error("JWKS fetch failed"),
369369
);
370370

371371
const result = await validator.validateRequest({
@@ -408,7 +408,7 @@ describe("SaleorWebhookValidator", () => {
408408

409409
expect(verifySignatureModule.verifySignatureWithJwks).toHaveBeenCalledTimes(2);
410410
expect(fetchRemoteJwksModule.fetchRemoteJwks).toHaveBeenCalledWith(
411-
authDataNoJwks.saleorApiUrl
411+
authDataNoJwks.saleorApiUrl,
412412
);
413413
});
414414
});

0 commit comments

Comments
 (0)