npm-registry-fetch
Advanced tools
Comparing version 9.0.0 to 10.0.0
129
auth.js
'use strict' | ||
const npa = require('npm-package-arg') | ||
const defaultOpts = require('./default-opts.js') | ||
const url = require('url') | ||
// Find the longest registry key that is used for some kind of auth | ||
// in the options. | ||
const regKeyFromURI = (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, or :_password and :username | ||
// We walk up the "path" until we're left with just //<host>[:<port>], | ||
// stopping when we reach '//'. | ||
let regKey = `//${parsed.host}${parsed.pathname}` | ||
while (regKey.length > '//'.length) { | ||
// got some auth for this URI | ||
if (hasAuth(regKey, opts)) | ||
return regKey | ||
module.exports = getAuth | ||
function getAuth (registry, opts_ = {}) { | ||
if (!registry) | ||
throw new Error('registry is required') | ||
const opts = opts_.forceAuth ? opts_.forceAuth : { ...defaultOpts, ...opts_ } | ||
const AUTH = {} | ||
const regKey = registry && registryKey(registry) | ||
const doKey = (key, alias) => addKey(opts, AUTH, regKey, key, alias) | ||
doKey('token') | ||
doKey('_authToken', 'token') | ||
doKey('username') | ||
doKey('password') | ||
doKey('_password', 'password') | ||
doKey('email') | ||
doKey('_auth') | ||
doKey('otp') | ||
doKey('always-auth', 'alwaysAuth') | ||
if (AUTH.password) | ||
AUTH.password = Buffer.from(AUTH.password, 'base64').toString('utf8') | ||
if (AUTH._auth && !(AUTH.username && AUTH.password)) { | ||
let auth = Buffer.from(AUTH._auth, 'base64').toString() | ||
auth = auth.split(':') | ||
AUTH.username = auth.shift() | ||
AUTH.password = auth.join(':') | ||
// 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(/([^/]+|\/)$/, '') | ||
} | ||
AUTH.alwaysAuth = AUTH.alwaysAuth === 'false' ? false : !!AUTH.alwaysAuth | ||
return AUTH | ||
} | ||
function addKey (opts, obj, scope, key, objKey) { | ||
if (opts[key]) | ||
obj[objKey || key] = opts[key] | ||
const hasAuth = (regKey, opts) => ( | ||
opts[`${regKey}:_authToken`] || | ||
opts[`${regKey}:_auth`] || | ||
opts[`${regKey}:username`] && opts[`${regKey}:_password`] | ||
) | ||
if (scope && opts[`${scope}:${key}`]) | ||
obj[objKey || key] = opts[`${scope}:${key}`] | ||
} | ||
const getAuth = (uri, opts = {}) => { | ||
const { forceAuth } = opts | ||
if (!uri) | ||
throw new Error('URI is required') | ||
const regKey = regKeyFromURI(uri, forceAuth || opts) | ||
// Called a nerf dart in the main codebase. Used as a "safe" | ||
// key when fetching registry info from config. | ||
function registryKey (registry) { | ||
const parsed = new url.URL(registry) | ||
const formatted = url.format({ | ||
protocol: parsed.protocol, | ||
host: parsed.host, | ||
pathname: parsed.pathname, | ||
slashes: true, | ||
// we are only allowed to use what's in forceAuth if specified | ||
if (forceAuth && !regKey) { | ||
return new Auth({ | ||
scopeAuthKey: null, | ||
token: forceAuth._authToken, | ||
username: forceAuth.username, | ||
password: forceAuth._password || forceAuth.password, | ||
auth: forceAuth._auth || forceAuth.auth, | ||
}) | ||
} | ||
// no auth for this URI | ||
if (!regKey && opts.spec) { | ||
// If making a tarball request to a different base URI than the | ||
// 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 { spec } = opts | ||
const { scope: specScope, subSpec } = npa(spec) | ||
const subSpecScope = subSpec && subSpec.scope | ||
const scope = subSpec ? subSpecScope : specScope | ||
const scopeReg = scope && opts[`${scope}:registry`] | ||
const scopeAuthKey = scopeReg && regKeyFromURI(scopeReg, opts) | ||
return new Auth({ scopeAuthKey }) | ||
} | ||
const { | ||
[`${regKey}:_authToken`]: token, | ||
[`${regKey}:username`]: username, | ||
[`${regKey}:_password`]: password, | ||
[`${regKey}:_auth`]: auth, | ||
} = opts | ||
return new Auth({ | ||
scopeAuthKey: null, | ||
token, | ||
auth, | ||
username, | ||
password, | ||
}) | ||
return url.format(new url.URL('.', formatted)).replace(/^[^:]+:/, '') | ||
} | ||
class Auth { | ||
constructor ({ token, auth, username, password, scopeAuthKey }) { | ||
this.scopeAuthKey = scopeAuthKey | ||
this.token = null | ||
this.auth = null | ||
if (token) | ||
this.token = token | ||
else if (auth) | ||
this.auth = auth | ||
else if (username && password) { | ||
const p = Buffer.from(password, 'base64').toString('utf8') | ||
this.auth = Buffer.from(`${username}:${p}`, 'utf8').toString('base64') | ||
} | ||
} | ||
} | ||
module.exports = getAuth |
@@ -8,5 +8,4 @@ 'use strict' | ||
module.exports = checkResponse | ||
function checkResponse (method, res, registry, startTime, opts_ = {}) { | ||
const opts = { ...defaultOpts, ...opts_ } | ||
const checkResponse = async ({ method, uri, res, registry, startTime, auth, opts }) => { | ||
opts = { ...defaultOpts, ...opts } | ||
if (res.headers.has('npm-notice') && !res.headers.has('x-local-cache')) | ||
@@ -18,2 +17,13 @@ opts.log.notice('', res.headers.get('npm-notice')) | ||
logRequest(method, res, startTime, opts) | ||
if (auth && auth.scopeAuthKey && !auth.token && !auth.auth) { | ||
// we didn't have auth for THIS request, but we do have auth for | ||
// requests to the registry indicated by the spec's scope value. | ||
// Warn the user. | ||
opts.log.warn('registry', `No auth for URI, but auth present for scoped registry. | ||
URI: ${uri} | ||
Scoped Registry Key: ${auth.scopeAuthKey} | ||
More info here: https://github.com/npm/cli/wiki/No-auth-for-URI,-but-auth-present-for-scoped-registry`) | ||
} | ||
return checkErrors(method, res, startTime, opts) | ||
@@ -29,2 +39,3 @@ } else { | ||
} | ||
module.exports = checkResponse | ||
@@ -31,0 +42,0 @@ function logRequest (method, res, startTime, opts) { |
const pkg = require('./package.json') | ||
const ciDetect = require('@npmcli/ci-detect') | ||
module.exports = { | ||
isFromCI: ciDetect(), | ||
log: require('./silentlog.js'), | ||
@@ -6,0 +4,0 @@ maxSockets: 12, |
@@ -25,2 +25,3 @@ 'use strict' | ||
super() | ||
this.name = this.constructor.name | ||
this.headers = res.headers.raw() | ||
@@ -27,0 +28,0 @@ this.statusCode = res.status |
127
index.js
'use strict' | ||
const { HttpErrorAuthOTP } = require('./errors.js') | ||
const checkResponse = require('./check-response.js') | ||
@@ -30,10 +31,13 @@ const getAuth = require('./auth.js') | ||
} | ||
const registry = opts.registry = ( | ||
(opts.spec && pickRegistry(opts.spec, opts)) || | ||
opts.registry || | ||
/* istanbul ignore next */ | ||
'https://registry.npmjs.org/' | ||
) | ||
if (!urlIsValid(uri)) { | ||
// if we did not get a fully qualified URI, then we look at the registry | ||
// config or relevant scope to resolve it. | ||
const uriValid = urlIsValid(uri) | ||
let registry = opts.registry || defaultOpts.registry | ||
if (!uriValid) { | ||
registry = opts.registry = ( | ||
(opts.spec && pickRegistry(opts.spec, opts)) || | ||
opts.registry || | ||
registry | ||
) | ||
uri = `${ | ||
@@ -44,2 +48,4 @@ registry.trim().replace(/\/?$/g, '') | ||
}` | ||
// asserts that this is now valid | ||
new url.URL(uri) | ||
} | ||
@@ -51,3 +57,4 @@ | ||
const startTime = Date.now() | ||
const headers = getHeaders(registry, uri, opts) | ||
const auth = getAuth(uri, opts) | ||
const headers = getHeaders(uri, auth, opts) | ||
let body = opts.body | ||
@@ -98,31 +105,54 @@ const bodyIsStream = Minipass.isStream(body) | ||
const doFetch = (body) => fetch(uri, { | ||
agent: opts.agent, | ||
algorithms: opts.algorithms, | ||
body, | ||
cache: getCacheMode(opts), | ||
cacheManager: opts.cache, | ||
ca: opts.ca, | ||
cert: opts.cert, | ||
headers, | ||
integrity: opts.integrity, | ||
key: opts.key, | ||
localAddress: opts.localAddress, | ||
maxSockets: opts.maxSockets, | ||
memoize: opts.memoize, | ||
method: method, | ||
noProxy: opts.noProxy, | ||
proxy: opts.httpsProxy || opts.proxy, | ||
retry: opts.retry ? opts.retry : { | ||
retries: opts.fetchRetries, | ||
factor: opts.fetchRetryFactor, | ||
minTimeout: opts.fetchRetryMintimeout, | ||
maxTimeout: opts.fetchRetryMaxtimeout, | ||
}, | ||
strictSSL: opts.strictSSL, | ||
timeout: opts.timeout || 30 * 1000, | ||
}).then(res => checkResponse( | ||
method, res, registry, startTime, opts | ||
)) | ||
const doFetch = async body => { | ||
const p = fetch(uri, { | ||
agent: opts.agent, | ||
algorithms: opts.algorithms, | ||
body, | ||
cache: getCacheMode(opts), | ||
cacheManager: opts.cache, | ||
ca: opts.ca, | ||
cert: opts.cert, | ||
headers, | ||
integrity: opts.integrity, | ||
key: opts.key, | ||
localAddress: opts.localAddress, | ||
maxSockets: opts.maxSockets, | ||
memoize: opts.memoize, | ||
method: method, | ||
noProxy: opts.noProxy, | ||
proxy: opts.httpsProxy || opts.proxy, | ||
retry: opts.retry ? opts.retry : { | ||
retries: opts.fetchRetries, | ||
factor: opts.fetchRetryFactor, | ||
minTimeout: opts.fetchRetryMintimeout, | ||
maxTimeout: opts.fetchRetryMaxtimeout, | ||
}, | ||
strictSSL: opts.strictSSL, | ||
timeout: opts.timeout || 30 * 1000, | ||
}).then(res => checkResponse({ | ||
method, | ||
uri, | ||
res, | ||
registry, | ||
startTime, | ||
auth, | ||
opts, | ||
})) | ||
if (typeof opts.otpPrompt === 'function') { | ||
return p.catch(async er => { | ||
if (er instanceof HttpErrorAuthOTP) { | ||
// if otp fails to complete, we fail with that failure | ||
const otp = await opts.otpPrompt() | ||
// if no otp provided, throw the original HTTP error | ||
if (!otp) | ||
throw er | ||
return regFetch(uri, { ...opts, otp }) | ||
} | ||
throw er | ||
}) | ||
} else | ||
return p | ||
} | ||
return Promise.resolve(body).then(doFetch) | ||
@@ -158,3 +188,3 @@ } | ||
if (!registry) | ||
registry = opts.registry || 'https://registry.npmjs.org/' | ||
registry = opts.registry || defaultOpts.registry | ||
@@ -171,5 +201,4 @@ return registry | ||
function getHeaders (registry, uri, opts) { | ||
function getHeaders (uri, auth, opts) { | ||
const headers = Object.assign({ | ||
'npm-in-ci': !!opts.isFromCI, | ||
'user-agent': opts.userAgent, | ||
@@ -187,23 +216,13 @@ }, opts.headers || {}) | ||
const auth = getAuth(registry, opts) | ||
// If a tarball is hosted on a different place than the manifest, only send | ||
// credentials on `alwaysAuth` | ||
const shouldAuth = ( | ||
auth.alwaysAuth || | ||
new url.URL(uri).host === new url.URL(registry).host | ||
) | ||
if (shouldAuth && auth.token) | ||
if (auth.token) | ||
headers.authorization = `Bearer ${auth.token}` | ||
else if (shouldAuth && auth.username && auth.password) { | ||
const encoded = Buffer.from( | ||
`${auth.username}:${auth.password}`, 'utf8' | ||
).toString('base64') | ||
headers.authorization = `Basic ${encoded}` | ||
} else if (shouldAuth && auth._auth) | ||
headers.authorization = `Basic ${auth._auth}` | ||
else if (auth.auth) | ||
headers.authorization = `Basic ${auth.auth}` | ||
if (shouldAuth && auth.otp) | ||
headers['npm-otp'] = auth.otp | ||
if (opts.otp) | ||
headers['npm-otp'] = opts.otp | ||
return headers | ||
} |
{ | ||
"name": "npm-registry-fetch", | ||
"version": "9.0.0", | ||
"version": "10.0.0", | ||
"description": "Fetch-based http client for use with npm registry APIs", | ||
@@ -32,3 +32,2 @@ "main": "index.js", | ||
"dependencies": { | ||
"@npmcli/ci-detect": "^1.0.0", | ||
"lru-cache": "^6.0.0", | ||
@@ -35,0 +34,0 @@ "make-fetch-happen": "^8.0.9", |
@@ -312,9 +312,2 @@ # npm-registry-fetch | ||
##### <a name='opts-isFromCI'></a> `opts.isFromCI` | ||
* Type: Boolean | ||
* Default: Based on environment variables | ||
This is used to populate the `npm-in-ci` request header sent to the registry. | ||
##### <a name="opts-key"></a> `opts.key` | ||
@@ -429,2 +422,15 @@ | ||
##### <a name="opts-otpPrompt"></a> `opts.otpPrompt` | ||
* Type: Function | ||
* Default: null | ||
This is a method which will be called to provide an OTP if the server | ||
responds with a 401 response indicating that a one-time-password is | ||
required. | ||
It may return a promise, which must resolve to the OTP value to be used. | ||
If the method fails to provide an OTP value, then the fetch will fail with | ||
the auth error that indicated an OTP was needed. | ||
##### <a name="opts-password"></a> `opts.password` | ||
@@ -431,0 +437,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50391
7
507
636
2
- Removed@npmcli/ci-detect@^1.0.0
- Removed@npmcli/ci-detect@1.4.0(transitive)