UNPKG

7 kBJavaScriptView Raw
1'use strict'
2const profile = require('npm-profile')
3const npm = require('./npm.js')
4const output = require('./utils/output.js')
5const Table = require('cli-table3')
6const Bluebird = require('bluebird')
7const isCidrV4 = require('is-cidr').v4
8const isCidrV6 = require('is-cidr').v6
9const readUserInfo = require('./utils/read-user-info.js')
10const ansistyles = require('ansistyles')
11const log = require('npmlog')
12const pulseTillDone = require('./utils/pulse-till-done.js')
13
14module.exports = token
15
16token._validateCIDRList = validateCIDRList
17
18token.usage =
19 'npm token list\n' +
20 'npm token revoke <tokenKey>\n' +
21 'npm token create [--read-only] [--cidr=list]\n'
22
23token.subcommands = ['list', 'revoke', 'create']
24
25token.completion = function (opts, cb) {
26 var argv = opts.conf.argv.remain
27
28 switch (argv[2]) {
29 case 'list':
30 case 'revoke':
31 case 'create':
32 return cb(null, [])
33 default:
34 return cb(new Error(argv[2] + ' not recognized'))
35 }
36}
37
38function withCb (prom, cb) {
39 prom.then((value) => cb(null, value), cb)
40}
41
42function token (args, cb) {
43 log.gauge.show('token')
44 if (args.length === 0) return withCb(list([]), cb)
45 switch (args[0]) {
46 case 'list':
47 case 'ls':
48 withCb(list(), cb)
49 break
50 case 'delete':
51 case 'revoke':
52 case 'remove':
53 case 'rm':
54 withCb(rm(args.slice(1)), cb)
55 break
56 case 'create':
57 withCb(create(args.slice(1)), cb)
58 break
59 default:
60 cb(new Error('Unknown profile command: ' + args[0]))
61 }
62}
63
64function generateTokenIds (tokens, minLength) {
65 const byId = {}
66 tokens.forEach((token) => {
67 token.id = token.key
68 for (let ii = minLength; ii < token.key.length; ++ii) {
69 if (!tokens.some((ot) => ot !== token && ot.key.slice(0, ii) === token.key.slice(0, ii))) {
70 token.id = token.key.slice(0, ii)
71 break
72 }
73 }
74 byId[token.id] = token
75 })
76 return byId
77}
78
79function config () {
80 const conf = {
81 json: npm.config.get('json'),
82 parseable: npm.config.get('parseable'),
83 registry: npm.config.get('registry'),
84 otp: npm.config.get('otp')
85 }
86 const creds = npm.config.getCredentialsByURI(conf.registry)
87 if (creds.token) {
88 conf.auth = {token: creds.token}
89 } else if (creds.username) {
90 conf.auth = {basic: {username: creds.username, password: creds.password}}
91 } else if (creds.auth) {
92 const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2)
93 conf.auth = {basic: {username: auth[0], password: auth[1]}}
94 } else {
95 conf.auth = {}
96 }
97 if (conf.otp) conf.auth.otp = conf.otp
98 return conf
99}
100
101function list (args) {
102 const conf = config()
103 log.info('token', 'getting list')
104 return pulseTillDone.withPromise(profile.listTokens(conf)).then((tokens) => {
105 if (conf.json) {
106 output(JSON.stringify(tokens, null, 2))
107 return
108 } else if (conf.parseable) {
109 output(['key', 'token', 'created', 'readonly', 'CIDR whitelist'].join('\t'))
110 tokens.forEach((token) => {
111 output([
112 token.key,
113 token.token,
114 token.created,
115 token.readonly ? 'true' : 'false',
116 token.cidr_whitelist ? token.cidr_whitelist.join(',') : ''
117 ].join('\t'))
118 })
119 return
120 }
121 generateTokenIds(tokens, 6)
122 const idWidth = tokens.reduce((acc, token) => Math.max(acc, token.id.length), 0)
123 const table = new Table({
124 head: ['id', 'token', 'created', 'readonly', 'CIDR whitelist'],
125 colWidths: [Math.max(idWidth, 2) + 2, 9, 12, 10]
126 })
127 tokens.forEach((token) => {
128 table.push([
129 token.id,
130 token.token + '…',
131 String(token.created).slice(0, 10),
132 token.readonly ? 'yes' : 'no',
133 token.cidr_whitelist ? token.cidr_whitelist.join(', ') : ''
134 ])
135 })
136 output(table.toString())
137 })
138}
139
140function rm (args) {
141 if (args.length === 0) {
142 throw new Error('npm token revoke <tokenKey>')
143 }
144 const conf = config()
145 const toRemove = []
146 const progress = log.newItem('removing tokens', toRemove.length)
147 progress.info('token', 'getting existing list')
148 return pulseTillDone.withPromise(profile.listTokens(conf).then((tokens) => {
149 args.forEach((id) => {
150 const matches = tokens.filter((token) => token.key.indexOf(id) === 0)
151 if (matches.length === 1) {
152 toRemove.push(matches[0].key)
153 } else if (matches.length > 1) {
154 throw new Error(`Token ID "${id}" was ambiguous, a new token may have been created since you last ran \`npm-profile token list\`.`)
155 } else {
156 const tokenMatches = tokens.filter((token) => id.indexOf(token.token) === 0)
157 if (tokenMatches === 0) {
158 throw new Error(`Unknown token id or value "${id}".`)
159 }
160 toRemove.push(id)
161 }
162 })
163 return Bluebird.map(toRemove, (key) => {
164 return profile.removeToken(key, conf).catch((ex) => {
165 if (ex.code !== 'EOTP') throw ex
166 log.info('token', 'failed because revoking this token requires OTP')
167 return readUserInfo.otp().then((otp) => {
168 conf.auth.otp = otp
169 return profile.removeToken(key, conf)
170 })
171 })
172 })
173 })).then(() => {
174 if (conf.json) {
175 output(JSON.stringify(toRemove))
176 } else if (conf.parseable) {
177 output(toRemove.join('\t'))
178 } else {
179 output('Removed ' + toRemove.length + ' token' + (toRemove.length !== 1 ? 's' : ''))
180 }
181 })
182}
183
184function create (args) {
185 const conf = config()
186 const cidr = npm.config.get('cidr')
187 const readonly = npm.config.get('read-only')
188
189 const validCIDR = validateCIDRList(cidr)
190 return readUserInfo.password().then((password) => {
191 log.info('token', 'creating')
192 return profile.createToken(password, readonly, validCIDR, conf).catch((ex) => {
193 if (ex.code !== 'EOTP') throw ex
194 log.info('token', 'failed because it requires OTP')
195 return readUserInfo.otp().then((otp) => {
196 conf.auth.otp = otp
197 log.info('token', 'creating with OTP')
198 return pulseTillDone.withPromise(profile.createToken(password, readonly, validCIDR, conf))
199 })
200 })
201 }).then((result) => {
202 delete result.key
203 delete result.updated
204 if (conf.json) {
205 output(JSON.stringify(result))
206 } else if (conf.parseable) {
207 Object.keys(result).forEach((k) => output(k + '\t' + result[k]))
208 } else {
209 const table = new Table()
210 Object.keys(result).forEach((k) => table.push({[ansistyles.bright(k)]: String(result[k])}))
211 output(table.toString())
212 }
213 })
214}
215
216function validateCIDR (cidr) {
217 if (isCidrV6(cidr)) {
218 throw new Error('CIDR whitelist can only contain IPv4 addresses, ' + cidr + ' is IPv6')
219 }
220 if (!isCidrV4(cidr)) {
221 throw new Error('CIDR whitelist contains invalid CIDR entry: ' + cidr)
222 }
223}
224
225function validateCIDRList (cidrs) {
226 const maybeList = cidrs ? (Array.isArray(cidrs) ? cidrs : [cidrs]) : []
227 const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList
228 list.forEach(validateCIDR)
229 return list
230}