From 55c8f1afcbcf6d1500a045e13a198801edb02b8e Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Tue, 28 Feb 2023 12:56:48 +0100 Subject: [PATCH] Invoices: validate Saleor version and fail if lower than declared (#220) * Invoices: validate Saleor version and fail if lower than decalred * Fix package lock * Replace tiny-invariant error to custom one to get explicit error message --- .changeset/good-camels-pull.md | 5 ++ apps/invoices/package.json | 3 +- apps/invoices/src/pages/api/register.ts | 86 ++++++++++++++++++++++++- pnpm-lock.yaml | 28 +++++++- 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 .changeset/good-camels-pull.md diff --git a/.changeset/good-camels-pull.md b/.changeset/good-camels-pull.md new file mode 100644 index 0000000..5bcbf91 --- /dev/null +++ b/.changeset/good-camels-pull.md @@ -0,0 +1,5 @@ +--- +"saleor-app-invoices": minor +--- + +App now validates Saleor version and will fail if lower than 3.10 diff --git a/apps/invoices/package.json b/apps/invoices/package.json index f276087..e4d9a4f 100644 --- a/apps/invoices/package.json +++ b/apps/invoices/package.json @@ -16,10 +16,11 @@ "schemaVersion": "3.10" }, "dependencies": { + "semver": "^7.3.8", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "4.0.0-alpha.61", - "@saleor/app-sdk": "0.29.0", + "@saleor/app-sdk": "0.32.7", "@saleor/macaw-ui": "^0.7.2", "@sentry/nextjs": "^7.36.0", "@tanstack/react-query": "^4.24.4", diff --git a/apps/invoices/src/pages/api/register.ts b/apps/invoices/src/pages/api/register.ts index 4596d14..b72e529 100644 --- a/apps/invoices/src/pages/api/register.ts +++ b/apps/invoices/src/pages/api/register.ts @@ -1,9 +1,29 @@ import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next"; - import { saleorApp } from "../../../saleor-app"; +import { gql } from "urql"; +import { createClient } from "../../lib/graphql"; +import { SaleorVersionQuery } from "../../../generated/graphql"; +import invariant from "tiny-invariant"; +import { createLogger } from "../../lib/logger"; + +const semver = require("semver"); const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN; +const SaleorVersion = gql` + query SaleorVersion { + shop { + version + } + } +`; + +/** + * TODO: Move to Manifest, when implemented + * @see https://github.com/saleor/saleor-app-sdk/pull/186 + */ +const APP_SEMVER_REQUIREMENTS = ">=3.10"; + /** * Required endpoint, called by Saleor to install app. * It will exchange tokens with app, so saleorApp.apl will contain token @@ -25,4 +45,68 @@ export default createAppRegisterHandler({ return true; }, ], + /** + * Check Saleor version and reject installation if it doesn't match + * + * TODO: Consider moving to app-sdk and do it under the hood + * + * Also, consume version, if possible, from the request directly + * @see https://github.com/saleor/saleor/issues/12144 + */ + async onRequestVerified(req, { authData: { token, saleorApiUrl }, respondWithError }) { + const logger = createLogger({ + context: "onRequestVerified", + }); + + try { + const client = createClient(saleorApiUrl, async () => { + return { + token, + }; + }); + + const saleorVersion = await client + .query(SaleorVersion, {}) + .toPromise() + .then((res) => { + return res.data?.shop.version; + }); + + logger.debug({ saleorVersion }, "Received saleor version from Shop query"); + + if (!saleorVersion) { + throw new Error("Saleor Version couldnt be fetched from the API"); + } + + const versionIsValid = semver.satisfies( + semver.coerce(saleorVersion), + APP_SEMVER_REQUIREMENTS + ); + + logger.debug( + { saleorVersion, APP_SEMVER_REQUIREMENTS, coerced: semver.coerce(saleorVersion) }, + "Semver validation failed" + ); + + if (!versionIsValid) { + throw new Error(`App requires Saleor matching semver: ${APP_SEMVER_REQUIREMENTS}`); + } + } catch (e: unknown) { + const message = (e as Error)?.message ?? "Unknown error"; + + logger.debug({ message }, "Failed validating semver, will respond with error and status 400"); + + throw respondWithError({ + message: message, + body: { + success: false, + error: { + code: "INVALID_SALEOR_VERSION", + message: message, + }, + }, + status: 400, + }); + } + }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d7d629..0a5ec18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,7 +120,7 @@ importers: '@material-ui/core': ^4.12.4 '@material-ui/icons': ^4.11.3 '@material-ui/lab': 4.0.0-alpha.61 - '@saleor/app-sdk': 0.29.0 + '@saleor/app-sdk': 0.32.7 '@saleor/apps-shared': workspace:* '@saleor/macaw-ui': ^0.7.2 '@sentry/nextjs': ^7.36.0 @@ -151,6 +151,7 @@ importers: react-dom: 18.2.0 react-hook-form: ^7.41.0 rimraf: ^3.0.2 + semver: ^7.3.8 tiny-invariant: ^1.3.1 typescript: 4.9.5 urql: ^3.0.3 @@ -162,7 +163,7 @@ importers: '@material-ui/core': 4.12.4_5ndqzdd6t4rivxsukjv3i3ak2q '@material-ui/icons': 4.11.3_x54wk6dsnsxe7g7vvfmytp77te '@material-ui/lab': 4.0.0-alpha.61_x54wk6dsnsxe7g7vvfmytp77te - '@saleor/app-sdk': 0.29.0_3vryta7zmbcsw4rrqf4axjqggm + '@saleor/app-sdk': 0.32.7_3vryta7zmbcsw4rrqf4axjqggm '@saleor/apps-shared': link:../../packages/shared '@saleor/macaw-ui': 0.7.2_pmlnlm755hlzzzocw2qhf3a34e '@sentry/nextjs': 7.36.0_next@13.1.6+react@18.2.0 @@ -184,6 +185,7 @@ importers: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-hook-form: 7.43.1_react@18.2.0 + semver: 7.3.8 tiny-invariant: 1.3.1 urql: 3.0.3_onqnqwb3ubg5opvemcqf7c2qhy usehooks-ts: 2.9.1_biqbaboplfbrettd7655fr4n2y @@ -4054,6 +4056,28 @@ packages: - supports-color dev: false + /@saleor/app-sdk/0.32.7_3vryta7zmbcsw4rrqf4axjqggm: + resolution: {integrity: sha512-nt4ylAc+pq39LUFzPdlDvmLlvgWDo0K+qMAklvFjiPFGXfFRqTpWs9t9pqDMACiJLM5dvFAkGYJI2T6h/LUD8Q==} + peerDependencies: + next: '>=12' + react: '>=17' + react-dom: '>=17' + dependencies: + '@changesets/cli': 2.26.0 + debug: 4.3.4 + fast-glob: 3.2.12 + graphql: 16.6.0 + jose: 4.11.4 + next: 13.1.6_biqbaboplfbrettd7655fr4n2y + raw-body: 2.5.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + retes: 0.33.0 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + dev: false + /@saleor/macaw-ui/0.7.2_2dwar4pp5qoelfawvjffoi6dne: resolution: {integrity: sha512-Fli7fhTWuHu7q2CzxwTUpB4x9HYaxHSAzCLZLA23VY1ieIWbCxbsXadMiMGWp/nuYitswMr6JXMm+1SDe9K8LQ==} engines: {node: '>=16 <19'}