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