1const t = require('tap') 2const mockNpm = require('../../fixtures/mock-npm') 3 4const mockToken = async (t, { profile, getCredentialsByURI, readUserInfo, ...opts } = {}) => { 5 const mocks = {} 6 7 if (profile) { 8 mocks['npm-profile'] = profile 9 } 10 11 if (readUserInfo) { 12 mocks['{LIB}/utils/read-user-info.js'] = readUserInfo 13 } 14 15 const mock = await mockNpm(t, { 16 ...opts, 17 command: 'token', 18 mocks, 19 }) 20 21 // XXX: replace with mock registry 22 if (getCredentialsByURI) { 23 mock.npm.config.getCredentialsByURI = getCredentialsByURI 24 } 25 26 return mock 27} 28 29t.test('completion', async t => { 30 const { token } = await mockToken(t) 31 32 const testComp = (argv, expect) => { 33 t.resolveMatch(token.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' ')) 34 } 35 36 testComp(['npm', 'token'], ['list', 'revoke', 'create']) 37 testComp(['npm', 'token', 'list'], []) 38 testComp(['npm', 'token', 'revoke'], []) 39 testComp(['npm', 'token', 'create'], []) 40 41 t.rejects(token.completion({ conf: { argv: { remain: ['npm', 'token', 'foobar'] } } }), { 42 message: 'foobar not recognize', 43 }) 44}) 45 46t.test('token foobar', async t => { 47 const { token } = await mockToken(t) 48 49 await t.rejects(token.exec(['foobar']), /foobar is not a recognized subcommand/) 50}) 51 52t.test('token list', async t => { 53 const now = new Date().toISOString() 54 const tokens = [ 55 { 56 key: 'abcd1234abcd1234', 57 token: 'efgh5678efgh5678', 58 cidr_whitelist: null, 59 readonly: false, 60 created: now, 61 updated: now, 62 }, 63 { 64 key: 'abcd1256', 65 token: 'hgfe8765', 66 cidr_whitelist: ['192.168.1.1/32'], 67 readonly: true, 68 created: now, 69 updated: now, 70 }, 71 ] 72 73 const { token, joinedOutput } = await mockToken(t, { 74 config: { registry: 'https://registry.npmjs.org', otp: '123456' }, 75 getCredentialsByURI: uri => { 76 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 77 return { token: 'thisisnotarealtoken' } 78 }, 79 profile: { 80 listTokens: conf => { 81 t.same(conf.auth, { token: 'thisisnotarealtoken', otp: '123456' }) 82 return tokens 83 }, 84 }, 85 }) 86 87 await token.exec([]) 88 89 const lines = joinedOutput().split(/\r?\n/) 90 t.match(lines[3], ' abcd123 ', 'includes the trimmed key') 91 t.match(lines[3], ' efgh56… ', 'includes the trimmed token') 92 t.match(lines[3], ` ${now.slice(0, 10)} `, 'includes the trimmed creation timestamp') 93 t.match(lines[3], ' no ', 'includes the "no" string for readonly state') 94 t.match(lines[5], ' abcd125 ', 'includes the trimmed key') 95 t.match(lines[5], ' hgfe87… ', 'includes the trimmed token') 96 t.match(lines[5], ` ${now.slice(0, 10)} `, 'includes the trimmed creation timestamp') 97 t.match(lines[5], ' yes ', 'includes the "no" string for readonly state') 98 t.match(lines[5], ` ${tokens[1].cidr_whitelist.join(',')} `, 'includes the cidr whitelist') 99}) 100 101t.test('token list json output', async t => { 102 const now = new Date().toISOString() 103 const tokens = [ 104 { 105 key: 'abcd1234abcd1234', 106 token: 'efgh5678efgh5678', 107 cidr_whitelist: null, 108 readonly: false, 109 created: now, 110 updated: now, 111 }, 112 ] 113 114 const { token, joinedOutput } = await mockToken(t, { 115 config: { registry: 'https://registry.npmjs.org', json: true }, 116 getCredentialsByURI: uri => { 117 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 118 return { username: 'foo', password: 'bar' } 119 }, 120 profile: { 121 listTokens: conf => { 122 t.same( 123 conf.auth, 124 { basic: { username: 'foo', password: 'bar' } }, 125 'passes the correct auth' 126 ) 127 return tokens 128 }, 129 }, 130 131 }) 132 133 await token.exec(['list']) 134 135 const parsed = JSON.parse(joinedOutput()) 136 t.match(parsed, tokens, 'prints the json parsed tokens') 137}) 138 139t.test('token list parseable output', async t => { 140 const now = new Date().toISOString() 141 const tokens = [ 142 { 143 key: 'abcd1234abcd1234', 144 token: 'efgh5678efgh5678', 145 cidr_whitelist: null, 146 readonly: false, 147 created: now, 148 updated: now, 149 }, 150 { 151 key: 'efgh5678ijkl9101', 152 token: 'hgfe8765', 153 cidr_whitelist: ['192.168.1.1/32'], 154 readonly: true, 155 created: now, 156 updated: now, 157 }, 158 ] 159 160 const { token, joinedOutput } = await mockToken(t, { 161 config: { registry: 'https://registry.npmjs.org', parseable: true }, 162 getCredentialsByURI: uri => { 163 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 164 return { auth: Buffer.from('foo:bar').toString('base64') } 165 }, 166 profile: { 167 listTokens: conf => { 168 t.same( 169 conf.auth, 170 { basic: { username: 'foo', password: 'bar' } }, 171 'passes the correct auth' 172 ) 173 return tokens 174 }, 175 }, 176 }) 177 178 await token.exec(['list']) 179 180 const lines = joinedOutput().split(/\r?\n/) 181 182 t.equal( 183 lines[0], 184 ['key', 'token', 'created', 'readonly', 'CIDR whitelist'].join('\t'), 185 'prints header' 186 ) 187 188 t.equal( 189 lines[1], 190 [tokens[0].key, tokens[0].token, tokens[0].created, tokens[0].readonly, ''].join('\t'), 191 'prints token info' 192 ) 193 194 t.equal( 195 lines[2], 196 [ 197 tokens[1].key, 198 tokens[1].token, 199 tokens[1].created, 200 tokens[1].readonly, 201 tokens[1].cidr_whitelist.join(','), 202 ].join('\t'), 203 'prints token info' 204 ) 205}) 206 207t.test('token revoke', async t => { 208 const { token, joinedOutput } = await mockToken(t, { 209 config: { registry: 'https://registry.npmjs.org' }, 210 getCredentialsByURI: uri => { 211 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 212 return {} 213 }, 214 profile: { 215 listTokens: conf => { 216 t.same(conf.auth, {}, 'passes the correct empty auth') 217 return Promise.resolve([{ key: 'abcd1234' }]) 218 }, 219 removeToken: key => { 220 t.equal(key, 'abcd1234', 'deletes the correct token') 221 }, 222 }, 223 }) 224 225 await token.exec(['rm', 'abcd']) 226 227 t.equal(joinedOutput(), 'Removed 1 token') 228}) 229 230t.test('token revoke multiple tokens', async t => { 231 const { token, joinedOutput } = await mockToken(t, { 232 config: { registry: 'https://registry.npmjs.org' }, 233 getCredentialsByURI: uri => { 234 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 235 return { token: 'thisisnotarealtoken' } 236 }, 237 profile: { 238 listTokens: () => Promise.resolve([{ key: 'abcd1234' }, { key: 'efgh5678' }]), 239 removeToken: key => { 240 // this will run twice 241 t.ok(['abcd1234', 'efgh5678'].includes(key), 'deletes the correct token') 242 }, 243 }, 244 }) 245 246 await token.exec(['revoke', 'abcd', 'efgh']) 247 248 t.equal(joinedOutput(), 'Removed 2 tokens') 249}) 250 251t.test('token revoke json output', async t => { 252 const { token, joinedOutput } = await mockToken(t, { 253 config: { registry: 'https://registry.npmjs.org', json: true }, 254 getCredentialsByURI: uri => { 255 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 256 return { token: 'thisisnotarealtoken' } 257 }, 258 profile: { 259 listTokens: () => Promise.resolve([{ key: 'abcd1234' }]), 260 removeToken: key => { 261 t.equal(key, 'abcd1234', 'deletes the correct token') 262 }, 263 }, 264 265 }) 266 267 await token.exec(['delete', 'abcd']) 268 269 const parsed = JSON.parse(joinedOutput()) 270 t.same(parsed, ['abcd1234'], 'logs the token as json') 271}) 272 273t.test('token revoke parseable output', async t => { 274 const { token, joinedOutput } = await mockToken(t, { 275 config: { registry: 'https://registry.npmjs.org', parseable: true }, 276 getCredentialsByURI: uri => { 277 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 278 return { token: 'thisisnotarealtoken' } 279 }, 280 profile: { 281 listTokens: () => Promise.resolve([{ key: 'abcd1234' }]), 282 removeToken: key => { 283 t.equal(key, 'abcd1234', 'deletes the correct token') 284 }, 285 }, 286 }) 287 288 await token.exec(['remove', 'abcd']) 289 290 t.equal(joinedOutput(), 'abcd1234', 'logs the token as a string') 291}) 292 293t.test('token revoke by token', async t => { 294 const { token, joinedOutput } = await mockToken(t, { 295 config: { registry: 'https://registry.npmjs.org' }, 296 getCredentialsByURI: uri => { 297 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 298 return { token: 'thisisnotarealtoken' } 299 }, 300 profile: { 301 listTokens: () => Promise.resolve([{ key: 'abcd1234', token: 'efgh5678' }]), 302 removeToken: key => { 303 t.equal(key, 'efgh5678', 'passes through user input') 304 }, 305 }, 306 }) 307 308 await token.exec(['rm', 'efgh5678']) 309 t.equal(joinedOutput(), 'Removed 1 token') 310}) 311 312t.test('token revoke requires an id', async t => { 313 const { token } = await mockToken(t) 314 315 await t.rejects(token.exec(['rm']), /`<tokenKey>` argument is required/) 316}) 317 318t.test('token revoke ambiguous id errors', async t => { 319 const { token } = await mockToken(t, { 320 config: { registry: 'https://registry.npmjs.org' }, 321 getCredentialsByURI: uri => { 322 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 323 return { token: 'thisisnotarealtoken' } 324 }, 325 profile: { 326 listTokens: () => Promise.resolve([{ key: 'abcd1234' }, { key: 'abcd5678' }]), 327 }, 328 }) 329 330 await t.rejects(token.exec(['rm', 'abcd']), /Token ID "abcd" was ambiguous/) 331}) 332 333t.test('token revoke unknown id errors', async t => { 334 const { token } = await mockToken(t, { 335 config: { registry: 'https://registry.npmjs.org' }, 336 getCredentialsByURI: uri => { 337 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 338 return { token: 'thisisnotarealtoken' } 339 }, 340 profile: { 341 listTokens: () => Promise.resolve([{ key: 'abcd1234' }]), 342 }, 343 }) 344 345 await t.rejects(token.exec(['rm', 'efgh']), /Unknown token id or value "efgh"./) 346}) 347 348t.test('token create', async t => { 349 const now = new Date().toISOString() 350 const password = 'thisisnotreallyapassword' 351 352 const { token, joinedOutput } = await mockToken(t, { 353 config: { 354 registry: 'https://registry.npmjs.org', 355 cidr: ['10.0.0.0/8', '192.168.1.0/24'], 356 }, 357 getCredentialsByURI: uri => { 358 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 359 return { token: 'thisisnotarealtoken' } 360 }, 361 readUserInfo: { 362 password: () => Promise.resolve(password), 363 }, 364 profile: { 365 createToken: (pw, readonly, cidr) => { 366 t.equal(pw, password) 367 t.equal(readonly, false) 368 t.same(cidr, ['10.0.0.0/8', '192.168.1.0/24'], 'defaults to empty array') 369 return { 370 key: 'abcd1234', 371 token: 'efgh5678', 372 created: now, 373 updated: now, 374 readonly: false, 375 cidr_whitelist: [], 376 } 377 }, 378 }, 379 380 }) 381 382 await token.exec(['create']) 383 384 const lines = joinedOutput().split(/\r?\n/) 385 t.match(lines[1], 'token') 386 t.match(lines[1], 'efgh5678', 'prints the whole token') 387 t.match(lines[3], 'created') 388 t.match(lines[3], now, 'prints the correct timestamp') 389 t.match(lines[5], 'readonly') 390 t.match(lines[5], 'false', 'prints the readonly flag') 391 t.match(lines[7], 'cidr_whitelist') 392}) 393 394t.test('token create json output', async t => { 395 const now = new Date().toISOString() 396 const password = 'thisisnotreallyapassword' 397 398 const { token } = await mockToken(t, { 399 config: { registry: 'https://registry.npmjs.org', json: true }, 400 getCredentialsByURI: uri => { 401 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 402 return { token: 'thisisnotarealtoken' } 403 }, 404 readUserInfo: { 405 password: () => Promise.resolve(password), 406 }, 407 profile: { 408 createToken: (pw, readonly, cidr) => { 409 t.equal(pw, password) 410 t.equal(readonly, false) 411 t.same(cidr, [], 'defaults to empty array') 412 return { 413 key: 'abcd1234', 414 token: 'efgh5678', 415 created: now, 416 updated: now, 417 readonly: false, 418 cidr_whitelist: [], 419 } 420 }, 421 }, 422 output: spec => { 423 t.type(spec, 'string', 'outputs a string') 424 const parsed = JSON.parse(spec) 425 t.same( 426 parsed, 427 { token: 'efgh5678', created: now, readonly: false, cidr_whitelist: [] }, 428 'outputs the correct object' 429 ) 430 }, 431 }) 432 433 await token.exec(['create']) 434}) 435 436t.test('token create parseable output', async t => { 437 const now = new Date().toISOString() 438 const password = 'thisisnotreallyapassword' 439 440 const { token, joinedOutput } = await mockToken(t, { 441 config: { registry: 'https://registry.npmjs.org', parseable: true }, 442 getCredentialsByURI: uri => { 443 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 444 return { token: 'thisisnotarealtoken' } 445 }, 446 readUserInfo: { 447 password: () => Promise.resolve(password), 448 }, 449 profile: { 450 createToken: (pw, readonly, cidr) => { 451 t.equal(pw, password) 452 t.equal(readonly, false) 453 t.same(cidr, [], 'defaults to empty array') 454 return { 455 key: 'abcd1234', 456 token: 'efgh5678', 457 created: now, 458 updated: now, 459 readonly: false, 460 cidr_whitelist: [], 461 } 462 }, 463 }, 464 }) 465 466 await token.exec(['create']) 467 468 const spec = joinedOutput().split(/\r?\n/) 469 470 t.match(spec[0], 'token\tefgh5678', 'prints the token') 471 t.match(spec[1], `created\t${now}`, 'prints the created timestamp') 472 t.match(spec[2], 'readonly\tfalse', 'prints the readonly flag') 473 t.match(spec[3], 'cidr_whitelist\t', 'prints the cidr whitelist') 474}) 475 476t.test('token create ipv6 cidr', async t => { 477 const password = 'thisisnotreallyapassword' 478 479 const { token } = await mockToken(t, { 480 config: { registry: 'https://registry.npmjs.org', cidr: '::1/128' }, 481 getCredentialsByURI: uri => { 482 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 483 return { token: 'thisisnotarealtoken' } 484 }, 485 readUserInfo: { 486 password: () => Promise.resolve(password), 487 }, 488 }) 489 490 await t.rejects( 491 token.exec(['create']), 492 { 493 code: 'EINVALIDCIDR', 494 message: /CIDR whitelist can only contain IPv4 addresses, ::1\/128 is IPv6/, 495 }, 496 'returns correct error' 497 ) 498}) 499 500t.test('token create invalid cidr', async t => { 501 const password = 'thisisnotreallyapassword' 502 503 const { token } = await mockToken(t, { 504 config: { registry: 'https://registry.npmjs.org', cidr: 'apple/cider' }, 505 getCredentialsByURI: uri => { 506 t.equal(uri, 'https://registry.npmjs.org/', 'requests correct registry') 507 return { token: 'thisisnotarealtoken' } 508 }, 509 readUserInfo: { 510 password: () => Promise.resolve(password), 511 }, 512 }) 513 514 await t.rejects( 515 token.exec(['create']), 516 { code: 'EINVALIDCIDR', message: /CIDR whitelist contains invalid CIDR entry: apple\/cider/ }, 517 'returns correct error' 518 ) 519}) 520