UNPKG

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