mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-11 10:40:53 -04:00
feat(network): basic auth header (#7376)
close #7371 --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
6
.changeset/chatty-insects-push.md
Normal file
6
.changeset/chatty-insects-push.md
Normal 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).
|
||||
@@ -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()
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user