Skip to content

Commit

Permalink
feat: include regKey and authKey in auth object
Browse files Browse the repository at this point in the history
This will allow consumers to know *what* auth value provided the auth
  • Loading branch information
wraithgar committed Oct 10, 2023
1 parent 92ec0da commit 84823a5
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 14 deletions.
64 changes: 50 additions & 14 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const npa = require('npm-package-arg')
const { URL } = require('url')

// Find the longest registry key that is used for some kind of auth
// in the options.
const regKeyFromURI = (uri, opts) => {
// in the options. Returns the registry key and the auth config.
const regFromURI = (uri, opts) => {
const parsed = new URL(uri)
// try to find a config key indicating we have auth for this registry
// can be one of :_authToken, :_auth, :_password and :username, or
Expand All @@ -14,23 +14,40 @@ const regKeyFromURI = (uri, opts) => {
// stopping when we reach '//'.
let regKey = `//${parsed.host}${parsed.pathname}`
while (regKey.length > '//'.length) {
const authKey = hasAuth(regKey, opts)
// got some auth for this URI
if (hasAuth(regKey, opts)) {
return regKey
if (authKey) {
return { regKey, authKey }
}

// can be either //host/some/path/:_auth or //host/some/path:_auth
// walk up by removing EITHER what's after the slash OR the slash itself
regKey = regKey.replace(/([^/]+|\/)$/, '')
}
return { regKey: false, authKey: null }
}

const hasAuth = (regKey, opts) => (
opts[`${regKey}:_authToken`] ||
opts[`${regKey}:_auth`] ||
opts[`${regKey}:username`] && opts[`${regKey}:_password`] ||
opts[`${regKey}:certfile`] && opts[`${regKey}:keyfile`]
)
// Not only do we want to know if there is auth, but if we are calling `npm
// logout` we want to know what config value specifically provided it. This is
// so we can look up where the config came from to delete it (i.e. user vs
// project)
const hasAuth = (regKey, opts) => {
if (opts[`${regKey}:_authToken`]) {
return '_authToken'
}
if (opts[`${regKey}:_auth`]) {
return '_auth'
}
if (opts[`${regKey}:username`] && opts[`${regKey}:_password`]) {
// 'password' can be inferred to also be present
return 'username'
}
if (opts[`${regKey}:certfile`] && opts[`${regKey}:keyfile`]) {
// 'keyfile' can be inferred to also be present
return 'certfile'
}
return false
}

const sameHost = (a, b) => {
const parsedA = new URL(a)
Expand Down Expand Up @@ -63,11 +80,14 @@ const getAuth = (uri, opts = {}) => {
if (!uri) {
throw new Error('URI is required')
}
const regKey = regKeyFromURI(uri, forceAuth || opts)
const { regKey, authKey } = regFromURI(uri, forceAuth || opts)

// we are only allowed to use what's in forceAuth if specified
if (forceAuth && !regKey) {
return new Auth({
// if we force auth we don't want to refer back to anything in config
regKey: false,
authKey: null,
scopeAuthKey: null,
token: forceAuth._authToken || forceAuth.token,
username: forceAuth.username,
Expand All @@ -88,8 +108,8 @@ const getAuth = (uri, opts = {}) => {
// registry where we logged in, but the same auth SHOULD be sent
// to that artifact host, then we track where it was coming in from,
// and warn the user if we get a 4xx error on it.
const scopeAuthKey = regKeyFromURI(registry, opts)
return new Auth({ scopeAuthKey })
const { regKey: scopeAuthKey, authKey: _authKey } = regFromURI(registry, opts)
return new Auth({ scopeAuthKey, regKey: scopeAuthKey, authKey: _authKey })
}
}

Expand All @@ -104,6 +124,8 @@ const getAuth = (uri, opts = {}) => {

return new Auth({
scopeAuthKey: null,
regKey,
authKey,
token,
auth,
username,
Expand All @@ -114,8 +136,22 @@ const getAuth = (uri, opts = {}) => {
}

class Auth {
constructor ({ token, auth, username, password, scopeAuthKey, certfile, keyfile }) {
constructor ({
token,
auth,
username,
password,
scopeAuthKey,
certfile,
keyfile,
regKey,
authKey,
}) {
// same as regKey but only present for scoped auth. Should have been named scopeRegKey
this.scopeAuthKey = scopeAuthKey
// `${regKey}:${authKey}` will get you back to the auth config that gave us auth
this.regKey = regKey
this.authKey = authKey
this.token = null
this.auth = null
this.isBasicAuth = false
Expand Down
44 changes: 44 additions & 0 deletions test/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ t.test('basic auth', t => {
const gotAuth = getAuth(config.registry, config)
t.same(gotAuth, {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: 'username',
token: null,
isBasicAuth: true,
auth: Buffer.from('user:pass').toString('base64'),
Expand Down Expand Up @@ -61,6 +63,8 @@ t.test('token auth', t => {
}
t.same(getAuth(`${config.registry}/foo/-/foo.tgz`, config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_authToken',
isBasicAuth: false,
token: 'c0ffee',
auth: null,
Expand Down Expand Up @@ -107,6 +111,8 @@ t.test('forceAuth', t => {
}
t.same(getAuth(config.registry, config), {
scopeAuthKey: null,
regKey: false,
authKey: null,
token: null,
isBasicAuth: true,
auth: Buffer.from('user:pass').toString('base64'),
Expand Down Expand Up @@ -140,6 +146,8 @@ t.test('forceAuth token', t => {
}
t.same(getAuth(config.registry, config), {
scopeAuthKey: null,
regKey: false,
authKey: null,
isBasicAuth: false,
token: 'cafebad',
auth: null,
Expand Down Expand Up @@ -168,6 +176,8 @@ t.test('_auth auth', t => {
}
t.same(getAuth(`${config.registry}/asdf/foo/bar/baz`, config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_auth',
token: null,
isBasicAuth: false,
auth: 'c0ffee',
Expand Down Expand Up @@ -195,6 +205,8 @@ t.test('_auth username:pass auth', t => {
}
t.same(getAuth(config.registry, config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_auth',
token: null,
isBasicAuth: false,
auth: auth,
Expand Down Expand Up @@ -246,6 +258,8 @@ t.test('globally-configured auth', t => {
}
t.same(getAuth(basicConfig.registry, basicConfig), {
scopeAuthKey: null,
regKey: '//different.registry/',
authKey: 'username',
token: null,
isBasicAuth: true,
auth: Buffer.from('globaluser:globalpass').toString('base64'),
Expand All @@ -261,6 +275,8 @@ t.test('globally-configured auth', t => {
}
t.same(getAuth(tokenConfig.registry, tokenConfig), {
scopeAuthKey: null,
regKey: '//different.registry/',
authKey: '_authToken',
token: 'deadbeef',
isBasicAuth: false,
auth: null,
Expand All @@ -276,6 +292,8 @@ t.test('globally-configured auth', t => {
}
t.same(getAuth(`${_authConfig.registry}/foo`, _authConfig), {
scopeAuthKey: null,
regKey: '//different.registry',
authKey: '_auth',
token: null,
isBasicAuth: false,
auth: 'deadbeef',
Expand All @@ -296,6 +314,8 @@ t.test('otp token passed through', t => {
}
t.same(getAuth(config.registry, config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_authToken',
token: 'c0ffee',
isBasicAuth: false,
auth: null,
Expand Down Expand Up @@ -365,6 +385,8 @@ t.test('always-auth', t => {
}
t.same(getAuth(config.registry, config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_authToken',
token: 'c0ffee',
isBasicAuth: false,
auth: null,
Expand Down Expand Up @@ -399,6 +421,8 @@ t.test('scope-based auth', t => {
}
t.same(getAuth(config['@myscope:registry'], config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_authToken',
auth: null,
isBasicAuth: false,
token: 'c0ffee',
Expand All @@ -407,6 +431,8 @@ t.test('scope-based auth', t => {
}, 'correct auth token picked out')
t.same(getAuth(config['@myscope:registry'], config), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: '_authToken',
auth: null,
isBasicAuth: false,
token: 'c0ffee',
Expand Down Expand Up @@ -446,6 +472,8 @@ t.test('certfile and keyfile errors', t => {
'//my.custom.registry/here/:keyfile': `${dir}/nosuch.key`,
}), {
scopeAuthKey: null,
regKey: '//my.custom.registry/here/',
authKey: 'certfile',
auth: null,
isBasicAuth: false,
token: null,
Expand Down Expand Up @@ -479,6 +507,8 @@ t.test('do not be thrown by other weird configs', t => {
const auth = getAuth(uri, opts)
t.same(auth, {
scopeAuthKey: null,
regKey: '//localhost:15443/foo',
authKey: '_authToken',
token: 'correct bearer token',
isBasicAuth: false,
auth: null,
Expand All @@ -499,6 +529,8 @@ t.test('scopeAuthKey tests', t => {

t.same(getAuth(uri, { ...opts, spec: '@scope/foo@latest' }), {
scopeAuthKey: '//scope-host.com/',
regKey: '//scope-host.com/',
authKey: '_authToken',
auth: null,
isBasicAuth: false,
token: null,
Expand All @@ -508,6 +540,8 @@ t.test('scopeAuthKey tests', t => {

t.same(getAuth(uri, { ...opts, spec: 'foo@npm:@scope/foo@latest' }), {
scopeAuthKey: '//scope-host.com/',
regKey: '//scope-host.com/',
authKey: '_authToken',
auth: null,
isBasicAuth: false,
token: null,
Expand All @@ -517,6 +551,8 @@ t.test('scopeAuthKey tests', t => {

t.same(getAuth(uri, { ...opts, spec: '@other-scope/foo@npm:@scope/foo@latest' }), {
scopeAuthKey: '//scope-host.com/',
regKey: '//scope-host.com/',
authKey: '_authToken',
auth: null,
isBasicAuth: false,
token: null,
Expand All @@ -526,6 +562,8 @@ t.test('scopeAuthKey tests', t => {

t.same(getAuth(uri, { ...opts, spec: '@scope/foo@npm:foo@latest' }), {
scopeAuthKey: null,
regKey: false,
authKey: null,
auth: null,
isBasicAuth: false,
token: null,
Expand All @@ -547,6 +585,8 @@ t.test('registry host matches, path does not, send auth', t => {
const uri = 'https://scope-host.com/blahblah/bloobloo/foo.tgz'
t.same(getAuth(uri, { ...opts, spec: '@scope/foo' }), {
scopeAuthKey: null,
regKey: '//scope-host.com/scope/host/',
authKey: '_authToken',
token: 'c0ffee',
auth: null,
isBasicAuth: false,
Expand All @@ -555,6 +595,8 @@ t.test('registry host matches, path does not, send auth', t => {
})
t.same(getAuth(uri, { ...opts, spec: '@other-scope/foo' }), {
scopeAuthKey: '//other-scope-registry.com/other/scope/',
regKey: '//other-scope-registry.com/other/scope/',
authKey: '_authToken',
token: null,
auth: null,
isBasicAuth: false,
Expand All @@ -563,6 +605,8 @@ t.test('registry host matches, path does not, send auth', t => {
})
t.same(getAuth(uri, { ...opts, registry: 'https://scope-host.com/scope/host/' }), {
scopeAuthKey: null,
regKey: '//scope-host.com/scope/host/',
authKey: '_authToken',
token: 'c0ffee',
auth: null,
isBasicAuth: false,
Expand Down

0 comments on commit 84823a5

Please sign in to comment.