Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
token.js282 linesDownload Raw Back to commands
1const { log, output, META } = require('proc-log')
2const fetch = require('npm-registry-fetch')
3const { otplease } = require('../utils/auth.js')
4const readUserInfo = require('../utils/read-user-info.js')
5const BaseCommand = require('../base-cmd.js')
6
7async function paginate (href, opts, items = []) {
8  while (href) {
9    const result = await fetch.json(href, opts)
10    items = items.concat(result.objects)
11    href = result.urls.next
12  }
13  return items
14}
15
16class Token extends BaseCommand {
17  static description = 'Manage your authentication tokens'
18  static name = 'token'
19  static usage = ['list', 'revoke <id|token>', 'create']
20  static params = ['name',
21    'token-description',
22    'expires',
23    'packages',
24    'packages-all',
25    'scopes',
26    'orgs',
27    'packages-and-scopes-permission',
28    'orgs-permission',
29    'cidr',
30    'bypass-2fa',
31    'password',
32    'registry',
33    'otp',
34    'read-only',
35  ]
36
37  static async completion (opts) {
38    const argv = opts.conf.argv.remain
39    const subcommands = ['list', 'revoke', 'create']
40    if (argv.length === 2) {
41      return subcommands
42    }
43
44    if (subcommands.includes(argv[2])) {
45      return []
46    }
47
48    throw new Error(argv[2] + ' not recognized')
49  }
50
51  async exec (args) {
52    if (args.length === 0) {
53      return this.list()
54    }
55    switch (args[0]) {
56      case 'list':
57      case 'ls':
58        return this.list()
59      case 'rm':
60      case 'delete':
61      case 'revoke':
62      case 'remove':
63        return this.rm(args.slice(1))
64      case 'create':
65        return this.create(args.slice(1))
66      default:
67        throw this.usageError(`${args[0]} is not a recognized subcommand.`)
68    }
69  }
70
71  async list () {
72    const json = this.npm.config.get('json')
73    const parseable = this.npm.config.get('parseable')
74    log.info('token', 'getting list')
75    const tokens = await paginate('/-/npm/v1/tokens', this.npm.flatOptions)
76    if (json) {
77      output.buffer(tokens)
78      return
79    }
80    if (parseable) {
81      output.standard(['key', 'token', 'created', 'readonly', 'CIDR whitelist'].join('\t'))
82      tokens.forEach(token => {
83        output.standard(
84          [
85            token.key,
86            token.token,
87            token.created,
88            token.readonly ? 'true' : 'false',
89            token.cidr_whitelist ? token.cidr_whitelist.join(',') : '',
90          ].join('\t')
91        )
92      })
93      return
94    }
95    this.generateTokenIds(tokens, 6)
96    const chalk = this.npm.chalk
97    for (const token of tokens) {
98      const created = String(token.created).slice(0, 10)
99      output.standard(`${chalk.blue('Token')} ${token.token}… with id ${chalk.cyan(token.id)} created ${created}`)
100      if (token.cidr_whitelist) {
101        output.standard(`with IP whitelist: ${chalk.green(token.cidr_whitelist.join(','))}`)
102      }
103      output.standard()
104    }
105  }
106
107  async rm (args) {
108    if (args.length === 0) {
109      throw this.usageError('`<tokenKey>` argument is required.')
110    }
111
112    const json = this.npm.config.get('json')
113    const parseable = this.npm.config.get('parseable')
114    const toRemove = []
115    log.info('token', `removing ${toRemove.length} tokens`)
116    const tokens = await paginate('/-/npm/v1/tokens', this.npm.flatOptions)
117    for (const id of args) {
118      const matches = tokens.filter(token => token.key.indexOf(id) === 0)
119      if (matches.length === 1) {
120        toRemove.push(matches[0].key)
121      } else if (matches.length > 1) {
122        throw new Error(
123          `Token ID "${id}" was ambiguous, a new token may have been created since you last ran \`npm token list\`.`
124        )
125      } else {
126        const tokenMatches = tokens.some(t => id.indexOf(t.token) === 0)
127        if (!tokenMatches) {
128          throw new Error(`Unknown token id or value "${id}".`)
129        }
130
131        toRemove.push(id)
132      }
133    }
134    for (const tokenKey of toRemove) {
135      await otplease(this.npm, this.npm.flatOptions, opts =>
136        fetch(`/-/npm/v1/tokens/token/${tokenKey}`, {
137          ...opts,
138          method: 'DELETE',
139          ignoreBody: true,
140        })
141      )
142    }
143    if (json) {
144      output.buffer(toRemove)
145    } else if (parseable) {
146      output.standard(toRemove.join('\t'))
147    } else {
148      output.standard('Removed ' + toRemove.length + ' token' + (toRemove.length !== 1 ? 's' : ''))
149    }
150  }
151
152  async create () {
153    const json = this.npm.config.get('json')
154    const parseable = this.npm.config.get('parseable')
155    const cidr = this.npm.config.get('cidr')
156    const name = this.npm.config.get('name')
157    const tokenDescription = this.npm.config.get('token-description')
158    const expires = this.npm.config.get('expires')
159    const packages = this.npm.config.get('packages')
160    const packagesAll = this.npm.config.get('packages-all')
161    const scopes = this.npm.config.get('scopes')
162    const orgs = this.npm.config.get('orgs')
163    const packagesAndScopesPermission = this.npm.config.get('packages-and-scopes-permission')
164    const orgsPermission = this.npm.config.get('orgs-permission')
165    const bypassTwoFactor = this.npm.config.get('bypass-2fa')
166    let password = this.npm.config.get('password')
167
168    const validCIDR = await this.validateCIDRList(cidr)
169
170    /* istanbul ignore if - skip testing read input */
171    if (!password) {
172      password = await readUserInfo.password()
173    }
174
175    const tokenData = {
176      name: name,
177      password: password,
178    }
179
180    if (tokenDescription) {
181      tokenData.description = tokenDescription
182    }
183
184    if (packages?.length > 0) {
185      tokenData.packages = packages
186    }
187    if (packagesAll) {
188      tokenData.packages_all = true
189    }
190    if (scopes?.length > 0) {
191      tokenData.scopes = scopes
192    }
193    if (orgs?.length > 0) {
194      tokenData.orgs = orgs
195    }
196
197    if (packagesAndScopesPermission) {
198      tokenData.packages_and_scopes_permission = packagesAndScopesPermission
199    }
200    if (orgsPermission) {
201      tokenData.orgs_permission = orgsPermission
202    }
203
204    // Add expiration in days
205    if (expires) {
206      tokenData.expires = parseInt(expires, 10)
207    }
208
209    // Add optional fields
210    if (validCIDR?.length > 0) {
211      tokenData.cidr_whitelist = validCIDR
212    }
213    if (bypassTwoFactor) {
214      tokenData.bypass_2fa = true
215    }
216
217    log.info('token', 'creating')
218    const result = await otplease(this.npm, this.npm.flatOptions, opts =>
219      fetch.json('/-/npm/v1/tokens', {
220        ...opts,
221        method: 'POST',
222        body: tokenData,
223      })
224    )
225    delete result.key
226    delete result.updated
227    if (json) {
228      output.buffer(result)
229    } else if (parseable) {
230      Object.keys(result).forEach(k => output.standard(k + '\t' + result[k]))
231    } else {
232      const chalk = this.npm.chalk
233      output.standard(`Created token ${result.token}`, { [META]: true, redact: false })
234      if (result.cidr_whitelist?.length) {
235        output.standard(`with IP whitelist: ${chalk.green(result.cidr_whitelist.join(','))}`)
236      }
237      if (result.expires) {
238        output.standard(`expires: ${result.expires}`)
239      }
240    }
241  }
242
243  invalidCIDRError (msg) {
244    return Object.assign(new Error(msg), { code: 'EINVALIDCIDR' })
245  }
246
247  generateTokenIds (tokens, minLength) {
248    for (const token of tokens) {
249      token.id = token.key
250      for (let ii = minLength; ii < token.key.length; ++ii) {
251        const match = tokens.some(
252          ot => ot !== token && ot.key.slice(0, ii) === token.key.slice(0, ii)
253        )
254        if (!match) {
255          token.id = token.key.slice(0, ii)
256          break
257        }
258      }
259    }
260  }
261
262  async validateCIDRList (cidrs) {
263    const { v4: isCidrV4, v6: isCidrV6 } = await import('is-cidr')
264    const maybeList = [].concat(cidrs).filter(Boolean)
265    const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList
266    for (const cidr of list) {
267      if (isCidrV6(cidr)) {
268        throw this.invalidCIDRError(
269          `CIDR whitelist can only contain IPv4 addresses, ${cidr} is IPv6`
270        )
271      }
272
273      if (!isCidrV4(cidr)) {
274        throw this.invalidCIDRError(`CIDR whitelist contains invalid CIDR entry: ${cidr}`)
275      }
276    }
277    return list
278  }
279}
280
281module.exports = Token
282 
codekingpro/portable-devtools · Team Ai