File Explorer

/var/lang/lib/node_modules/npm/lib/commands

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

1 dir
67 files
token.js8.0 KB · 282 lines
const { log, output, META } = require('proc-log')const fetch = require('npm-registry-fetch')const { otplease } = require('../utils/auth.js')const readUserInfo = require('../utils/read-user-info.js')const BaseCommand = require('../base-cmd.js') async function paginate (href, opts, items = []) {  while (href) {    const result = await fetch.json(href, opts)    items = items.concat(result.objects)    href = result.urls.next  }  return items} class Token extends BaseCommand {  static description = 'Manage your authentication tokens'  static name = 'token'  static usage = ['list', 'revoke <id|token>', 'create']  static params = ['name',    'token-description',    'expires',    'packages',    'packages-all',    'scopes',    'orgs',    'packages-and-scopes-permission',    'orgs-permission',    'cidr',    'bypass-2fa',    'password',    'registry',    'otp',    'read-only',  ]   static async completion (opts) {    const argv = opts.conf.argv.remain    const subcommands = ['list', 'revoke', 'create']    if (argv.length === 2) {      return subcommands    }     if (subcommands.includes(argv[2])) {      return []    }     throw new Error(argv[2] + ' not recognized')  }   async exec (args) {    if (args.length === 0) {      return this.list()    }    switch (args[0]) {      case 'list':      case 'ls':        return this.list()      case 'rm':      case 'delete':      case 'revoke':      case 'remove':        return this.rm(args.slice(1))      case 'create':        return this.create(args.slice(1))      default:        throw this.usageError(`${args[0]} is not a recognized subcommand.`)    }  }   async list () {    const json = this.npm.config.get('json')    const parseable = this.npm.config.get('parseable')    log.info('token', 'getting list')    const tokens = await paginate('/-/npm/v1/tokens', this.npm.flatOptions)    if (json) {      output.buffer(tokens)      return    }    if (parseable) {      output.standard(['key', 'token', 'created', 'readonly', 'CIDR whitelist'].join('\t'))      tokens.forEach(token => {        output.standard(          [            token.key,            token.token,            token.created,            token.readonly ? 'true' : 'false',            token.cidr_whitelist ? token.cidr_whitelist.join(',') : '',          ].join('\t')        )      })      return    }    this.generateTokenIds(tokens, 6)    const chalk = this.npm.chalk    for (const token of tokens) {      const created = String(token.created).slice(0, 10)      output.standard(`${chalk.blue('Token')} ${token.token}… with id ${chalk.cyan(token.id)} created ${created}`)      if (token.cidr_whitelist) {        output.standard(`with IP whitelist: ${chalk.green(token.cidr_whitelist.join(','))}`)      }      output.standard()    }  }   async rm (args) {    if (args.length === 0) {      throw this.usageError('`<tokenKey>` argument is required.')    }     const json = this.npm.config.get('json')    const parseable = this.npm.config.get('parseable')    const toRemove = []    log.info('token', `removing ${toRemove.length} tokens`)    const tokens = await paginate('/-/npm/v1/tokens', this.npm.flatOptions)    for (const id of args) {      const matches = tokens.filter(token => token.key.indexOf(id) === 0)      if (matches.length === 1) {        toRemove.push(matches[0].key)      } else if (matches.length > 1) {        throw new Error(          `Token ID "${id}" was ambiguous, a new token may have been created since you last ran \`npm token list\`.`        )      } else {        const tokenMatches = tokens.some(t => id.indexOf(t.token) === 0)        if (!tokenMatches) {          throw new Error(`Unknown token id or value "${id}".`)        }         toRemove.push(id)      }    }    for (const tokenKey of toRemove) {      await otplease(this.npm, this.npm.flatOptions, opts =>        fetch(`/-/npm/v1/tokens/token/${tokenKey}`, {          ...opts,          method: 'DELETE',          ignoreBody: true,        })      )    }    if (json) {      output.buffer(toRemove)    } else if (parseable) {      output.standard(toRemove.join('\t'))    } else {      output.standard('Removed ' + toRemove.length + ' token' + (toRemove.length !== 1 ? 's' : ''))    }  }   async create () {    const json = this.npm.config.get('json')    const parseable = this.npm.config.get('parseable')    const cidr = this.npm.config.get('cidr')    const name = this.npm.config.get('name')    const tokenDescription = this.npm.config.get('token-description')    const expires = this.npm.config.get('expires')    const packages = this.npm.config.get('packages')    const packagesAll = this.npm.config.get('packages-all')    const scopes = this.npm.config.get('scopes')    const orgs = this.npm.config.get('orgs')    const packagesAndScopesPermission = this.npm.config.get('packages-and-scopes-permission')    const orgsPermission = this.npm.config.get('orgs-permission')    const bypassTwoFactor = this.npm.config.get('bypass-2fa')    let password = this.npm.config.get('password')     const validCIDR = await this.validateCIDRList(cidr)     /* istanbul ignore if - skip testing read input */    if (!password) {      password = await readUserInfo.password()    }     const tokenData = {      name: name,      password: password,    }     if (tokenDescription) {      tokenData.description = tokenDescription    }     if (packages?.length > 0) {      tokenData.packages = packages    }    if (packagesAll) {      tokenData.packages_all = true    }    if (scopes?.length > 0) {      tokenData.scopes = scopes    }    if (orgs?.length > 0) {      tokenData.orgs = orgs    }     if (packagesAndScopesPermission) {      tokenData.packages_and_scopes_permission = packagesAndScopesPermission    }    if (orgsPermission) {      tokenData.orgs_permission = orgsPermission    }     // Add expiration in days    if (expires) {      tokenData.expires = parseInt(expires, 10)    }     // Add optional fields    if (validCIDR?.length > 0) {      tokenData.cidr_whitelist = validCIDR    }    if (bypassTwoFactor) {      tokenData.bypass_2fa = true    }     log.info('token', 'creating')    const result = await otplease(this.npm, this.npm.flatOptions, opts =>      fetch.json('/-/npm/v1/tokens', {        ...opts,        method: 'POST',        body: tokenData,      })    )    delete result.key    delete result.updated    if (json) {      output.buffer(result)    } else if (parseable) {      Object.keys(result).forEach(k => output.standard(k + '\t' + result[k]))    } else {      const chalk = this.npm.chalk      output.standard(`Created token ${result.token}`, { [META]: true, redact: false })      if (result.cidr_whitelist?.length) {        output.standard(`with IP whitelist: ${chalk.green(result.cidr_whitelist.join(','))}`)      }      if (result.expires) {        output.standard(`expires: ${result.expires}`)      }    }  }   invalidCIDRError (msg) {    return Object.assign(new Error(msg), { code: 'EINVALIDCIDR' })  }   generateTokenIds (tokens, minLength) {    for (const token of tokens) {      token.id = token.key      for (let ii = minLength; ii < token.key.length; ++ii) {        const match = tokens.some(          ot => ot !== token && ot.key.slice(0, ii) === token.key.slice(0, ii)        )        if (!match) {          token.id = token.key.slice(0, ii)          break        }      }    }  }   async validateCIDRList (cidrs) {    const { v4: isCidrV4, v6: isCidrV6 } = await import('is-cidr')    const maybeList = [].concat(cidrs).filter(Boolean)    const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList    for (const cidr of list) {      if (isCidrV6(cidr)) {        throw this.invalidCIDRError(          `CIDR whitelist can only contain IPv4 addresses, ${cidr} is IPv6`        )      }       if (!isCidrV4(cidr)) {        throw this.invalidCIDRError(`CIDR whitelist contains invalid CIDR entry: ${cidr}`)      }    }    return list  }} module.exports = Token