Skip to content

Commit 6875d17

Browse files
Require deleteMetadata to metadata manager (#419)
* Require deleteMetadata to metadata manager * Update metadata-manager.ts * Update src/settings-manager/metadata-manager.ts Co-authored-by: Krzysztof Żuraw <9116238+krzysztofzuraw@users.noreply.github.com> * fix lint * fix build * fix build --------- Co-authored-by: Krzysztof Żuraw <9116238+krzysztofzuraw@users.noreply.github.com>
1 parent 8dc8d4a commit 6875d17

8 files changed

+56
-30
lines changed

.changeset/tangy-rings-retire.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@saleor/app-sdk": major
3+
---
4+
5+
MetadataManager now requires deleteMetadata to be defined

src/auth/verify-signature.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { verifySignatureWithJwks } from "@/auth/verify-signature";
4+
5+
/**
6+
* Actual signature copied from testing webhook payload.
7+
*/
8+
const testSignature =
9+
"eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImlzcyI6Imh0dHBzOi8vaGFja2F0aG9uLXNoaXBwaW5nLmV1LnNhbGVvci5jbG91ZC9ncmFwaHFsLyIsImtpZCI6InlLdzlYeUVmOHgyYnd5SkdCWVlTckhzeXpHaHhKaktLdlpkb2tOZTlGSWMiLCJ0eXAiOiJKV1QifQ..ShPbfvYc_A5Aq3hiT6sisDclKikDkhxOvGXT2ZWgdsGRjZpg9ukiHRZym0kbfMfDqU5C3Pfo6n7am0ExwbnFWBfOil3pfe3uJOcOn_UGRj76Fy-59TB0JdS_WuTgNQcYM8Yjvlq2sNK4jdAfJVRTTx8FVgEpFrHBKmcMPfD7zuDozswIDMZOkklYqBcyQ76DJYIRVhl3QsktYPPrxDoqf-GJ--e9FuNqtqNDksP1weiDSraqXCF4-Ie7UWZsMIFxkPF8jdKjF_s1UmNS8Xel8soFQQ9L6Gps-NEv7xcHicGt5lgohH4mqhz1YIxCR7v_NTQgWImu_GQ6ELBiBSIZ2Q";
10+
11+
const rawContent = "{\"__typename\": \"OrderCreated\"}";
12+
13+
const jwks =
14+
"{\"keys\": [{\"kty\": \"RSA\", \"key_ops\": [\"verify\"], \"n\": \"uDhbbpspufsQiqHsmC4kvmFQ5l2mGZsGcWhKVSQKQubSdXMedPpLnPD3Z3DsY76DILTm6WfOtSp5rr4KzF5wjurlOEhuFsB1HUfK9ZZB2nEDCQbweoIv3SOdclaNB__pYvQ0nmQHwsAeqH1QUuFUIvOL3t31rhjvzX6wvS49fGNb7rDlqQjufCvaX_n-ADJTgEAg6y1Mzn5NhgoTV1KTBeviyZqCdwvD6bk1ghN2XXWpNcARTzu3WHrmzIzkTwQeIMG8efwIddjfCaMGiOzAfzdQlqHlHPL1Xb5kV9AVX3kiSy-9shaQY23HdWwwiodrb4k2w34Z9ZZN-MHp8i6JdQ\", \"e\": \"AQAB\", \"use\": \"sig\", \"kid\": \"yKw9XyEf8x2bwyJGBYYSrHsyzGhxJjKKvZdokNe9FIc\"}]}";
15+
16+
describe("verifySignatureWithJwks", () => {
17+
it("Returns empty promise if signature is valid", () =>
18+
expect(verifySignatureWithJwks(jwks, testSignature, rawContent)).resolves.not.toThrow());
19+
20+
it("Throws if signature is invalid", () =>
21+
expect(
22+
verifySignatureWithJwks(jwks, testSignature, "{\"forged\": \"payload\"}"),
23+
).rejects.toThrow());
24+
});

src/auth/verify-signature.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ const debug = createDebug("verify-signature");
77
/**
88
* Verify the Webhook payload signature from provided JWKS string.
99
* JWKS can be cached to avoid unnecessary calls.
10-
*
11-
* TODO: Add test
1210
*/
1311
export const verifySignatureWithJwks = async (jwks: string, signature: string, rawBody: string) => {
1412
const [header, , jwsSignature] = signature.split(".");
@@ -22,14 +20,14 @@ export const verifySignatureWithJwks = async (jwks: string, signature: string, r
2220

2321
try {
2422
const parsedJWKS = JSON.parse(jwks);
23+
2524
localJwks = jose.createLocalJWKSet(parsedJWKS) as jose.FlattenedVerifyGetKey;
2625
} catch {
2726
debug("Could not create local JWKSSet from given data: %s", jwks);
27+
2828
throw new Error("JWKS verification failed - could not parse given JWKS");
2929
}
3030

31-
debug("Created remote JWKS");
32-
3331
try {
3432
await jose.flattenedVerify(jws, localJwks);
3533
debug("JWKS verified");

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

-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ describe("SaleorWebhookValidator", () => {
230230
});
231231
});
232232

233-
// TODO: This should be required
234233
it("Fallbacks to null if version is missing in payload", async () => {
235234
vi.spyOn(adapter, "getRawBody").mockResolvedValue(JSON.stringify({}));
236235
vi.spyOn(requestProcessor, "getSaleorHeaders").mockReturnValue(validHeaders);

src/settings-manager/encrypted-metadata-manager.test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ describe("settings-manager", () => {
4040
mutateMetadata: mutateMock,
4141
// @ts-expect-error
4242
encryptionKey: undefined,
43-
})
43+
}),
4444
).toThrowError(
45-
"Encryption key for the EncryptedMetadataManager has not been set. Setting it for the production environments is necessary. You can find more in the documentation: https://docs.saleor.io/docs/3.x/developer/extending/apps/developing-apps/app-sdk/settings-manager"
45+
"Encryption key for the EncryptedMetadataManager has not been set. Setting it for the production environments is necessary. You can find more in the documentation: https://docs.saleor.io/docs/3.x/developer/extending/apps/developing-apps/app-sdk/settings-manager",
4646
);
4747
});
4848

@@ -70,6 +70,7 @@ describe("settings-manager", () => {
7070
fetchMetadata: fetchMock,
7171
mutateMetadata: mutateMock,
7272
encryptionKey: "key",
73+
deleteMetadata: deleteMetadataMock,
7374
});
7475

7576
beforeEach(() => {
@@ -112,6 +113,7 @@ describe("settings-manager", () => {
112113
encryptionKey: "key",
113114
encryptionMethod: customEncrypt,
114115
decryptionMethod: customDecrypt,
116+
deleteMetadata: deleteMetadataMock,
115117
});
116118

117119
await customManager.set(newEntry);

src/settings-manager/encrypted-metadata-manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ export class EncryptedMetadataManager implements SettingsManager {
8080
if (process.env.NODE_ENV === "production") {
8181
console.error("Can't start the application without the secret key.");
8282
throw new Error(
83-
"Encryption key for the EncryptedMetadataManager has not been set. Setting it for the production environments is necessary. You can find more in the documentation: https://docs.saleor.io/docs/3.x/developer/extending/apps/developing-apps/app-sdk/settings-manager"
83+
"Encryption key for the EncryptedMetadataManager has not been set. Setting it for the production environments is necessary. You can find more in the documentation: https://docs.saleor.io/docs/3.x/developer/extending/apps/developing-apps/app-sdk/settings-manager",
8484
);
8585
}
8686
console.warn(
87-
"WARNING: Encrypted Metadata Manager encryption key has not been set. For production deployments, it need's to be set"
87+
"WARNING: Encrypted Metadata Manager encryption key has not been set. For production deployments, it need's to be set",
8888
);
8989
console.warn("Using placeholder value for the development.");
9090
this.encryptionKey = "CHANGE_ME";

src/settings-manager/metadata-manager.test.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ describe("settings-manager", () => {
2121
});
2222

2323
it("Get method - using cached values", async () => {
24-
const manager = new MetadataManager({ fetchMetadata: fetchMock, mutateMetadata: mutateMock });
24+
const manager = new MetadataManager({
25+
fetchMetadata: fetchMock,
26+
mutateMetadata: mutateMock,
27+
deleteMetadata: deleteMetadataMock,
28+
});
2529
expect(fetchMock).toBeCalledTimes(0);
2630

2731
// Fetch should be called just after getting a first value
@@ -38,15 +42,23 @@ describe("settings-manager", () => {
3842
});
3943

4044
it("Get method - return values for chosen domain", async () => {
41-
const manager = new MetadataManager({ fetchMetadata: fetchMock, mutateMetadata: mutateMock });
45+
const manager = new MetadataManager({
46+
fetchMetadata: fetchMock,
47+
mutateMetadata: mutateMock,
48+
deleteMetadata: deleteMetadataMock,
49+
});
4250

4351
expect(await manager.get("a", "x.com")).toBe(entryForDomainX.value);
4452
expect(await manager.get("a", "y.com")).toBe(entryForDomainY.value);
4553
expect(await manager.get("a", "unknown.com")).toBe(undefined);
4654
});
4755

4856
it("Set method - return values for chosen domain", async () => {
49-
const manager = new MetadataManager({ fetchMetadata: fetchMock, mutateMetadata: mutateMock });
57+
const manager = new MetadataManager({
58+
fetchMetadata: fetchMock,
59+
mutateMetadata: mutateMock,
60+
deleteMetadata: deleteMetadataMock,
61+
});
5062
const newEntry = { key: "new", value: "new value" };
5163

5264
await manager.set(newEntry);
@@ -64,22 +76,12 @@ describe("settings-manager", () => {
6476
const manager = new MetadataManager({
6577
fetchMetadata: fetchMock,
6678
mutateMetadata: mutateMock,
79+
deleteMetadata: deleteMetadataMock,
6780
});
6881

6982
expect(manager).toBeDefined();
7083
});
7184

72-
it("Throws if \"delete\" method is called, but deleteMetadata was not passed to constructor", async () => {
73-
const manager = new MetadataManager({
74-
fetchMetadata: fetchMock,
75-
mutateMetadata: mutateMock,
76-
});
77-
78-
await expect(manager.delete("test")).rejects.toThrowErrorMatchingInlineSnapshot(
79-
"[Error: Delete not implemented. Ensure MetadataManager is configured with deleteMetadata param in constructor]"
80-
);
81-
});
82-
8385
it("Calls deleteMetadata constructor param when \"delete\" method called", async () => {
8486
const manager = new MetadataManager({
8587
fetchMetadata: fetchMock,

src/settings-manager/metadata-manager.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,7 @@ const serializeSettingsToMetadata = ({ key, value, domain }: SettingsValue): Met
4141
export interface MetadataManagerConfig {
4242
fetchMetadata: FetchMetadataCallback;
4343
mutateMetadata: MutateMetadataCallback;
44-
/**
45-
* Keep it optional, to avoid breaking changes. If not provided, delete will throw.
46-
* TODO: Make it required in next major version
47-
*/
48-
deleteMetadata?: DeleteMetadataCallback;
44+
deleteMetadata: DeleteMetadataCallback;
4945
}
5046

5147
/**
@@ -62,7 +58,7 @@ export class MetadataManager implements SettingsManager {
6258

6359
private mutateMetadata: MutateMetadataCallback;
6460

65-
private deleteMetadata?: DeleteMetadataCallback;
61+
private deleteMetadata: DeleteMetadataCallback;
6662

6763
constructor({ fetchMetadata, mutateMetadata, deleteMetadata }: MetadataManagerConfig) {
6864
this.fetchMetadata = fetchMetadata;
@@ -98,7 +94,7 @@ export class MetadataManager implements SettingsManager {
9894
async delete(args: DeleteSettingsValue | DeleteSettingsValue[] | string | string[]) {
9995
if (!this.deleteMetadata) {
10096
throw new Error(
101-
"Delete not implemented. Ensure MetadataManager is configured with deleteMetadata param in constructor"
97+
"Delete not implemented. Ensure MetadataManager is configured with deleteMetadata param in constructor",
10298
);
10399
}
104100

0 commit comments

Comments
 (0)