11cb0ef41Sopenharmony_ci'use strict' 21cb0ef41Sopenharmony_ciconst fs = require('fs') 31cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg') 41cb0ef41Sopenharmony_ciconst { URL } = require('url') 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ci// Find the longest registry key that is used for some kind of auth 71cb0ef41Sopenharmony_ci// in the options. Returns the registry key and the auth config. 81cb0ef41Sopenharmony_ciconst regFromURI = (uri, opts) => { 91cb0ef41Sopenharmony_ci const parsed = new URL(uri) 101cb0ef41Sopenharmony_ci // try to find a config key indicating we have auth for this registry 111cb0ef41Sopenharmony_ci // can be one of :_authToken, :_auth, :_password and :username, or 121cb0ef41Sopenharmony_ci // :certfile and :keyfile 131cb0ef41Sopenharmony_ci // We walk up the "path" until we're left with just //<host>[:<port>], 141cb0ef41Sopenharmony_ci // stopping when we reach '//'. 151cb0ef41Sopenharmony_ci let regKey = `//${parsed.host}${parsed.pathname}` 161cb0ef41Sopenharmony_ci while (regKey.length > '//'.length) { 171cb0ef41Sopenharmony_ci const authKey = hasAuth(regKey, opts) 181cb0ef41Sopenharmony_ci // got some auth for this URI 191cb0ef41Sopenharmony_ci if (authKey) { 201cb0ef41Sopenharmony_ci return { regKey, authKey } 211cb0ef41Sopenharmony_ci } 221cb0ef41Sopenharmony_ci 231cb0ef41Sopenharmony_ci // can be either //host/some/path/:_auth or //host/some/path:_auth 241cb0ef41Sopenharmony_ci // walk up by removing EITHER what's after the slash OR the slash itself 251cb0ef41Sopenharmony_ci regKey = regKey.replace(/([^/]+|\/)$/, '') 261cb0ef41Sopenharmony_ci } 271cb0ef41Sopenharmony_ci return { regKey: false, authKey: null } 281cb0ef41Sopenharmony_ci} 291cb0ef41Sopenharmony_ci 301cb0ef41Sopenharmony_ci// Not only do we want to know if there is auth, but if we are calling `npm 311cb0ef41Sopenharmony_ci// logout` we want to know what config value specifically provided it. This is 321cb0ef41Sopenharmony_ci// so we can look up where the config came from to delete it (i.e. user vs 331cb0ef41Sopenharmony_ci// project) 341cb0ef41Sopenharmony_ciconst hasAuth = (regKey, opts) => { 351cb0ef41Sopenharmony_ci if (opts[`${regKey}:_authToken`]) { 361cb0ef41Sopenharmony_ci return '_authToken' 371cb0ef41Sopenharmony_ci } 381cb0ef41Sopenharmony_ci if (opts[`${regKey}:_auth`]) { 391cb0ef41Sopenharmony_ci return '_auth' 401cb0ef41Sopenharmony_ci } 411cb0ef41Sopenharmony_ci if (opts[`${regKey}:username`] && opts[`${regKey}:_password`]) { 421cb0ef41Sopenharmony_ci // 'password' can be inferred to also be present 431cb0ef41Sopenharmony_ci return 'username' 441cb0ef41Sopenharmony_ci } 451cb0ef41Sopenharmony_ci if (opts[`${regKey}:certfile`] && opts[`${regKey}:keyfile`]) { 461cb0ef41Sopenharmony_ci // 'keyfile' can be inferred to also be present 471cb0ef41Sopenharmony_ci return 'certfile' 481cb0ef41Sopenharmony_ci } 491cb0ef41Sopenharmony_ci return false 501cb0ef41Sopenharmony_ci} 511cb0ef41Sopenharmony_ci 521cb0ef41Sopenharmony_ciconst sameHost = (a, b) => { 531cb0ef41Sopenharmony_ci const parsedA = new URL(a) 541cb0ef41Sopenharmony_ci const parsedB = new URL(b) 551cb0ef41Sopenharmony_ci return parsedA.host === parsedB.host 561cb0ef41Sopenharmony_ci} 571cb0ef41Sopenharmony_ci 581cb0ef41Sopenharmony_ciconst getRegistry = opts => { 591cb0ef41Sopenharmony_ci const { spec } = opts 601cb0ef41Sopenharmony_ci const { scope: specScope, subSpec } = spec ? npa(spec) : {} 611cb0ef41Sopenharmony_ci const subSpecScope = subSpec && subSpec.scope 621cb0ef41Sopenharmony_ci const scope = subSpec ? subSpecScope : specScope 631cb0ef41Sopenharmony_ci const scopeReg = scope && opts[`${scope}:registry`] 641cb0ef41Sopenharmony_ci return scopeReg || opts.registry 651cb0ef41Sopenharmony_ci} 661cb0ef41Sopenharmony_ci 671cb0ef41Sopenharmony_ciconst maybeReadFile = file => { 681cb0ef41Sopenharmony_ci try { 691cb0ef41Sopenharmony_ci return fs.readFileSync(file, 'utf8') 701cb0ef41Sopenharmony_ci } catch (er) { 711cb0ef41Sopenharmony_ci if (er.code !== 'ENOENT') { 721cb0ef41Sopenharmony_ci throw er 731cb0ef41Sopenharmony_ci } 741cb0ef41Sopenharmony_ci return null 751cb0ef41Sopenharmony_ci } 761cb0ef41Sopenharmony_ci} 771cb0ef41Sopenharmony_ci 781cb0ef41Sopenharmony_ciconst getAuth = (uri, opts = {}) => { 791cb0ef41Sopenharmony_ci const { forceAuth } = opts 801cb0ef41Sopenharmony_ci if (!uri) { 811cb0ef41Sopenharmony_ci throw new Error('URI is required') 821cb0ef41Sopenharmony_ci } 831cb0ef41Sopenharmony_ci const { regKey, authKey } = regFromURI(uri, forceAuth || opts) 841cb0ef41Sopenharmony_ci 851cb0ef41Sopenharmony_ci // we are only allowed to use what's in forceAuth if specified 861cb0ef41Sopenharmony_ci if (forceAuth && !regKey) { 871cb0ef41Sopenharmony_ci return new Auth({ 881cb0ef41Sopenharmony_ci // if we force auth we don't want to refer back to anything in config 891cb0ef41Sopenharmony_ci regKey: false, 901cb0ef41Sopenharmony_ci authKey: null, 911cb0ef41Sopenharmony_ci scopeAuthKey: null, 921cb0ef41Sopenharmony_ci token: forceAuth._authToken || forceAuth.token, 931cb0ef41Sopenharmony_ci username: forceAuth.username, 941cb0ef41Sopenharmony_ci password: forceAuth._password || forceAuth.password, 951cb0ef41Sopenharmony_ci auth: forceAuth._auth || forceAuth.auth, 961cb0ef41Sopenharmony_ci certfile: forceAuth.certfile, 971cb0ef41Sopenharmony_ci keyfile: forceAuth.keyfile, 981cb0ef41Sopenharmony_ci }) 991cb0ef41Sopenharmony_ci } 1001cb0ef41Sopenharmony_ci 1011cb0ef41Sopenharmony_ci // no auth for this URI, but might have it for the registry 1021cb0ef41Sopenharmony_ci if (!regKey) { 1031cb0ef41Sopenharmony_ci const registry = getRegistry(opts) 1041cb0ef41Sopenharmony_ci if (registry && uri !== registry && sameHost(uri, registry)) { 1051cb0ef41Sopenharmony_ci return getAuth(registry, opts) 1061cb0ef41Sopenharmony_ci } else if (registry !== opts.registry) { 1071cb0ef41Sopenharmony_ci // If making a tarball request to a different base URI than the 1081cb0ef41Sopenharmony_ci // registry where we logged in, but the same auth SHOULD be sent 1091cb0ef41Sopenharmony_ci // to that artifact host, then we track where it was coming in from, 1101cb0ef41Sopenharmony_ci // and warn the user if we get a 4xx error on it. 1111cb0ef41Sopenharmony_ci const { regKey: scopeAuthKey, authKey: _authKey } = regFromURI(registry, opts) 1121cb0ef41Sopenharmony_ci return new Auth({ scopeAuthKey, regKey: scopeAuthKey, authKey: _authKey }) 1131cb0ef41Sopenharmony_ci } 1141cb0ef41Sopenharmony_ci } 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_ci const { 1171cb0ef41Sopenharmony_ci [`${regKey}:_authToken`]: token, 1181cb0ef41Sopenharmony_ci [`${regKey}:username`]: username, 1191cb0ef41Sopenharmony_ci [`${regKey}:_password`]: password, 1201cb0ef41Sopenharmony_ci [`${regKey}:_auth`]: auth, 1211cb0ef41Sopenharmony_ci [`${regKey}:certfile`]: certfile, 1221cb0ef41Sopenharmony_ci [`${regKey}:keyfile`]: keyfile, 1231cb0ef41Sopenharmony_ci } = opts 1241cb0ef41Sopenharmony_ci 1251cb0ef41Sopenharmony_ci return new Auth({ 1261cb0ef41Sopenharmony_ci scopeAuthKey: null, 1271cb0ef41Sopenharmony_ci regKey, 1281cb0ef41Sopenharmony_ci authKey, 1291cb0ef41Sopenharmony_ci token, 1301cb0ef41Sopenharmony_ci auth, 1311cb0ef41Sopenharmony_ci username, 1321cb0ef41Sopenharmony_ci password, 1331cb0ef41Sopenharmony_ci certfile, 1341cb0ef41Sopenharmony_ci keyfile, 1351cb0ef41Sopenharmony_ci }) 1361cb0ef41Sopenharmony_ci} 1371cb0ef41Sopenharmony_ci 1381cb0ef41Sopenharmony_ciclass Auth { 1391cb0ef41Sopenharmony_ci constructor ({ 1401cb0ef41Sopenharmony_ci token, 1411cb0ef41Sopenharmony_ci auth, 1421cb0ef41Sopenharmony_ci username, 1431cb0ef41Sopenharmony_ci password, 1441cb0ef41Sopenharmony_ci scopeAuthKey, 1451cb0ef41Sopenharmony_ci certfile, 1461cb0ef41Sopenharmony_ci keyfile, 1471cb0ef41Sopenharmony_ci regKey, 1481cb0ef41Sopenharmony_ci authKey, 1491cb0ef41Sopenharmony_ci }) { 1501cb0ef41Sopenharmony_ci // same as regKey but only present for scoped auth. Should have been named scopeRegKey 1511cb0ef41Sopenharmony_ci this.scopeAuthKey = scopeAuthKey 1521cb0ef41Sopenharmony_ci // `${regKey}:${authKey}` will get you back to the auth config that gave us auth 1531cb0ef41Sopenharmony_ci this.regKey = regKey 1541cb0ef41Sopenharmony_ci this.authKey = authKey 1551cb0ef41Sopenharmony_ci this.token = null 1561cb0ef41Sopenharmony_ci this.auth = null 1571cb0ef41Sopenharmony_ci this.isBasicAuth = false 1581cb0ef41Sopenharmony_ci this.cert = null 1591cb0ef41Sopenharmony_ci this.key = null 1601cb0ef41Sopenharmony_ci if (token) { 1611cb0ef41Sopenharmony_ci this.token = token 1621cb0ef41Sopenharmony_ci } else if (auth) { 1631cb0ef41Sopenharmony_ci this.auth = auth 1641cb0ef41Sopenharmony_ci } else if (username && password) { 1651cb0ef41Sopenharmony_ci const p = Buffer.from(password, 'base64').toString('utf8') 1661cb0ef41Sopenharmony_ci this.auth = Buffer.from(`${username}:${p}`, 'utf8').toString('base64') 1671cb0ef41Sopenharmony_ci this.isBasicAuth = true 1681cb0ef41Sopenharmony_ci } 1691cb0ef41Sopenharmony_ci // mTLS may be used in conjunction with another auth method above 1701cb0ef41Sopenharmony_ci if (certfile && keyfile) { 1711cb0ef41Sopenharmony_ci const cert = maybeReadFile(certfile, 'utf-8') 1721cb0ef41Sopenharmony_ci const key = maybeReadFile(keyfile, 'utf-8') 1731cb0ef41Sopenharmony_ci if (cert && key) { 1741cb0ef41Sopenharmony_ci this.cert = cert 1751cb0ef41Sopenharmony_ci this.key = key 1761cb0ef41Sopenharmony_ci } 1771cb0ef41Sopenharmony_ci } 1781cb0ef41Sopenharmony_ci } 1791cb0ef41Sopenharmony_ci} 1801cb0ef41Sopenharmony_ci 1811cb0ef41Sopenharmony_cimodule.exports = getAuth 182