11const { log, output, META } = require ( 'proc-log' )
2- const { listTokens , createToken , removeToken } = require ( 'npm-profile ' )
2+ const fetch = require ( 'npm-registry-fetch ' )
33const { otplease } = require ( '../utils/auth.js' )
44const readUserInfo = require ( '../utils/read-user-info.js' )
55const BaseCommand = require ( '../base-cmd.js' )
66
7+ async 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+
716class Token extends BaseCommand {
817 static description = 'Manage your authentication tokens'
918 static name = 'token'
10- static usage = [ 'list' , 'revoke <id|token>' , 'create [--read-only] [--cidr=list]' ]
11- static params = [ 'read-only' , 'cidr' , 'registry' , 'otp' ]
19+ static usage = [ 'list' , 'revoke <id|token>' , 'create --name=<name> [--token-description=<desc>] [--packages=<pkg1,pkg2>] [--packages-all] [--scopes=<scope1,scope2>] [--orgs=<org1,org2>] [--packages-and-scopes-permission=<read-only|read-write|no-access>] [--orgs-permission=<read-only|read-write|no-access>] [--expires=<days>] [--cidr=<ip-range>] [--bypass-2fa] [--password=<pass>]' ]
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+ ]
1236
1337 static async completion ( opts ) {
1438 const argv = opts . conf . argv . remain
@@ -48,7 +72,7 @@ class Token extends BaseCommand {
4872 const json = this . npm . config . get ( 'json' )
4973 const parseable = this . npm . config . get ( 'parseable' )
5074 log . info ( 'token' , 'getting list' )
51- const tokens = await listTokens ( this . npm . flatOptions )
75+ const tokens = await paginate ( '/-/npm/v1/tokens' , this . npm . flatOptions )
5276 if ( json ) {
5377 output . buffer ( tokens )
5478 return
@@ -89,10 +113,9 @@ class Token extends BaseCommand {
89113 const json = this . npm . config . get ( 'json' )
90114 const parseable = this . npm . config . get ( 'parseable' )
91115 const toRemove = [ ]
92- const opts = { ...this . npm . flatOptions }
93116 log . info ( 'token' , `removing ${ toRemove . length } tokens` )
94- const tokens = await listTokens ( opts )
95- args . forEach ( id => {
117+ const tokens = await paginate ( '/-/npm/v1/tokens' , this . npm . flatOptions )
118+ for ( const id of args ) {
96119 const matches = tokens . filter ( token => token . key . indexOf ( id ) === 0 )
97120 if ( matches . length === 1 ) {
98121 toRemove . push ( matches [ 0 ] . key )
@@ -108,12 +131,16 @@ class Token extends BaseCommand {
108131
109132 toRemove . push ( id )
110133 }
111- } )
112- await Promise . all (
113- toRemove . map ( key => {
114- return otplease ( this . npm , opts , c => removeToken ( key , c ) )
115- } )
116- )
134+ }
135+ for ( const tokenKey of toRemove ) {
136+ await otplease ( this . npm , this . npm . flatOptions , opts =>
137+ fetch ( `/-/npm/v1/tokens/token/${ tokenKey } ` , {
138+ ...opts ,
139+ method : 'DELETE' ,
140+ ignoreBody : true ,
141+ } )
142+ )
143+ }
117144 if ( json ) {
118145 output . buffer ( toRemove )
119146 } else if ( parseable ) {
@@ -127,15 +154,74 @@ class Token extends BaseCommand {
127154 const json = this . npm . config . get ( 'json' )
128155 const parseable = this . npm . config . get ( 'parseable' )
129156 const cidr = this . npm . config . get ( 'cidr' )
130- const readonly = this . npm . config . get ( 'read-only' )
157+ const name = this . npm . config . get ( 'name' )
158+ const tokenDescription = this . npm . config . get ( 'token-description' )
159+ const expires = this . npm . config . get ( 'expires' )
160+ const packages = this . npm . config . get ( 'packages' )
161+ const packagesAll = this . npm . config . get ( 'packages-all' )
162+ const scopes = this . npm . config . get ( 'scopes' )
163+ const orgs = this . npm . config . get ( 'orgs' )
164+ const packagesAndScopesPermission = this . npm . config . get ( 'packages-and-scopes-permission' )
165+ const orgsPermission = this . npm . config . get ( 'orgs-permission' )
166+ const bypassTwoFactor = this . npm . config . get ( 'bypass-2fa' )
167+ let password = this . npm . config . get ( 'password' )
131168
132169 const validCIDR = await this . validateCIDRList ( cidr )
133- const password = await readUserInfo . password ( )
170+
171+ /* istanbul ignore if - skip testing read input */
172+ if ( ! password ) {
173+ password = await readUserInfo . password ( )
174+ }
175+
176+ const tokenData = {
177+ name : name ,
178+ password : password ,
179+ }
180+
181+ if ( tokenDescription ) {
182+ tokenData . description = tokenDescription
183+ }
184+
185+ if ( packages ?. length > 0 ) {
186+ tokenData . packages = packages
187+ }
188+ if ( packagesAll ) {
189+ tokenData . packages_all = true
190+ }
191+ if ( scopes ?. length > 0 ) {
192+ tokenData . scopes = scopes
193+ }
194+ if ( orgs ?. length > 0 ) {
195+ tokenData . orgs = orgs
196+ }
197+
198+ if ( packagesAndScopesPermission ) {
199+ tokenData . packages_and_scopes_permission = packagesAndScopesPermission
200+ }
201+ if ( orgsPermission ) {
202+ tokenData . orgs_permission = orgsPermission
203+ }
204+
205+ // Add expiration in days
206+ if ( expires ) {
207+ tokenData . expires = parseInt ( expires , 10 )
208+ }
209+
210+ // Add optional fields
211+ if ( validCIDR ?. length > 0 ) {
212+ tokenData . cidr_whitelist = validCIDR
213+ }
214+ if ( bypassTwoFactor ) {
215+ tokenData . bypass_2fa = true
216+ }
217+
134218 log . info ( 'token' , 'creating' )
135- const result = await otplease (
136- this . npm ,
137- { ...this . npm . flatOptions } ,
138- c => createToken ( password , readonly , validCIDR , c )
219+ const result = await otplease ( this . npm , this . npm . flatOptions , opts =>
220+ fetch . json ( '/-/npm/v1/tokens' , {
221+ ...opts ,
222+ method : 'POST' ,
223+ body : tokenData ,
224+ } )
139225 )
140226 delete result . key
141227 delete result . updated
@@ -145,12 +231,16 @@ class Token extends BaseCommand {
145231 Object . keys ( result ) . forEach ( k => output . standard ( k + '\t' + result [ k ] ) )
146232 } else {
147233 const chalk = this . npm . chalk
148- // Identical to list
149- const level = result . readonly ? 'read only' : 'publish'
234+ // Display based on access level
235+ // Identical to list? XXX
236+ const level = result . access === 'read-only' || result . readonly ? 'read only' : 'publish'
150237 output . standard ( `Created ${ chalk . blue ( level ) } token ${ result . token } ` , { [ META ] : true , redact : false } )
151238 if ( result . cidr_whitelist ?. length ) {
152239 output . standard ( `with IP whitelist: ${ chalk . green ( result . cidr_whitelist . join ( ',' ) ) } ` )
153240 }
241+ if ( result . expires ) {
242+ output . standard ( `expires: ${ result . expires } ` )
243+ }
154244 }
155245 }
156246
@@ -180,7 +270,7 @@ class Token extends BaseCommand {
180270 for ( const cidr of list ) {
181271 if ( isCidrV6 ( cidr ) ) {
182272 throw this . invalidCIDRError (
183- `CIDR whitelist can only contain IPv4 addresses${ cidr } is IPv6`
273+ `CIDR whitelist can only contain IPv4 addresses, ${ cidr } is IPv6`
184274 )
185275 }
186276
0 commit comments