@@ -51,6 +51,10 @@ const parseField = require('./parse-field.js')
51
51
const typeDescription = require ( './type-description.js' )
52
52
const setEnvs = require ( './set-envs.js' )
53
53
54
+ const {
55
+ ErrInvalidAuth,
56
+ } = require ( './errors.js' )
57
+
54
58
// types that can be saved back to
55
59
const confFileTypes = new Set ( [
56
60
'global' ,
@@ -280,26 +284,10 @@ class Config {
280
284
await this . loadGlobalConfig ( )
281
285
process . emit ( 'timeEnd' , 'config:load:global' )
282
286
283
- // warn if anything is not valid
284
- process . emit ( 'time' , 'config:load:validate' )
285
- this . validate ( )
286
- process . emit ( 'timeEnd' , 'config:load:validate' )
287
-
288
287
// set this before calling setEnvs, so that we don't have to share
289
288
// symbols, as that module also does a bunch of get operations
290
289
this [ _loaded ] = true
291
290
292
- process . emit ( 'time' , 'config:load:credentials' )
293
- const reg = this . get ( 'registry' )
294
- const creds = this . getCredentialsByURI ( reg )
295
- // ignore this error because a failed set will strip out anything that
296
- // might be a security hazard, which was the intention.
297
- try {
298
- this . setCredentialsByURI ( reg , creds )
299
- // eslint-disable-next-line no-empty
300
- } catch ( _ ) { }
301
- process . emit ( 'timeEnd' , 'config:load:credentials' )
302
-
303
291
// set proper globalPrefix now that everything is loaded
304
292
this . globalPrefix = this . get ( 'prefix' )
305
293
@@ -399,15 +387,58 @@ class Config {
399
387
validate ( where ) {
400
388
if ( ! where ) {
401
389
let valid = true
402
- for ( const [ entryWhere ] of this . data . entries ( ) ) {
390
+ const authProblems = [ ]
391
+
392
+ for ( const entryWhere of this . data . keys ( ) ) {
403
393
// no need to validate our defaults, we know they're fine
404
394
// cli was already validated when parsed the first time
405
395
if ( entryWhere === 'default' || entryWhere === 'builtin' || entryWhere === 'cli' ) {
406
396
continue
407
397
}
408
398
const ret = this . validate ( entryWhere )
409
399
valid = valid && ret
400
+
401
+ if ( [ 'global' , 'user' , 'project' ] . includes ( entryWhere ) ) {
402
+ // after validating everything else, we look for old auth configs we no longer support
403
+ // if these keys are found, we build up a list of them and the appropriate action and
404
+ // attach it as context on the thrown error
405
+
406
+ // first, keys that should be removed
407
+ for ( const key of [ '_authtoken' , '-authtoken' ] ) {
408
+ if ( this . get ( key , entryWhere ) ) {
409
+ authProblems . push ( { action : 'delete' , key, where : entryWhere } )
410
+ }
411
+ }
412
+
413
+ // NOTE we pull registry without restricting to the current 'where' because we want to
414
+ // suggest scoping things to the registry they would be applied to, which is the default
415
+ // regardless of where it was defined
416
+ const nerfedReg = nerfDart ( this . get ( 'registry' ) )
417
+ // keys that should be nerfed but currently are not
418
+ for ( const key of [ '_auth' , '_authToken' , 'username' , '_password' ] ) {
419
+ if ( this . get ( key , entryWhere ) ) {
420
+ // username and _password must both exist in the same file to be recognized correctly
421
+ if ( key === 'username' && ! this . get ( '_password' , entryWhere ) ) {
422
+ authProblems . push ( { action : 'delete' , key, where : entryWhere } )
423
+ } else if ( key === '_password' && ! this . get ( 'username' , entryWhere ) ) {
424
+ authProblems . push ( { action : 'delete' , key, where : entryWhere } )
425
+ } else {
426
+ authProblems . push ( {
427
+ action : 'rename' ,
428
+ from : key ,
429
+ to : `${ nerfedReg } :${ key } ` ,
430
+ where : entryWhere ,
431
+ } )
432
+ }
433
+ }
434
+ }
435
+ }
410
436
}
437
+
438
+ if ( authProblems . length ) {
439
+ throw new ErrInvalidAuth ( authProblems )
440
+ }
441
+
411
442
return valid
412
443
} else {
413
444
const obj = this . data . get ( where )
@@ -423,6 +454,40 @@ class Config {
423
454
}
424
455
}
425
456
457
+ // fixes problems identified by validate(), accepts the 'problems' property from a thrown
458
+ // ErrInvalidAuth to avoid having to check everything again
459
+ repair ( problems ) {
460
+ if ( ! problems ) {
461
+ try {
462
+ this . validate ( )
463
+ } catch ( err ) {
464
+ // coverage skipped here because we don't need to test re-throwing an error
465
+ // istanbul ignore next
466
+ if ( err . code !== 'ERR_INVALID_AUTH' ) {
467
+ throw err
468
+ }
469
+
470
+ problems = err . problems
471
+ } finally {
472
+ if ( ! problems ) {
473
+ problems = [ ]
474
+ }
475
+ }
476
+ }
477
+
478
+ for ( const problem of problems ) {
479
+ // coverage disabled for else branch because it doesn't do anything and shouldn't
480
+ // istanbul ignore else
481
+ if ( problem . action === 'delete' ) {
482
+ this . delete ( problem . key , problem . where )
483
+ } else if ( problem . action === 'rename' ) {
484
+ const old = this . get ( problem . from , problem . where )
485
+ this . set ( problem . to , old , problem . where )
486
+ this . delete ( problem . from , problem . where )
487
+ }
488
+ }
489
+ }
490
+
426
491
// Returns true if the value is coming directly from the source defined
427
492
// in default definitions, if the current value for the key config is
428
493
// coming from any other different source, returns false
@@ -644,21 +709,19 @@ class Config {
644
709
if ( ! confFileTypes . has ( where ) ) {
645
710
throw new Error ( 'invalid config location param: ' + where )
646
711
}
712
+
647
713
const conf = this . data . get ( where )
648
714
conf [ _raw ] = { ...conf . data }
649
715
conf [ _loadError ] = null
650
716
651
- // upgrade auth configs to more secure variants before saving
652
717
if ( where === 'user' ) {
653
- const reg = this . get ( 'registry' )
654
- const creds = this . getCredentialsByURI ( reg )
655
- // we ignore this error because the failed set already removed
656
- // anything that might be a security hazard, and it won't be
657
- // saved back to the .npmrc file, so we're good.
658
- try {
659
- this . setCredentialsByURI ( reg , creds )
660
- // eslint-disable-next-line no-empty
661
- } catch ( _ ) { }
718
+ // if email is nerfed, then we want to de-nerf it
719
+ const nerfed = nerfDart ( this . get ( 'registry' ) )
720
+ const email = this . get ( `${ nerfed } :email` , 'user' )
721
+ if ( email ) {
722
+ this . delete ( `${ nerfed } :email` , 'user' )
723
+ this . set ( 'email' , email , 'user' )
724
+ }
662
725
}
663
726
664
727
const iniData = ini . stringify ( conf . data ) . trim ( ) + '\n'
@@ -686,14 +749,17 @@ class Config {
686
749
const nerfed = nerfDart ( uri )
687
750
const def = nerfDart ( this . get ( 'registry' ) )
688
751
if ( def === nerfed ) {
689
- // do not delete email, that shouldn't be nerfed any more.
690
- // just delete the nerfed copy, if one exists.
691
752
this . delete ( `-authtoken` , 'user' )
692
753
this . delete ( `_authToken` , 'user' )
693
754
this . delete ( `_authtoken` , 'user' )
694
755
this . delete ( `_auth` , 'user' )
695
756
this . delete ( `_password` , 'user' )
696
757
this . delete ( `username` , 'user' )
758
+ // de-nerf email if it's nerfed to the default registry
759
+ const email = this . get ( `${ nerfed } :email` , 'user' )
760
+ if ( email ) {
761
+ this . set ( 'email' , email , 'user' )
762
+ }
697
763
}
698
764
this . delete ( `${ nerfed } :_authToken` , 'user' )
699
765
this . delete ( `${ nerfed } :_auth` , 'user' )
@@ -706,28 +772,9 @@ class Config {
706
772
707
773
setCredentialsByURI ( uri , { token, username, password, email, certfile, keyfile } ) {
708
774
const nerfed = nerfDart ( uri )
709
- const def = nerfDart ( this . get ( 'registry' ) )
710
775
711
- if ( def === nerfed ) {
712
- // remove old style auth info not limited to a single registry
713
- this . delete ( '_password' , 'user' )
714
- this . delete ( 'username' , 'user' )
715
- this . delete ( '_auth' , 'user' )
716
- this . delete ( '_authtoken' , 'user' )
717
- this . delete ( '-authtoken' , 'user' )
718
- this . delete ( '_authToken' , 'user' )
719
- }
720
-
721
- // email used to be nerfed always. if we're using the default
722
- // registry, de-nerf it.
723
- if ( nerfed === def ) {
724
- email = email ||
725
- this . get ( 'email' , 'user' ) ||
726
- this . get ( `${ nerfed } :email` , 'user' )
727
- if ( email ) {
728
- this . set ( 'email' , email , 'user' )
729
- }
730
- }
776
+ // email is either provided, a top level key, or nothing
777
+ email = email || this . get ( 'email' , 'user' )
731
778
732
779
// field that hasn't been used as documented for a LONG time,
733
780
// and as of npm 7.10.0, isn't used at all. We just always
@@ -765,15 +812,17 @@ class Config {
765
812
// this has to be a bit more complicated to support legacy data of all forms
766
813
getCredentialsByURI ( uri ) {
767
814
const nerfed = nerfDart ( uri )
815
+ const def = nerfDart ( this . get ( 'registry' ) )
768
816
const creds = { }
769
817
770
- const deprecatedAuthWarning = [
771
- '`_auth`, `_authToken`, `username` and `_password` must be scoped to a registry.' ,
772
- 'see `npm help npmrc` for more information.' ,
773
- ] . join ( ' ' )
774
-
818
+ // email is handled differently, it used to always be nerfed and now it never should be
819
+ // if it's set nerfed to the default registry, then we copy it to the unnerfed key
820
+ // TODO: evaluate removing 'email' from the credentials object returned here
775
821
const email = this . get ( `${ nerfed } :email` ) || this . get ( 'email' )
776
822
if ( email ) {
823
+ if ( nerfed === def ) {
824
+ this . set ( 'email' , email , 'user' )
825
+ }
777
826
creds . email = email
778
827
}
779
828
@@ -785,13 +834,8 @@ class Config {
785
834
// cert/key may be used in conjunction with other credentials, thus no `return`
786
835
}
787
836
788
- const defaultToken = nerfDart ( this . get ( 'registry' ) ) && this . get ( '_authToken' )
789
- const tokenReg = this . get ( `${ nerfed } :_authToken` ) || defaultToken
790
-
837
+ const tokenReg = this . get ( `${ nerfed } :_authToken` )
791
838
if ( tokenReg ) {
792
- if ( tokenReg === defaultToken ) {
793
- log . warn ( 'config' , deprecatedAuthWarning )
794
- }
795
839
creds . token = tokenReg
796
840
return creds
797
841
}
@@ -816,37 +860,7 @@ class Config {
816
860
return creds
817
861
}
818
862
819
- // at this point, we can only use the values if the URI is the
820
- // default registry.
821
- const defaultNerf = nerfDart ( this . get ( 'registry' ) )
822
- if ( nerfed !== defaultNerf ) {
823
- return creds
824
- }
825
-
826
- const userDef = this . get ( 'username' )
827
- const passDef = this . get ( '_password' )
828
- if ( userDef && passDef ) {
829
- log . warn ( 'config' , deprecatedAuthWarning )
830
- creds . username = userDef
831
- creds . password = Buffer . from ( passDef , 'base64' ) . toString ( 'utf8' )
832
- const auth = `${ creds . username } :${ creds . password } `
833
- creds . auth = Buffer . from ( auth , 'utf8' ) . toString ( 'base64' )
834
- return creds
835
- }
836
-
837
- // Handle the old-style _auth=<base64> style for the default
838
- // registry, if set.
839
- const auth = this . get ( '_auth' )
840
- if ( ! auth ) {
841
- return creds
842
- }
843
-
844
- log . warn ( 'config' , deprecatedAuthWarning )
845
- const authDecode = Buffer . from ( auth , 'base64' ) . toString ( 'utf8' )
846
- const authSplit = authDecode . split ( ':' )
847
- creds . username = authSplit . shift ( )
848
- creds . password = authSplit . join ( ':' )
849
- creds . auth = auth
863
+ // at this point, nothing else is usable so just return what we do have
850
864
return creds
851
865
}
852
866
0 commit comments