11cb0ef41Sopenharmony_ciconst Table = require('cli-table3')
21cb0ef41Sopenharmony_ciconst log = require('../utils/log-shim.js')
31cb0ef41Sopenharmony_ciconst profile = require('npm-profile')
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciconst otplease = require('../utils/otplease.js')
61cb0ef41Sopenharmony_ciconst pulseTillDone = require('../utils/pulse-till-done.js')
71cb0ef41Sopenharmony_ciconst readUserInfo = require('../utils/read-user-info.js')
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciconst BaseCommand = require('../base-command.js')
101cb0ef41Sopenharmony_ciclass Token extends BaseCommand {
111cb0ef41Sopenharmony_ci  static description = 'Manage your authentication tokens'
121cb0ef41Sopenharmony_ci  static name = 'token'
131cb0ef41Sopenharmony_ci  static usage = ['list', 'revoke <id|token>', 'create [--read-only] [--cidr=list]']
141cb0ef41Sopenharmony_ci  static params = ['read-only', 'cidr', 'registry', 'otp']
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ci  static async completion (opts) {
171cb0ef41Sopenharmony_ci    const argv = opts.conf.argv.remain
181cb0ef41Sopenharmony_ci    const subcommands = ['list', 'revoke', 'create']
191cb0ef41Sopenharmony_ci    if (argv.length === 2) {
201cb0ef41Sopenharmony_ci      return subcommands
211cb0ef41Sopenharmony_ci    }
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ci    if (subcommands.includes(argv[2])) {
241cb0ef41Sopenharmony_ci      return []
251cb0ef41Sopenharmony_ci    }
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci    throw new Error(argv[2] + ' not recognized')
281cb0ef41Sopenharmony_ci  }
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci  async exec (args) {
311cb0ef41Sopenharmony_ci    log.gauge.show('token')
321cb0ef41Sopenharmony_ci    if (args.length === 0) {
331cb0ef41Sopenharmony_ci      return this.list()
341cb0ef41Sopenharmony_ci    }
351cb0ef41Sopenharmony_ci    switch (args[0]) {
361cb0ef41Sopenharmony_ci      case 'list':
371cb0ef41Sopenharmony_ci      case 'ls':
381cb0ef41Sopenharmony_ci        return this.list()
391cb0ef41Sopenharmony_ci      case 'delete':
401cb0ef41Sopenharmony_ci      case 'revoke':
411cb0ef41Sopenharmony_ci      case 'remove':
421cb0ef41Sopenharmony_ci      case 'rm':
431cb0ef41Sopenharmony_ci        return this.rm(args.slice(1))
441cb0ef41Sopenharmony_ci      case 'create':
451cb0ef41Sopenharmony_ci        return this.create(args.slice(1))
461cb0ef41Sopenharmony_ci      default:
471cb0ef41Sopenharmony_ci        throw this.usageError(`${args[0]} is not a recognized subcommand.`)
481cb0ef41Sopenharmony_ci    }
491cb0ef41Sopenharmony_ci  }
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci  async list () {
521cb0ef41Sopenharmony_ci    const conf = this.config()
531cb0ef41Sopenharmony_ci    log.info('token', 'getting list')
541cb0ef41Sopenharmony_ci    const tokens = await pulseTillDone.withPromise(profile.listTokens(conf))
551cb0ef41Sopenharmony_ci    if (conf.json) {
561cb0ef41Sopenharmony_ci      this.npm.output(JSON.stringify(tokens, null, 2))
571cb0ef41Sopenharmony_ci      return
581cb0ef41Sopenharmony_ci    } else if (conf.parseable) {
591cb0ef41Sopenharmony_ci      this.npm.output(['key', 'token', 'created', 'readonly', 'CIDR whitelist'].join('\t'))
601cb0ef41Sopenharmony_ci      tokens.forEach(token => {
611cb0ef41Sopenharmony_ci        this.npm.output(
621cb0ef41Sopenharmony_ci          [
631cb0ef41Sopenharmony_ci            token.key,
641cb0ef41Sopenharmony_ci            token.token,
651cb0ef41Sopenharmony_ci            token.created,
661cb0ef41Sopenharmony_ci            token.readonly ? 'true' : 'false',
671cb0ef41Sopenharmony_ci            token.cidr_whitelist ? token.cidr_whitelist.join(',') : '',
681cb0ef41Sopenharmony_ci          ].join('\t')
691cb0ef41Sopenharmony_ci        )
701cb0ef41Sopenharmony_ci      })
711cb0ef41Sopenharmony_ci      return
721cb0ef41Sopenharmony_ci    }
731cb0ef41Sopenharmony_ci    this.generateTokenIds(tokens, 6)
741cb0ef41Sopenharmony_ci    const idWidth = tokens.reduce((acc, token) => Math.max(acc, token.id.length), 0)
751cb0ef41Sopenharmony_ci    const table = new Table({
761cb0ef41Sopenharmony_ci      head: ['id', 'token', 'created', 'readonly', 'CIDR whitelist'],
771cb0ef41Sopenharmony_ci      colWidths: [Math.max(idWidth, 2) + 2, 9, 12, 10],
781cb0ef41Sopenharmony_ci    })
791cb0ef41Sopenharmony_ci    tokens.forEach(token => {
801cb0ef41Sopenharmony_ci      table.push([
811cb0ef41Sopenharmony_ci        token.id,
821cb0ef41Sopenharmony_ci        token.token + '…',
831cb0ef41Sopenharmony_ci        String(token.created).slice(0, 10),
841cb0ef41Sopenharmony_ci        token.readonly ? 'yes' : 'no',
851cb0ef41Sopenharmony_ci        token.cidr_whitelist ? token.cidr_whitelist.join(', ') : '',
861cb0ef41Sopenharmony_ci      ])
871cb0ef41Sopenharmony_ci    })
881cb0ef41Sopenharmony_ci    this.npm.output(table.toString())
891cb0ef41Sopenharmony_ci  }
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ci  async rm (args) {
921cb0ef41Sopenharmony_ci    if (args.length === 0) {
931cb0ef41Sopenharmony_ci      throw this.usageError('`<tokenKey>` argument is required.')
941cb0ef41Sopenharmony_ci    }
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci    const conf = this.config()
971cb0ef41Sopenharmony_ci    const toRemove = []
981cb0ef41Sopenharmony_ci    const progress = log.newItem('removing tokens', toRemove.length)
991cb0ef41Sopenharmony_ci    progress.info('token', 'getting existing list')
1001cb0ef41Sopenharmony_ci    const tokens = await pulseTillDone.withPromise(profile.listTokens(conf))
1011cb0ef41Sopenharmony_ci    args.forEach(id => {
1021cb0ef41Sopenharmony_ci      const matches = tokens.filter(token => token.key.indexOf(id) === 0)
1031cb0ef41Sopenharmony_ci      if (matches.length === 1) {
1041cb0ef41Sopenharmony_ci        toRemove.push(matches[0].key)
1051cb0ef41Sopenharmony_ci      } else if (matches.length > 1) {
1061cb0ef41Sopenharmony_ci        throw new Error(
1071cb0ef41Sopenharmony_ci          /* eslint-disable-next-line max-len */
1081cb0ef41Sopenharmony_ci          `Token ID "${id}" was ambiguous, a new token may have been created since you last ran \`npm token list\`.`
1091cb0ef41Sopenharmony_ci        )
1101cb0ef41Sopenharmony_ci      } else {
1111cb0ef41Sopenharmony_ci        const tokenMatches = tokens.some(t => id.indexOf(t.token) === 0)
1121cb0ef41Sopenharmony_ci        if (!tokenMatches) {
1131cb0ef41Sopenharmony_ci          throw new Error(`Unknown token id or value "${id}".`)
1141cb0ef41Sopenharmony_ci        }
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_ci        toRemove.push(id)
1171cb0ef41Sopenharmony_ci      }
1181cb0ef41Sopenharmony_ci    })
1191cb0ef41Sopenharmony_ci    await Promise.all(
1201cb0ef41Sopenharmony_ci      toRemove.map(key => {
1211cb0ef41Sopenharmony_ci        return otplease(this.npm, conf, c => profile.removeToken(key, c))
1221cb0ef41Sopenharmony_ci      })
1231cb0ef41Sopenharmony_ci    )
1241cb0ef41Sopenharmony_ci    if (conf.json) {
1251cb0ef41Sopenharmony_ci      this.npm.output(JSON.stringify(toRemove))
1261cb0ef41Sopenharmony_ci    } else if (conf.parseable) {
1271cb0ef41Sopenharmony_ci      this.npm.output(toRemove.join('\t'))
1281cb0ef41Sopenharmony_ci    } else {
1291cb0ef41Sopenharmony_ci      this.npm.output('Removed ' + toRemove.length + ' token' + (toRemove.length !== 1 ? 's' : ''))
1301cb0ef41Sopenharmony_ci    }
1311cb0ef41Sopenharmony_ci  }
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci  async create (args) {
1341cb0ef41Sopenharmony_ci    const conf = this.config()
1351cb0ef41Sopenharmony_ci    const cidr = conf.cidr
1361cb0ef41Sopenharmony_ci    const readonly = conf.readOnly
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_ci    const password = await readUserInfo.password()
1391cb0ef41Sopenharmony_ci    const validCIDR = await this.validateCIDRList(cidr)
1401cb0ef41Sopenharmony_ci    log.info('token', 'creating')
1411cb0ef41Sopenharmony_ci    const result = await pulseTillDone.withPromise(
1421cb0ef41Sopenharmony_ci      otplease(this.npm, conf, c => profile.createToken(password, readonly, validCIDR, c))
1431cb0ef41Sopenharmony_ci    )
1441cb0ef41Sopenharmony_ci    delete result.key
1451cb0ef41Sopenharmony_ci    delete result.updated
1461cb0ef41Sopenharmony_ci    if (conf.json) {
1471cb0ef41Sopenharmony_ci      this.npm.output(JSON.stringify(result))
1481cb0ef41Sopenharmony_ci    } else if (conf.parseable) {
1491cb0ef41Sopenharmony_ci      Object.keys(result).forEach(k => this.npm.output(k + '\t' + result[k]))
1501cb0ef41Sopenharmony_ci    } else {
1511cb0ef41Sopenharmony_ci      const table = new Table()
1521cb0ef41Sopenharmony_ci      for (const k of Object.keys(result)) {
1531cb0ef41Sopenharmony_ci        table.push({ [this.npm.chalk.bold(k)]: String(result[k]) })
1541cb0ef41Sopenharmony_ci      }
1551cb0ef41Sopenharmony_ci      this.npm.output(table.toString())
1561cb0ef41Sopenharmony_ci    }
1571cb0ef41Sopenharmony_ci  }
1581cb0ef41Sopenharmony_ci
1591cb0ef41Sopenharmony_ci  config () {
1601cb0ef41Sopenharmony_ci    const conf = { ...this.npm.flatOptions }
1611cb0ef41Sopenharmony_ci    const creds = this.npm.config.getCredentialsByURI(conf.registry)
1621cb0ef41Sopenharmony_ci    if (creds.token) {
1631cb0ef41Sopenharmony_ci      conf.auth = { token: creds.token }
1641cb0ef41Sopenharmony_ci    } else if (creds.username) {
1651cb0ef41Sopenharmony_ci      conf.auth = {
1661cb0ef41Sopenharmony_ci        basic: {
1671cb0ef41Sopenharmony_ci          username: creds.username,
1681cb0ef41Sopenharmony_ci          password: creds.password,
1691cb0ef41Sopenharmony_ci        },
1701cb0ef41Sopenharmony_ci      }
1711cb0ef41Sopenharmony_ci    } else if (creds.auth) {
1721cb0ef41Sopenharmony_ci      const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2)
1731cb0ef41Sopenharmony_ci      conf.auth = {
1741cb0ef41Sopenharmony_ci        basic: {
1751cb0ef41Sopenharmony_ci          username: auth[0],
1761cb0ef41Sopenharmony_ci          password: auth[1],
1771cb0ef41Sopenharmony_ci        },
1781cb0ef41Sopenharmony_ci      }
1791cb0ef41Sopenharmony_ci    } else {
1801cb0ef41Sopenharmony_ci      conf.auth = {}
1811cb0ef41Sopenharmony_ci    }
1821cb0ef41Sopenharmony_ci
1831cb0ef41Sopenharmony_ci    if (conf.otp) {
1841cb0ef41Sopenharmony_ci      conf.auth.otp = conf.otp
1851cb0ef41Sopenharmony_ci    }
1861cb0ef41Sopenharmony_ci    return conf
1871cb0ef41Sopenharmony_ci  }
1881cb0ef41Sopenharmony_ci
1891cb0ef41Sopenharmony_ci  invalidCIDRError (msg) {
1901cb0ef41Sopenharmony_ci    return Object.assign(new Error(msg), { code: 'EINVALIDCIDR' })
1911cb0ef41Sopenharmony_ci  }
1921cb0ef41Sopenharmony_ci
1931cb0ef41Sopenharmony_ci  generateTokenIds (tokens, minLength) {
1941cb0ef41Sopenharmony_ci    const byId = {}
1951cb0ef41Sopenharmony_ci    for (const token of tokens) {
1961cb0ef41Sopenharmony_ci      token.id = token.key
1971cb0ef41Sopenharmony_ci      for (let ii = minLength; ii < token.key.length; ++ii) {
1981cb0ef41Sopenharmony_ci        const match = tokens.some(
1991cb0ef41Sopenharmony_ci          ot => ot !== token && ot.key.slice(0, ii) === token.key.slice(0, ii)
2001cb0ef41Sopenharmony_ci        )
2011cb0ef41Sopenharmony_ci        if (!match) {
2021cb0ef41Sopenharmony_ci          token.id = token.key.slice(0, ii)
2031cb0ef41Sopenharmony_ci          break
2041cb0ef41Sopenharmony_ci        }
2051cb0ef41Sopenharmony_ci      }
2061cb0ef41Sopenharmony_ci      byId[token.id] = token
2071cb0ef41Sopenharmony_ci    }
2081cb0ef41Sopenharmony_ci    return byId
2091cb0ef41Sopenharmony_ci  }
2101cb0ef41Sopenharmony_ci
2111cb0ef41Sopenharmony_ci  async validateCIDRList (cidrs) {
2121cb0ef41Sopenharmony_ci    const { v4: isCidrV4, v6: isCidrV6 } = await import('is-cidr')
2131cb0ef41Sopenharmony_ci    const maybeList = [].concat(cidrs).filter(Boolean)
2141cb0ef41Sopenharmony_ci    const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList
2151cb0ef41Sopenharmony_ci    for (const cidr of list) {
2161cb0ef41Sopenharmony_ci      if (isCidrV6(cidr)) {
2171cb0ef41Sopenharmony_ci        throw this.invalidCIDRError(
2181cb0ef41Sopenharmony_ci          'CIDR whitelist can only contain IPv4 addresses, ' + cidr + ' is IPv6'
2191cb0ef41Sopenharmony_ci        )
2201cb0ef41Sopenharmony_ci      }
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_ci      if (!isCidrV4(cidr)) {
2231cb0ef41Sopenharmony_ci        throw this.invalidCIDRError('CIDR whitelist contains invalid CIDR entry: ' + cidr)
2241cb0ef41Sopenharmony_ci      }
2251cb0ef41Sopenharmony_ci    }
2261cb0ef41Sopenharmony_ci    return list
2271cb0ef41Sopenharmony_ci  }
2281cb0ef41Sopenharmony_ci}
2291cb0ef41Sopenharmony_cimodule.exports = Token
230