1+ const { inspect } = require ( 'node:util' )
12const t = require ( 'tap' )
23const mockNpm = require ( '../../fixtures/mock-npm' )
4+ const tmock = require ( '../../fixtures/tmock' )
35
4- const mockProfile = async ( t , { npmProfile, readUserInfo, qrcode, config, ...opts } = { } ) => {
6+ const mockProfile = async ( t , {
7+ npmProfile, readUserInfo, qrcode, config, isTTY, ...opts } = { } ) => {
8+ const mockReadUserInfo = {
9+ '{LIB}/utils/read-user-info.js' : readUserInfo || {
10+ async password ( ) { } ,
11+ async otp ( ) { } ,
12+ } ,
13+ }
514 const mocks = {
615 'npm-profile' : npmProfile || {
716 async get ( ) { } ,
817 async set ( ) { } ,
918 async createToken ( ) { } ,
1019 } ,
1120 'qrcode-terminal' : qrcode || { generate : ( url , cb ) => cb ( ) } ,
12- '{LIB}/utils/read-user-info.js' : readUserInfo || {
13- async password ( ) { } ,
14- async otp ( ) { } ,
15- } ,
21+ ...mockReadUserInfo ,
22+ '{LIB}/utils/auth.js' : tmock ( t , '{LIB}/utils/auth.js' , mockReadUserInfo ) ,
1623 }
1724
1825 const mock = await mockNpm ( t , {
@@ -22,6 +29,10 @@ const mockProfile = async (t, { npmProfile, readUserInfo, qrcode, config, ...opt
2229 color : false ,
2330 ...config ,
2431 } ,
32+ globals : {
33+ 'process.stdin.isTTY' : isTTY ,
34+ 'process.stdout.isTTY' : isTTY ,
35+ } ,
2536 mocks : {
2637 ...mocks ,
2738 ...opts . mocks ,
@@ -516,6 +527,12 @@ t.test('enable-2fa', async t => {
516527 t . match ( pass , 'bar' , 'should use password for basic auth' )
517528 return { }
518529 } ,
530+ async get ( ) {
531+ return {
532+ userProfile,
533+ tfa : null ,
534+ }
535+ } ,
519536 }
520537
521538 const { npm, profile } = await mockProfile ( t , {
@@ -543,6 +560,12 @@ t.test('enable-2fa', async t => {
543560 async createToken ( ) {
544561 return { }
545562 } ,
563+ async get ( ) {
564+ return {
565+ ...userProfile ,
566+ tfa : null ,
567+ }
568+ } ,
546569 }
547570
548571 const { npm, profile } = await mockProfile ( t , {
@@ -577,7 +600,9 @@ t.test('enable-2fa', async t => {
577600 } )
578601
579602 t . test ( 'from basic auth, asks for otp' , async t => {
580- t . plan ( 9 )
603+ t . plan ( 10 )
604+
605+ let setCallCount = 0
581606
582607 const npmProfile = {
583608 async createToken ( pass ) {
@@ -588,18 +613,36 @@ t.test('enable-2fa', async t => {
588613 return userProfile
589614 } ,
590615 async set ( newProfile ) {
591- t . match (
592- newProfile ,
593- {
616+ setCallCount ++
617+ if ( setCallCount === 1 ) {
618+ t . match (
619+ newProfile ,
620+ {
621+ tfa : {
622+ mode : 'auth-only' ,
623+ } ,
624+ } ,
625+ 'should set tfa mode on first call'
626+ )
627+ const err = new Error ( 'One-time password required' )
628+ err . code = 'EOTP'
629+ throw err
630+ } else if ( setCallCount === 2 ) {
631+ t . match (
632+ newProfile ,
633+ {
634+ tfa : {
635+ mode : 'auth-only' ,
636+ } ,
637+ } ,
638+ 'should set tfa mode'
639+ )
640+ return {
641+ ...userProfile ,
594642 tfa : {
595643 mode : 'auth-only' ,
596644 } ,
597- } ,
598- 'should set tfa mode'
599- )
600- return {
601- ...userProfile ,
602- tfa : null ,
645+ }
603646 }
604647 } ,
605648 }
@@ -612,14 +655,15 @@ t.test('enable-2fa', async t => {
612655 async otp ( label ) {
613656 t . equal (
614657 label ,
615- 'Enter one-time password: ' ,
658+ 'This operation requires a one-time password.\nEnter OTP: ' ,
616659 'should ask for otp confirmation'
617660 )
618661 return '123456'
619662 } ,
620663 }
621664
622665 const { npm, profile, result } = await mockProfile ( t , {
666+ isTTY : true ,
623667 npmProfile,
624668 readUserInfo,
625669 } )
@@ -817,12 +861,12 @@ t.test('enable-2fa', async t => {
817861
818862 t . equal (
819863 result ( ) ,
820- 'Two factor authentication mode changed to: auth-and-writes' ,
864+ 'Two factor authentication is already enabled and set to auth-and-writes' ,
821865 'should output success msg'
822866 )
823867 } )
824868
825- t . test ( 'missing tfa from user profile ' , async t => {
869+ t . test ( 'errors when tfa is return null (not otpauth URL) and tfa is not setup already (with auth-only) ' , async t => {
826870 const npmProfile = {
827871 async get ( ) {
828872 return {
@@ -847,7 +891,7 @@ t.test('enable-2fa', async t => {
847891 } ,
848892 }
849893
850- const { npm, profile, result } = await mockProfile ( t , {
894+ const { npm, profile } = await mockProfile ( t , {
851895 npmProfile,
852896 readUserInfo,
853897 } )
@@ -856,16 +900,15 @@ t.test('enable-2fa', async t => {
856900 return { token : 'token' }
857901 }
858902
859- await profile . exec ( [ 'enable-2fa' , 'auth-only' ] )
860-
861- t . equal (
862- result ( ) ,
863- 'Two factor authentication mode changed to: auth-only' ,
864- 'should output success msg'
865- )
903+ await t . rejects ( async ( ) => {
904+ await profile . exec ( [ 'enable-2fa' , 'auth-only' ] )
905+ } , new Error (
906+ 'Unknown error enabling two-factor authentication. Expected otpauth URL' +
907+ ', got: ' + inspect ( null )
908+ ) )
866909 } )
867910
868- t . test ( 'defaults to auth- and-writes permission if no mode specified ' , async t => {
911+ t . test ( 'errors when tfa is return null (not otpauth URL) and tfa is not setup already ' , async t => {
869912 const npmProfile = {
870913 async get ( ) {
871914 return {
@@ -890,7 +933,7 @@ t.test('enable-2fa', async t => {
890933 } ,
891934 }
892935
893- const { npm, profile, result } = await mockProfile ( t , {
936+ const { npm, profile } = await mockProfile ( t , {
894937 npmProfile,
895938 readUserInfo,
896939 } )
@@ -899,12 +942,12 @@ t.test('enable-2fa', async t => {
899942 return { token : 'token' }
900943 }
901944
902- await profile . exec ( [ 'enable-2fa' ] )
903- t . equal (
904- result ( ) ,
905- 'Two factor authentication mode changed to: auth-and-writes' ,
906- 'should enable 2fa with auth-and-writes permission'
907- )
945+ await t . rejects ( async ( ) => {
946+ await profile . exec ( [ 'enable-2fa' ] )
947+ } , new Error (
948+ 'Unknown error enabling two- factor authentication. Expected otpauth URL' +
949+ ', got: ' + inspect ( null )
950+ ) )
908951 } )
909952} )
910953
@@ -929,23 +972,33 @@ t.test('disable-2fa', async t => {
929972 } )
930973
931974 t . test ( 'requests otp' , async t => {
932- const npmProfile = t => ( {
933- async get ( ) {
934- return userProfile
935- } ,
936- async set ( newProfile ) {
937- t . same (
938- newProfile ,
939- {
940- tfa : {
941- password : 'password1234' ,
942- mode : 'disable' ,
943- } ,
944- } ,
945- 'should send the new info for setting in profile'
946- )
947- } ,
948- } )
975+ const OTP_ERROR = Object . assign ( new Error ( 'One-time password required' ) , { code : 'EOTP' } )
976+
977+ const npmProfile = ( t ) => {
978+ let setCallCount = 0
979+ return {
980+ async get ( ) {
981+ return userProfile
982+ } ,
983+ async set ( newProfile ) {
984+ setCallCount ++
985+ if ( setCallCount === 1 ) {
986+ throw OTP_ERROR
987+ } else if ( setCallCount === 2 ) {
988+ t . same (
989+ newProfile ,
990+ {
991+ tfa : {
992+ password : 'password1234' ,
993+ mode : 'disable' ,
994+ } ,
995+ } ,
996+ 'should send the new info for setting in profile'
997+ )
998+ }
999+ } ,
1000+ }
1001+ }
9491002
9501003 const readUserInfo = t => ( {
9511004 async password ( ) {
@@ -955,7 +1008,7 @@ t.test('disable-2fa', async t => {
9551008 async otp ( label ) {
9561009 t . equal (
9571010 label ,
958- 'Enter one-time password: ' ,
1011+ 'This operation requires a one-time password.\nEnter OTP: ' ,
9591012 'should ask for otp confirmation'
9601013 )
9611014 return '1234'
@@ -968,6 +1021,7 @@ t.test('disable-2fa', async t => {
9681021 const { profile, result } = await mockProfile ( t , {
9691022 npmProfile : npmProfile ( t ) ,
9701023 readUserInfo : readUserInfo ( t ) ,
1024+ isTTY : true ,
9711025 } )
9721026
9731027 await profile . exec ( [ 'disable-2fa' ] )
@@ -983,6 +1037,7 @@ t.test('disable-2fa', async t => {
9831037 npmProfile : npmProfile ( t ) ,
9841038 readUserInfo : readUserInfo ( t ) ,
9851039 config,
1040+ isTTY : true ,
9861041 } )
9871042
9881043 await profile . exec ( [ 'disable-2fa' ] )
@@ -999,6 +1054,7 @@ t.test('disable-2fa', async t => {
9991054 npmProfile : npmProfile ( t ) ,
10001055 readUserInfo : readUserInfo ( t ) ,
10011056 config,
1057+ isTTY : true ,
10021058 } )
10031059
10041060 await profile . exec ( [ 'disable-2fa' ] )
0 commit comments