From 9eef6b9f8dc65b6f33a06aabdf18b1a457b9edcf Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Thu, 25 Apr 2024 10:34:09 -0500 Subject: [PATCH 1/2] Update migration guide with lineItemBilling flag updates --- .../apps/shopify-api/docs/migrating-to-v10.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/apps/shopify-api/docs/migrating-to-v10.md b/packages/apps/shopify-api/docs/migrating-to-v10.md index 03302cf6b..e5ce2a5b0 100644 --- a/packages/apps/shopify-api/docs/migrating-to-v10.md +++ b/packages/apps/shopify-api/docs/migrating-to-v10.md @@ -7,3 +7,20 @@ This document outlines the changes you need to make to your app to migrate from The `scopes` property on the config object is now optional. If your app is using the new [managed install flow](https://shopify.dev/docs/apps/auth/installation), it is now recommended you omit the `scopes` property from the config object. Using both the `scopes` property and managed install can lead to unexpected behavior if these values are not kept in sync. + +If you are directly accessing the scopes from the config object, you should update your code to handle the case where the `scopes` property is not present. + +For example, but not limited to: +```js +// Before +const scopes = shopify.config.scopes.toString(); + +// After +const scopes = shopify.config.scopes + ? shopify.config.scopes.toString() + : ''; +``` + +## v10_lineItemBilling future flag has been renamed to lineItemBilling + +The `lineItemBilling` feature will **not** be enabled by default in v10. Because of this it has been renamed `lineItemBilling`. If you are using the `v10_lineItemBilling` future flag, you can optionally update your code to use the `lineItemBilling` feature flag instead. From 379206c9376984a0381c6942e7a51cb3132b81ab Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:41:53 -0400 Subject: [PATCH 2/2] Remove deprecations for v10 release --- .changeset/fifty-trains-brake.md | 7 +++ .changeset/tricky-actors-laugh.md | 5 ++ .../apps/shopify-api/docs/migrating-to-v10.md | 55 ++++++++++++++++++- .../lib/clients/admin/graphql/client.ts | 2 +- .../lib/clients/storefront/client.ts | 2 +- .../lib/webhooks/__tests__/validate.test.ts | 17 +++++- .../apps/shopify-api/lib/webhooks/process.ts | 4 ++ .../apps/shopify-api/lib/webhooks/validate.ts | 9 --- 8 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 .changeset/fifty-trains-brake.md create mode 100644 .changeset/tricky-actors-laugh.md diff --git a/.changeset/fifty-trains-brake.md b/.changeset/fifty-trains-brake.md new file mode 100644 index 000000000..5902ea94e --- /dev/null +++ b/.changeset/fifty-trains-brake.md @@ -0,0 +1,7 @@ +--- +"@shopify/shopify-api": major +--- + +Webhook validation will now return a different `reason` value when the HMAC value is missing from the request. Instead of returning `WebhookValidationErrorReason.MissingHeaders` as it does for the other headers it validates, it will now return a new `WebhookValidationErrorReason.MissingHmac` error so this check matches other HMAC validations. + +If your app doesn't explicitly check for the error after calling `webhook.validate()`, you don't need to make any changes. diff --git a/.changeset/tricky-actors-laugh.md b/.changeset/tricky-actors-laugh.md new file mode 100644 index 000000000..bd58f50c2 --- /dev/null +++ b/.changeset/tricky-actors-laugh.md @@ -0,0 +1,5 @@ +--- +"@shopify/shopify-api": patch +--- + +Postponed deprecating the GraphQL clients' `query` method because they haven't been deprecated for long enough. They'll be removed when v11 is released instead. diff --git a/packages/apps/shopify-api/docs/migrating-to-v10.md b/packages/apps/shopify-api/docs/migrating-to-v10.md index e5ce2a5b0..1470ee7f3 100644 --- a/packages/apps/shopify-api/docs/migrating-to-v10.md +++ b/packages/apps/shopify-api/docs/migrating-to-v10.md @@ -11,16 +11,65 @@ Using both the `scopes` property and managed install can lead to unexpected beha If you are directly accessing the scopes from the config object, you should update your code to handle the case where the `scopes` property is not present. For example, but not limited to: + ```js // Before const scopes = shopify.config.scopes.toString(); // After -const scopes = shopify.config.scopes - ? shopify.config.scopes.toString() - : ''; +const scopes = shopify.config.scopes ? shopify.config.scopes.toString() : ''; ``` ## v10_lineItemBilling future flag has been renamed to lineItemBilling The `lineItemBilling` feature will **not** be enabled by default in v10. Because of this it has been renamed `lineItemBilling`. If you are using the `v10_lineItemBilling` future flag, you can optionally update your code to use the `lineItemBilling` feature flag instead. + +## Webhook validation no longer returns `MissingHeaders` when HMAC header is missing + +Webhook validation will now return a different `reason` value when the HMAC value is missing from the request. + +Instead of returning `WebhookValidationErrorReason.MissingHeaders` as it does for the other headers it validates, it will now return a new `WebhookValidationErrorReason.MissingHmac` error so this check matches other HMAC validations. + +```ts +import {type WebhookValidationErrorReason} from '@shopify/shopify-api'; + +const check = await shopify.webhooks.validate({ + rawBody: (req as any).rawBody, + rawRequest: req, + rawResponse: res, +}); + +// Before +if ( + !check.valid && + check.reason === WebhookValidationErrorReason.MissingHeaders && + check.missingHeaders.includes(ShopifyHeader.Hmac) +) { + // Handle error +} + +// After +if (!check.valid && check.reason === WebhookValidationErrorReason.MissingHmac) { + // Handle error +} +``` + +## Internal build paths changed to introduce ESM and CJS exports + +We started exporting both CJS and ESM outputs in this version, which affected how we export the files from the package internally. + +While this should have no effect on most apps, if you're directly importing a file from the package, its path will have changed. + +Regular imports for package files remain unchanged. + +```ts +// Before +import 'node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client'; +import '@shopify/shopify-api/adapters/node'; + +// After +// Add `dist/esm|cjs/` before the file +import 'node_modules/@shopify/shopify-api/dist/esm/lib/clients/admin/graphql/client'; +// Unchanged +import '@shopify/shopify-api/adapters/node'; +``` diff --git a/packages/apps/shopify-api/lib/clients/admin/graphql/client.ts b/packages/apps/shopify-api/lib/clients/admin/graphql/client.ts index b1546a341..1c5aa044d 100644 --- a/packages/apps/shopify-api/lib/clients/admin/graphql/client.ts +++ b/packages/apps/shopify-api/lib/clients/admin/graphql/client.ts @@ -69,7 +69,7 @@ export class GraphqlClient { params: GraphqlParams, ): Promise> { logger(this.graphqlClass().config).deprecated( - '10.0.0', + '11.0.0', 'The query method is deprecated, and was replaced with the request method.\n' + 'See the migration guide: https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/migrating-to-v9.md#using-the-new-clients.', ); diff --git a/packages/apps/shopify-api/lib/clients/storefront/client.ts b/packages/apps/shopify-api/lib/clients/storefront/client.ts index bf838ac5a..4ec92e1a5 100644 --- a/packages/apps/shopify-api/lib/clients/storefront/client.ts +++ b/packages/apps/shopify-api/lib/clients/storefront/client.ts @@ -84,7 +84,7 @@ export class StorefrontClient { params: GraphqlParams, ): Promise> { logger(this.storefrontClass().config).deprecated( - '10.0.0', + '11.0.0', 'The query method is deprecated, and was replaced with the request method.\n' + 'See the migration guide: https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/migrating-to-v9.md#using-the-new-clients.', ); diff --git a/packages/apps/shopify-api/lib/webhooks/__tests__/validate.test.ts b/packages/apps/shopify-api/lib/webhooks/__tests__/validate.test.ts index 833f26b76..12ded1463 100644 --- a/packages/apps/shopify-api/lib/webhooks/__tests__/validate.test.ts +++ b/packages/apps/shopify-api/lib/webhooks/__tests__/validate.test.ts @@ -33,7 +33,6 @@ describe('shopify.webhooks.validate', () => { it.each([ {headers: {apiVersion: ''}, missingHeader: ShopifyHeader.ApiVersion}, {headers: {domain: ''}, missingHeader: ShopifyHeader.Domain}, - {headers: {hmac: ''}, missingHeader: ShopifyHeader.Hmac}, {headers: {topic: ''}, missingHeader: ShopifyHeader.Topic}, {headers: {webhookId: ''}, missingHeader: ShopifyHeader.WebhookId}, ])(`returns false on missing header $missingHeader`, async (config) => { @@ -73,6 +72,22 @@ describe('shopify.webhooks.validate', () => { }); }); + it('returns false on missing HMAC', async () => { + const shopify = shopifyApi(testConfig()); + const app = getTestApp(shopify); + + const response = await request(app) + .post('/webhooks') + .set(headers({hmac: ''})) + .send(rawBody) + .expect(200); + + expect(response.body.data).toEqual({ + valid: false, + reason: WebhookValidationErrorReason.MissingHmac, + }); + }); + it('returns false on invalid HMAC', async () => { const shopify = shopifyApi(testConfig()); const app = getTestApp(shopify); diff --git a/packages/apps/shopify-api/lib/webhooks/process.ts b/packages/apps/shopify-api/lib/webhooks/process.ts index 5f2f3cc77..1124a0e96 100644 --- a/packages/apps/shopify-api/lib/webhooks/process.ts +++ b/packages/apps/shopify-api/lib/webhooks/process.ts @@ -181,6 +181,10 @@ async function handleInvalidWebhook( response.statusCode = StatusCode.BadRequest; response.errorMessage = 'No body was received when processing webhook'; break; + case WebhookValidationErrorReason.MissingHmac: + response.statusCode = StatusCode.BadRequest; + response.errorMessage = `Missing HMAC header in request`; + break; case WebhookValidationErrorReason.InvalidHmac: response.statusCode = StatusCode.Unauthorized; response.errorMessage = `Could not validate request HMAC`; diff --git a/packages/apps/shopify-api/lib/webhooks/validate.ts b/packages/apps/shopify-api/lib/webhooks/validate.ts index 3a5352b94..c235dc4a4 100644 --- a/packages/apps/shopify-api/lib/webhooks/validate.ts +++ b/packages/apps/shopify-api/lib/webhooks/validate.ts @@ -48,15 +48,6 @@ export function validateFactory(config: ConfigInterface) { }); if (!validHmacResult.valid) { - // Deprecated: this is for backwards compatibility with the old HMAC validation - // This will be removed in the next major version, and missing_hmac will be returned instead of missing_header when the hmac is missing - if (validHmacResult.reason === ValidationErrorReason.MissingHmac) { - return { - valid: false, - reason: WebhookValidationErrorReason.MissingHeaders, - missingHeaders: [ShopifyHeader.Hmac], - }; - } if (validHmacResult.reason === ValidationErrorReason.InvalidHmac) { const log = logger(config); await log.debug(