diff --git a/.eslintrc b/.eslintrc index 3f351a7..8541367 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,6 +12,7 @@ "ignorePatterns": ["pnpm-lock.yaml"], "plugins": ["simple-import-sort", "@typescript-eslint"], "rules": { + "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], "quotes": ["error", "double"], "react/react-in-jsx-scope": "off", // next does not require react imports "import/extensions": "off", // file extension not required when importing diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 149ea5f..cb45003 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,6 +17,22 @@ jobs: - name: Check linters run: pnpm lint + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2.2.1 + with: + version: 6.19.1 + - uses: actions/setup-node@v3 + with: + node-version: "16" + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Test + run: pnpm test + build: runs-on: ubuntu-latest steps: diff --git a/package.json b/package.json index a668a30..aab37cb 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "prepublishOnly": "pnpm build", "watch": "tsup-node src/* --format esm,cjs --dts --watch", "build": "tsup-node src/* --format esm,cjs --dts && clear-package-json package.json -o dist/package.json --fields publishConfig", - "test": "uvu -r tsm spec", - "test-watch": "watchlist src spec -- pnpm test", + "test": "vitest", "prepare": "husky install", "lint": "prettier --loglevel warn --write . && eslint --fix ." }, @@ -41,7 +40,7 @@ "tsm": "^2.2.1", "tsup": "^6.2.0", "typescript": "^4.7.4", - "uvu": "^0.5.6", + "vitest": "^0.21.0", "watchlist": "^0.3.1" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19569c5..8d69867 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,7 @@ specifiers: tsm: ^2.2.1 tsup: ^6.2.0 typescript: ^4.7.4 - uvu: ^0.5.6 + vitest: ^0.21.0 watchlist: ^0.3.1 dependencies: @@ -51,7 +51,7 @@ devDependencies: tsm: 2.2.1 tsup: 6.2.0_typescript@4.7.4 typescript: 4.7.4 - uvu: 0.5.6 + vitest: 0.21.0 watchlist: 0.3.1 packages: @@ -133,6 +133,16 @@ packages: tslib: 2.4.0 dev: true + /@types/chai-subset/1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.3 + dev: true + + /@types/chai/4.3.3: + resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -366,6 +376,10 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /assertion-error/1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /ast-types-flow/0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true @@ -435,6 +449,19 @@ packages: engines: {node: '>=6'} dev: true + /chai/4.3.6: + resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 3.0.1 + get-func-name: 2.0.0 + loupe: 2.3.4 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -443,6 +470,10 @@ packages: supports-color: 7.2.0 dev: true + /check-error/1.0.2: + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + dev: true + /chokidar/3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -545,6 +576,13 @@ packages: ms: 2.1.2 dev: true + /deep-eql/3.0.1: + resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} + engines: {node: '>=0.12'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -562,16 +600,6 @@ packages: object-keys: 1.1.1 dev: true - /dequal/2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: true - - /diff/5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} - engines: {node: '>=0.3.1'} - dev: true - /dir-glob/3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1281,6 +1309,10 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /get-func-name/2.0.0: + resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + dev: true + /get-intrinsic/1.1.2: resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} dependencies: @@ -1635,11 +1667,6 @@ packages: object.assign: 4.1.2 dev: true - /kleur/4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - dev: true - /language-subtag-registry/0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -1672,6 +1699,11 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /local-pkg/0.4.2: + resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==} + engines: {node: '>=14'} + dev: true + /locate-path/2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -1695,6 +1727,12 @@ packages: js-tokens: 4.0.0 dev: true + /loupe/2.3.4: + resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} + dependencies: + get-func-name: 2.0.0 + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1753,6 +1791,12 @@ packages: thenify-all: 1.6.0 dev: true + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -1911,6 +1955,10 @@ packages: engines: {node: '>=8'} dev: true + /pathval/1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -1940,6 +1988,15 @@ packages: yaml: 1.10.2 dev: true + /postcss/8.4.16: + resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2054,13 +2111,6 @@ packages: dependencies: queue-microtask: 1.2.3 - /sade/1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - dependencies: - mri: 1.2.0 - dev: true - /semver/6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true @@ -2108,6 +2158,11 @@ packages: engines: {node: '>=12'} dev: true + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map/0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -2233,6 +2288,16 @@ packages: globrex: 0.1.2 dev: true + /tinypool/0.2.4: + resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy/1.0.0: + resolution: {integrity: sha512-FI5B2QdODQYDRjfuLF+OrJ8bjWRMCXokQPcwKm0W3IzcbUmBNv536cQc7eXGoAuXphZwgx1DFbqImwzz08Fnhw==} + engines: {node: '>=14.0.0'} + dev: true + /to-regex-range/5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2332,6 +2397,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -2358,21 +2428,79 @@ packages: punycode: 2.1.1 dev: true - /uvu/0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - dependencies: - dequal: 2.0.3 - diff: 5.1.0 - kleur: 4.1.5 - sade: 1.8.1 - dev: true - /v8-compile-cache/2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true + /vite/3.0.4: + resolution: {integrity: sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + terser: ^5.4.0 + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.14.49 + postcss: 8.4.16 + resolve: 1.22.1 + rollup: 2.76.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitest/0.21.0: + resolution: {integrity: sha512-+BQB2swk4wQdw5loOoL8esIYh/1ifAliuwj2HWHNE2F8SAl/jF7/aoCJBoXGSf/Ws19k3pH4NrWeVtcSwM0j2w==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + c8: '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + c8: + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.3 + '@types/chai-subset': 1.3.3 + '@types/node': 18.0.4 + chai: 4.3.6 + debug: 4.3.4 + local-pkg: 0.4.2 + tinypool: 0.2.4 + tinyspy: 1.0.0 + vite: 3.0.4 + transitivePeerDependencies: + - less + - sass + - stylus + - supports-color + - terser + dev: true + /watchlist/0.3.1: resolution: {integrity: sha512-m5r4bzxJ9eg07TT/O0Q49imFPD45ZTuQ3kaHwSpUJj1QwVd3pzit4UYOmySdmAP5Egkz6mB6hcAPuPfhIbNo0g==} engines: {node: '>=8'} diff --git a/src/middleware.test.ts b/src/middleware.test.ts new file mode 100644 index 0000000..4a4255f --- /dev/null +++ b/src/middleware.test.ts @@ -0,0 +1,32 @@ +import { Handler, Request } from "retes"; +import { Response } from "retes/types"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { withBaseURL } from "./middleware"; + +const getMockEmptyResponse = async () => ({} as Response); + +describe("middleware.test.ts", () => { + describe("withBaseURL", () => { + let mockHandlerFn: Handler = vi.fn(getMockEmptyResponse); + + beforeEach(() => { + mockHandlerFn = vi.fn(); + }); + + it("Adds base URL from request header to context and calls handler", async () => { + const mockRequest = { + context: {}, + headers: { + host: "my-saleor-env.saleor.cloud", + "x-forwarded-proto": "https", + }, + } as unknown as Request; + + await withBaseURL(mockHandlerFn)(mockRequest); + + expect(mockRequest.context.baseURL).toBe("https://my-saleor-env.saleor.cloud"); + expect(mockHandlerFn).toHaveBeenCalledOnce(); + }); + }); +}); diff --git a/src/middleware.ts b/src/middleware.ts index 5eb6c39..e31e7c4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -5,15 +5,14 @@ import { Response } from "retes/response"; import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_SIGNATURE_HEADER } from "./const"; import { getSaleorHeaders } from "./headers"; -import { jwksUrl } from "./urls"; +import { getJwksUrl } from "./urls"; export const withBaseURL: Middleware = (handler) => async (request) => { const { host, "x-forwarded-proto": protocol = "http" } = request.headers; request.context.baseURL = `${protocol}://${host}`; - const response = await handler(request); - return response; + return handler(request); }; export const withSaleorDomainPresent: Middleware = (handler) => async (request) => { @@ -100,7 +99,7 @@ export const withWebhookSignatureVerified = }; const remoteJwks = jose.createRemoteJWKSet( - new URL(jwksUrl(saleorDomain)) + new URL(getJwksUrl(saleorDomain)) ) as jose.FlattenedVerifyGetKey; try { @@ -176,7 +175,7 @@ export const withJWTVerified = } try { - const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl(domain))); + const JWKS = jose.createRemoteJWKSet(new URL(getJwksUrl(domain))); await jose.jwtVerify(token, JWKS); } catch (e) { console.error(e); diff --git a/src/urls.test.ts b/src/urls.test.ts new file mode 100644 index 0000000..be79d6a --- /dev/null +++ b/src/urls.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from "vitest"; + +import { getGraphQLUrl, getJwksUrl } from "./urls"; + +describe("urls.ts", () => { + describe("jwksUrl function", () => { + test.each([ + ["localhost:8000", "http://localhost:8000/.well-known/jwks.json"], + [ + "https://my-saleor.saleor.cloud", + "https://https://my-saleor.saleor.cloud/.well-known/jwks.json", + ], + ])("resolves %s to be %s", (input, expectedOutput) => { + expect(getJwksUrl(input)).toBe(expectedOutput); + }); + }); + + describe("graphQLUrl function", () => { + test.each([ + ["localhost:8000", "http://localhost:8000/graphql/"], + ["https://my-saleor.saleor.cloud", "https://https://my-saleor.saleor.cloud/graphql/"], + ])("resolves %s to be %s", (input, expectedOutput) => { + expect(getGraphQLUrl(input)).toBe(expectedOutput); + }); + }); +}); diff --git a/src/urls.ts b/src/urls.ts index 4dab213..3386940 100644 --- a/src/urls.ts +++ b/src/urls.ts @@ -1,8 +1,21 @@ -const urlProtocol = (saleorDomain: string): string => +/** + * TODO: Validate proper URL + */ +const resolveUrlProtocol = (saleorDomain: string): string => saleorDomain === "localhost:8000" ? "http" : "https"; -export const jwksUrl = (saleorDomain: string): string => - `${urlProtocol(saleorDomain)}://${saleorDomain}/.well-known/jwks.json`; +export const getJwksUrl = (saleorDomain: string): string => + `${resolveUrlProtocol(saleorDomain)}://${saleorDomain}/.well-known/jwks.json`; -export const graphQLUrl = (saleorDomain: string): string => - `${urlProtocol(saleorDomain)}://${saleorDomain}/graphql/`; +export const getGraphQLUrl = (saleorDomain: string): string => + `${resolveUrlProtocol(saleorDomain)}://${saleorDomain}/graphql/`; + +/** + * @deprecated Remove in v1, left for compatibility + */ +export const jwksUrl = getJwksUrl; + +/** + * @deprecated Remove in v1, left for compatibility + */ +export const graphQLUrl = getGraphQLUrl;