From c9e2899029010b3525007ee36bc1ac7a14c7890c Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Fri, 24 Jan 2025 15:39:49 -0800 Subject: [PATCH] feat(oas-normalize): support for basic auth in url strings Fixes #1152 --- packages/oas-normalize/src/index.ts | 3 ++- packages/oas-normalize/src/lib/utils.ts | 31 +++++++++++++++++++---- packages/oas-normalize/test/index.test.ts | 18 +++++++++++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/oas-normalize/src/index.ts b/packages/oas-normalize/src/index.ts index b95ba34d1..0af50b3a0 100644 --- a/packages/oas-normalize/src/index.ts +++ b/packages/oas-normalize/src/index.ts @@ -63,7 +63,8 @@ export default class OASNormalize { return resolve(this.file.toString()); case 'url': - const resp = await fetch(utils.normalizeURL(this.file)).then(res => res.text()); + const { url, options } = utils.prepareURL(this.file); + const resp = await fetch(url, options).then(res => res.text()); return resolve(resp); case 'path': diff --git a/packages/oas-normalize/src/lib/utils.ts b/packages/oas-normalize/src/lib/utils.ts index 9d17cfa31..86fe80490 100644 --- a/packages/oas-normalize/src/lib/utils.ts +++ b/packages/oas-normalize/src/lib/utils.ts @@ -14,13 +14,34 @@ export function isBuffer(obj: any) { } /** - * Converts GitHub blob URLs to raw URLs + * Deconstruct a URL into a payload for a `fetch` request. + * */ -export function normalizeURL(url: string) { - if (url.startsWith('https://github.com/') && url.includes('/blob/')) { - return url.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/'); +export function prepareURL(url: string) { + const options: RequestInit = {}; + const u = new URL(url); + + // `fetch` doesn't support supplying basic auth credentials in the URL so we need to move them + // into a header. + if (u.username || u.password) { + options.headers = { + Authorization: `Basic ${btoa(`${u.username}:${u.password}`)}`, + }; + + u.username = ''; + u.password = ''; } - return url; + + // Transform GitHub sources into their raw content URLs. + if (u.host === 'github.com' && u.pathname.includes('/blob/')) { + u.host = 'raw.githubusercontent.com'; + u.pathname = u.pathname.replace('/blob/', '/'); + } + + return { + url: u.toString(), + options, + }; } /** diff --git a/packages/oas-normalize/test/index.test.ts b/packages/oas-normalize/test/index.test.ts index 2ad11ec71..91086a9f9 100644 --- a/packages/oas-normalize/test/index.test.ts +++ b/packages/oas-normalize/test/index.test.ts @@ -16,8 +16,8 @@ describe('#load', () => { ['OpenAPI 3.0', '3.0'], ['OpenAPI 3.1', '3.1'], ])('%s support', (_, version) => { - let json; - let yaml; + let json: Record; + let yaml: string; beforeEach(async () => { json = await import(`@readme/oas-examples/${version}/json/petstore.json`).then(r => r.default); @@ -67,6 +67,20 @@ describe('#load', () => { await expect(o.load()).resolves.toStrictEqual(json); }); + it('should support URLs with basic auth', async () => { + nock('https://@example.com', { + reqheaders: { + Authorization: `Basic ${btoa('username:password')}`, + }, + }) + .get(`/api-${version}.json`) + .reply(200, json); + + const o = new OASNormalize(`https://username:password@example.com/api-${version}.json`); + + await expect(o.load()).resolves.toStrictEqual(json); + }); + it('should convert GitHub repo URLs to raw URLs', async () => { nock('https://raw.githubusercontent.com') .get('/readmeio/oas-examples/main/3.0/json/petstore.json')