diff --git a/sources/httpUtils.ts b/sources/httpUtils.ts index b76c3ad5e..3b3f1419c 100644 --- a/sources/httpUtils.ts +++ b/sources/httpUtils.ts @@ -21,18 +21,17 @@ async function fetch(input: string | URL, init?: RequestInit) { const password: string | undefined = input.password || process.env.COREPACK_NPM_PASSWORD; if (username || password) { - headers = { + const encodedCreds = Buffer.from(`${username ?? ''}:${password ?? ''}`, `utf8`).toString(`base64`); + headers = { ...headers, - authorization: `Basic ${Buffer.from(`${username}:${password}`).toString(`base64`)}`, + authorization: `Basic ${encodedCreds}`, }; input.username = input.password = ``; } - const registry = process.env.COREPACK_NPM_TOKEN && new URL(process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL); - - if (registry && input.origin === registry.origin) { - headers = { + if (input.origin === new URL(process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL).origin && process.env.COREPACK_NPM_TOKEN) { + headers = { ...headers, authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, }; diff --git a/sources/npmRegistryUtils.ts b/sources/npmRegistryUtils.ts index a7d110e95..46b9a8328 100644 --- a/sources/npmRegistryUtils.ts +++ b/sources/npmRegistryUtils.ts @@ -21,14 +21,6 @@ export async function fetchAsJson(packageName: string, version?: string) { const headers = {...DEFAULT_HEADERS}; - if (`COREPACK_NPM_TOKEN` in process.env) { - headers.authorization = `Bearer ${process.env.COREPACK_NPM_TOKEN}`; - } else if (`COREPACK_NPM_USERNAME` in process.env - && `COREPACK_NPM_PASSWORD` in process.env) { - const encodedCreds = Buffer.from(`${process.env.COREPACK_NPM_USERNAME}:${process.env.COREPACK_NPM_PASSWORD}`, `utf8`).toString(`base64`); - headers.authorization = `Basic ${encodedCreds}`; - } - return httpUtils.fetchAsJson(`${npmRegistryUrl}/${packageName}${version ? `/${version}` : ``}`, {headers}); } diff --git a/tests/npmRegistryUtils.test.ts b/tests/npmRegistryUtils.test.ts index 728d6f977..536534d9b 100644 --- a/tests/npmRegistryUtils.test.ts +++ b/tests/npmRegistryUtils.test.ts @@ -5,7 +5,11 @@ import {describe, beforeEach, it, expect, vi} from 'vitest'; import {fetchAsJson as httpFetchAsJson} from '../sources/httpUtils'; import {DEFAULT_HEADERS, DEFAULT_NPM_REGISTRY_URL, fetchAsJson} from '../sources/npmRegistryUtils'; -vi.mock(`../sources/httpUtils`); +const fetchMock = vi.fn(() => Promise.resolve({ + ok: true, + json: () => Promise.resolve({}), +})); +vi.stubGlobal(`fetch`, fetchMock); describe(`npm registry utils fetchAsJson`, () => { beforeEach(() => { @@ -22,8 +26,8 @@ describe(`npm registry utils fetchAsJson`, () => { it(`loads from DEFAULT_NPM_REGISTRY_URL by default`, async () => { await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: DEFAULT_HEADERS}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({headers: DEFAULT_HEADERS})); }); it(`loads from custom COREPACK_NPM_REGISTRY if set`, async () => { @@ -31,8 +35,8 @@ describe(`npm registry utils fetchAsJson`, () => { process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org`; await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${process.env.COREPACK_NPM_REGISTRY}/package-name`, {headers: DEFAULT_HEADERS}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${process.env.COREPACK_NPM_REGISTRY}/package-name`), expect.objectContaining({headers: DEFAULT_HEADERS})); }); it(`adds authorization header with bearer token if COREPACK_NPM_TOKEN is set`, async () => { @@ -41,11 +45,13 @@ describe(`npm registry utils fetchAsJson`, () => { await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { - ...DEFAULT_HEADERS, - authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, - }}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, + }, + })); }); it(`only adds authorization header with bearer token if COREPACK_NPM_TOKEN and COREPACK_NPM_USERNAME are set`, async () => { @@ -56,11 +62,13 @@ describe(`npm registry utils fetchAsJson`, () => { await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { - ...DEFAULT_HEADERS, - authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, - }}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Bearer foo`, + }, + })); }); @@ -73,20 +81,77 @@ describe(`npm registry utils fetchAsJson`, () => { await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { - ...DEFAULT_HEADERS, - authorization: `Basic ${encodedCreds}`, - }}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Basic ${encodedCreds}`, + }, + })); }); - it(`does not add authorization header if COREPACK_NPM_USERNAME is set and COREPACK_NPM_PASSWORD is not.`, async () => { + it(`adds authorization header if COREPACK_NPM_USERNAME is set and COREPACK_NPM_PASSWORD is not.`, async () => { // `process.env` is reset after each tests in setupTests.js. process.env.COREPACK_NPM_USERNAME = `foo`; + const encodedCreds = Buffer.from(`${process.env.COREPACK_NPM_USERNAME}:`, `utf8`).toString(`base64`); + + await fetchAsJson(`package-name`); + + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Basic ${encodedCreds}` + }); + }); + + it(`adds authorization header if COREPACK_NPM_PASSWORD is set and COREPACK_NPM_USERNAME is not.`, async () => { + // `process.env` is reset after each tests in setupTests.js. + process.env.COREPACK_NPM_PASSWORD = `foo`; + + const encodedCreds = Buffer.from(`:${process.env.COREPACK_NPM_PASSWORD}`, `utf8`).toString(`base64`); + + await fetchAsJson(`package-name`); + + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`${DEFAULT_NPM_REGISTRY_URL}/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Basic ${encodedCreds}` + })); + }); + + it(`does add authorization header if registry url contains a path`, async () => { + process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org/some/path`; + process.env.COREPACK_NPM_TOKEN = `foo`; + await fetchAsJson(`package-name`); - expect(httpFetchAsJson).toBeCalled(); - expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: DEFAULT_HEADERS}); + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`https://registry.example.org/some/path/package-name`), expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + authorization: `Bearer foo`, + }, + })); + }); +}); + +describe(`httpUtils fetchAsJson`, () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + it(`does not add authorization header if the origin is different from the registry origin`, async () => { + process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org/some/path`; + process.env.COREPACK_NPM_TOKEN = `foo`; + + await httpFetchAsJson(`https://another-registry.example.org/package-name`); + + expect(fetchMock).toBeCalled(); + expect(fetchMock).lastCalledWith(new URL(`https://another-registry.example.org/package-name`), expect.objectContaining({ + headers: undefined, + })); }); });