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