feat(network): basic auth header (#7376)

close #7371

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Khải
2023-12-06 00:59:50 +07:00
committed by GitHub
parent 6558d1865f
commit 3ac0487b3f
5 changed files with 46 additions and 11 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/network.auth-header": minor
"pnpm": minor
---
Add support for basic authorization header [#7371](https://github.com/pnpm/pnpm/issues/7371).

View File

@@ -1,7 +1,6 @@
import { URL } from 'url'
import { type URL } from 'url'
export function removePort (originalUrl: string) {
const urlObj = new URL(originalUrl)
export function removePort (urlObj: URL) {
if (urlObj.port === '') return urlObj.href
urlObj.port = ''
return urlObj.toString()

View File

@@ -12,7 +12,7 @@ export function createGetAuthHeaderByURI (
allSettings: opts.allSettings,
userSettings: opts.userSettings ?? {},
})
if (Object.keys(authHeaders).length === 0) return () => undefined
if (Object.keys(authHeaders).length === 0) return (uri: string) => basicAuth(new URL(uri))
return getAuthHeaderByURI.bind(null, authHeaders, getMaxParts(Object.keys(authHeaders)))
}
@@ -27,15 +27,24 @@ function getAuthHeaderByURI (authHeaders: Record<string, string>, maxParts: numb
if (!uri.endsWith('/')) {
uri += '/'
}
const parsedUri = new URL(uri)
const basic = basicAuth(parsedUri)
if (basic) return basic
const nerfed = nerfDart(uri)
const parts = nerfed.split('/')
for (let i = Math.min(parts.length, maxParts) - 1; i >= 3; i--) {
const key = `${parts.slice(0, i).join('/')}/`
if (authHeaders[key]) return authHeaders[key]
}
const urlWithoutPort = removePort(uri)
const urlWithoutPort = removePort(parsedUri)
if (urlWithoutPort !== uri) {
return getAuthHeaderByURI(authHeaders, maxParts, urlWithoutPort)
}
return undefined
}
function basicAuth (uri: URL): string | undefined {
if (!uri.username && !uri.password) return undefined
const auth64 = btoa(`${uri.username}:${uri.password}`)
return `Basic ${auth64}`
}

View File

@@ -21,6 +21,27 @@ test('getAuthHeaderByURI()', () => {
expect(getAuthHeaderByURI('https://reg.gg:8888/foo/-/foo-1.0.0.tgz')).toBe('Bearer 0000')
})
test('getAuthHeaderByURI() basic auth without settings', () => {
const getAuthHeaderByURI = createGetAuthHeaderByURI({
allSettings: {},
})
expect(getAuthHeaderByURI('https://user:secret@reg.io/')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:@reg.io/')).toBe('Basic ' + btoa('user:'))
expect(getAuthHeaderByURI('https://:secret@reg.io/')).toBe('Basic ' + btoa(':secret'))
expect(getAuthHeaderByURI('https://user@reg.io/')).toBe('Basic ' + btoa('user:'))
})
test('getAuthHeaderByURI() basic auth with settings', () => {
const getAuthHeaderByURI = createGetAuthHeaderByURI(opts)
expect(getAuthHeaderByURI('https://user:secret@reg.com/')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.com/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.com:8080/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.io/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.co/tarballs/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.gg:8888/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
expect(getAuthHeaderByURI('https://user:secret@reg.gg:8888/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret'))
})
test('getAuthHeaderByURI() https port 443 checks', () => {
const getAuthHeaderByURI = createGetAuthHeaderByURI(opts)
expect(getAuthHeaderByURI('https://custom.domain.com:443/artifactory/api/npm/npm-virtual/')).toBe('Bearer xyz')
@@ -60,4 +81,4 @@ test('getAuthHeaderByURI() when the registry has pathnames', () => {
expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123')
expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123')
expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123')
})
})

View File

@@ -3,10 +3,10 @@ import { removePort } from '../src/helpers/removePort'
describe('removePort()', () => {
it('does not mutate the url if no port is found', () => {
const urlString = 'https://custom.domain.com/npm/-/foo-1.0.0.tgz'
expect(removePort(urlString)).toEqual(urlString)
expect(removePort(new URL(urlString))).toEqual(urlString)
const urlStringWithTrailingSlash = 'https://custom.domain.com/npm/'
expect(removePort(urlStringWithTrailingSlash)).toEqual(
expect(removePort(new URL(urlStringWithTrailingSlash))).toEqual(
urlStringWithTrailingSlash
)
})
@@ -16,7 +16,7 @@ describe('removePort()', () => {
const protocols = ['http', 'https', 'ws', 'wss']
const getUrl = (port: number, protocol: string) =>
`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
new URL(`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`)
const expectedOutput = (protocol: string) =>
`${protocol}://custom.domain.com/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
@@ -39,7 +39,7 @@ describe('removePort()', () => {
])
const getUrl = (port: number, protocol: string) =>
`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
new URL(`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`)
const expectedOutput = (protocol: string) =>
`${protocol}://custom.domain.com/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
@@ -67,7 +67,7 @@ describe('removePort()', () => {
])
const getUrl = (port: number, protocol: string) =>
`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
new URL(`${protocol}://custom.domain.com:${port}/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`)
const expectedOutput = (protocol: string) =>
`${protocol}://custom.domain.com/artifactory/api/npm/npm-virtual/-/foo-1.0.0.tgz`
mismatchProtocolPorts.forEach((value: number, protocol) => {