@@ -76,6 +76,22 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
7676 expect ( res . body . oobCode ) . to . be . a ( "string" ) ;
7777 expect ( res . body . oobLink ) . to . be . a ( "string" ) ;
7878 } ) ;
79+
80+ await authApi ( )
81+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
82+ . set ( "Authorization" , "Bearer owner" )
83+ . send ( {
84+ email : user . email ,
85+ 86+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
87+ returnOobLink : true ,
88+ } )
89+ . then ( ( res ) => {
90+ expectStatusCode ( 200 , res ) ;
91+ expect ( res . body . email ) . to . equal ( user . email ) ;
92+ expect ( res . body . oobCode ) . to . be . a ( "string" ) ;
93+ expect ( res . body . oobLink ) . to . be . a ( "string" ) ;
94+ } ) ;
7995 } ) ;
8096
8197 it ( "should return OOB code by idToken for OAuth 2 requests as well" , async ( ) => {
@@ -91,6 +107,23 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
91107 expect ( res . body . oobCode ) . to . be . a ( "string" ) ;
92108 expect ( res . body . oobLink ) . to . be . a ( "string" ) ;
93109 } ) ;
110+
111+ await authApi ( )
112+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
113+ . set ( "Authorization" , "Bearer owner" )
114+ . send ( {
115+ email : user . email ,
116+ 117+ idToken,
118+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
119+ returnOobLink : true ,
120+ } )
121+ . then ( ( res ) => {
122+ expectStatusCode ( 200 , res ) ;
123+ expect ( res . body . email ) . to . equal ( user . email ) ;
124+ expect ( res . body . oobCode ) . to . be . a ( "string" ) ;
125+ expect ( res . body . oobLink ) . to . be . a ( "string" ) ;
126+ } ) ;
94127 } ) ;
95128
96129 it ( "should error when trying to verify email without idToken or email" , async ( ) => {
@@ -100,7 +133,7 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
100133 await authApi ( )
101134 . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
102135 . query ( { key : "fake-api-key" } )
103- . send ( { requestType : "VERIFY_EMAIL" } )
136+ . send ( { idToken : "hoge" , requestType : "VERIFY_EMAIL" } )
104137 . then ( ( res ) => {
105138 expectStatusCode ( 400 , res ) ;
106139 expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "INVALID_ID_TOKEN" ) ;
@@ -281,6 +314,7 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
281314 it ( "should return purpose of oobCodes via resetPassword endpoint" , async ( ) => {
282315 const user = { email :
"[email protected] " , password :
"notasecret" } ; 283316 const { idToken } = await registerUser ( authApi ( ) , user ) ;
317+ const newEmail = "[email protected] " ; 284318
285319 await authApi ( )
286320 . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
@@ -297,11 +331,22 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
297331 await authApi ( )
298332 . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
299333 . query ( { key : "fake-api-key" } )
300- . send ( { email :
"[email protected] " , requestType :
"EMAIL_SIGNIN" } ) 334+ . send ( { email : newEmail , requestType : "EMAIL_SIGNIN" } )
335+ . then ( ( res ) => expectStatusCode ( 200 , res ) ) ;
336+
337+ await authApi ( )
338+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
339+ . query ( { key : "fake-api-key" } )
340+ . send ( {
341+ email : user . email ,
342+ newEmail,
343+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
344+ idToken,
345+ } )
301346 . then ( ( res ) => expectStatusCode ( 200 , res ) ) ;
302347
303348 const oobs = await inspectOobs ( authApi ( ) ) ;
304- expect ( oobs ) . to . have . length ( 3 ) ;
349+ expect ( oobs ) . to . have . length ( 4 ) ;
305350
306351 for ( const oob of oobs ) {
307352 await authApi ( )
@@ -322,12 +367,15 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
322367 } else {
323368 expect ( res . body . email ) . to . equal ( oob . email ) ;
324369 }
370+ if ( oob . requestType === "VERIFY_AND_CHANGE_EMAIL" ) {
371+ expect ( res . body . newEmail ) . to . equal ( newEmail ) ;
372+ }
325373 } ) ;
326374 }
327375
328376 // OOB codes are not consumed by the lookup above.
329377 const oobs2 = await inspectOobs ( authApi ( ) ) ;
330- expect ( oobs2 ) . to . have . length ( 3 ) ;
378+ expect ( oobs2 ) . to . have . length ( 4 ) ;
331379 } ) ;
332380
333381 it ( "should error on resetPassword if auth is disabled" , async ( ) => {
@@ -395,4 +443,189 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => {
395443 expect ( res . body ) . to . have . property ( "email" ) . equals ( user . email ) ;
396444 } ) ;
397445 } ) ;
446+
447+ it ( "should generate OOB code for verify and change email" , async ( ) => {
448+ const user = { email :
"[email protected] " , password :
"notasecret" } ; 449+ const { idToken, localId } = await registerUser ( authApi ( ) , user ) ;
450+
451+ await authApi ( )
452+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
453+ . query ( { key : "fake-api-key" } )
454+ . send ( {
455+ email : user . email ,
456+ 457+ idToken,
458+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
459+ } )
460+ . then ( ( res ) => {
461+ expectStatusCode ( 200 , res ) ;
462+ expect ( res . body )
463+ . to . have . property ( "kind" )
464+ . equals ( "identitytoolkit#GetOobConfirmationCodeResponse" ) ;
465+ expect ( res . body . email ) . to . equal ( user . email ) ;
466+
467+ // These fields should not be set since returnOobLink is not set.
468+ expect ( res . body ) . not . to . have . property ( "oobCode" ) ;
469+ expect ( res . body ) . not . to . have . property ( "oobLink" ) ;
470+ } ) ;
471+
472+ const oobs = await inspectOobs ( authApi ( ) ) ;
473+ expect ( oobs ) . to . have . length ( 1 ) ;
474+ expect ( oobs [ 0 ] . email ) . to . equal ( user . email ) ;
475+ expect ( oobs [ 0 ] . requestType ) . to . equal ( "VERIFY_AND_CHANGE_EMAIL" ) ;
476+
477+ // The returned oobCode can be redeemed to verify and change the email.
478+ await authApi ( )
479+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:update" )
480+ . query ( { key : "fake-api-key" } )
481+ // OOB code is enough, no idToken needed.
482+ . send ( { oobCode : oobs [ 0 ] . oobCode } )
483+ . then ( ( res ) => {
484+ expectStatusCode ( 200 , res ) ;
485+ expect ( res . body . localId ) . to . equal ( localId ) ;
486+ expect ( res . body . email ) . to . equal ( "[email protected] " ) ; 487+ expect ( res . body . emailVerified ) . to . equal ( true ) ;
488+ } ) ;
489+
490+ // oobCode is removed after redeemed.
491+ const oobs2 = await inspectOobs ( authApi ( ) ) ;
492+ expect ( oobs2 ) . to . have . length ( 0 ) ;
493+ } ) ;
494+
495+ it ( "should error when trying to verify and change email without idToken or email or newEmail" , async ( ) => {
496+ const user = { email :
"[email protected] " , password :
"notasecret" } ; 497+ await registerUser ( authApi ( ) , user ) ;
498+
499+ await authApi ( )
500+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
501+ . query ( { key : "fake-api-key" } )
502+ . send ( { newEmail :
"[email protected] " , requestType :
"VERIFY_AND_CHANGE_EMAIL" } ) 503+ . then ( ( res ) => {
504+ expectStatusCode ( 400 , res ) ;
505+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "MISSING_ID_TOKEN" ) ;
506+ } ) ;
507+
508+ await authApi ( )
509+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
510+ . query ( { key : "fake-api-key" } )
511+ . send ( { email : user . email , requestType : "VERIFY_AND_CHANGE_EMAIL" } )
512+ . then ( ( res ) => {
513+ expectStatusCode ( 400 , res ) ;
514+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "MISSING_NEW_EMAIL" ) ;
515+ } ) ;
516+
517+ await authApi ( )
518+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
519+ . set ( "Authorization" , "Bearer owner" )
520+ . send ( {
521+ 522+ returnOobLink : true ,
523+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
524+ } )
525+ . then ( ( res ) => {
526+ expectStatusCode ( 400 , res ) ;
527+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "MISSING_EMAIL" ) ;
528+ } ) ;
529+
530+ await authApi ( )
531+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
532+ . set ( "Authorization" , "Bearer owner" )
533+ . send ( {
534+ email : user . email ,
535+ returnOobLink : true ,
536+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
537+ } )
538+ . then ( ( res ) => {
539+ expectStatusCode ( 400 , res ) ;
540+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "MISSING_NEW_EMAIL" ) ;
541+ } ) ;
542+
543+ const oobs = await inspectOobs ( authApi ( ) ) ;
544+ expect ( oobs ) . to . have . length ( 0 ) ;
545+ } ) ;
546+
547+ it ( "should error when trying to verify and change email without idToken if not returnOobLink" , async ( ) => {
548+ const user = await registerUser ( authApi ( ) , {
549+ 550+ password : "notasecret" ,
551+ } ) ;
552+
553+ await authApi ( )
554+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
555+ . query ( { key : "fake-api-key" } )
556+ . send ( {
557+ email : user . email ,
558+ 559+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
560+ } )
561+ . then ( ( res ) => {
562+ expectStatusCode ( 400 , res ) ;
563+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "MISSING_ID_TOKEN" ) ;
564+ } ) ;
565+
566+ const oobs = await inspectOobs ( authApi ( ) ) ;
567+ expect ( oobs ) . to . have . length ( 0 ) ;
568+ } ) ;
569+
570+ it ( "should error when trying to verify and change email not associated with any user" , async ( ) => {
571+ await authApi ( )
572+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
573+ . set ( "Authorization" , "Bearer owner" )
574+ . send ( {
575+ 576+ 577+ returnOobLink : true ,
578+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
579+ } )
580+ . then ( ( res ) => {
581+ expectStatusCode ( 400 , res ) ;
582+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "USER_NOT_FOUND" ) ;
583+ } ) ;
584+
585+ const oobs = await inspectOobs ( authApi ( ) ) ;
586+ expect ( oobs ) . to . have . length ( 0 ) ;
587+ } ) ;
588+
589+ it ( "should error if newEmail is already associated to another user" , async ( ) => {
590+ const user = {
591+ 592+ password : "notasecret" ,
593+ } ;
594+ const { idToken } = await registerUser ( authApi ( ) , user ) ;
595+ const anotherUser = await registerUser ( authApi ( ) , {
596+ 597+ password : "notasecret" ,
598+ } ) ;
599+
600+ await authApi ( )
601+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
602+ . query ( { key : "fake-api-key" } )
603+ . send ( {
604+ idToken,
605+ email : user . email ,
606+ newEmail : anotherUser . email ,
607+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
608+ } )
609+ . then ( ( res ) => {
610+ expectStatusCode ( 400 , res ) ;
611+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "EMAIL_EXISTS" ) ;
612+ } ) ;
613+
614+ await authApi ( )
615+ . post ( "/identitytoolkit.googleapis.com/v1/accounts:sendOobCode" )
616+ . set ( "Authorization" , "Bearer owner" )
617+ . send ( {
618+ email : user . email ,
619+ newEmail : anotherUser . email ,
620+ returnOobLink : true ,
621+ requestType : "VERIFY_AND_CHANGE_EMAIL" ,
622+ } )
623+ . then ( ( res ) => {
624+ expectStatusCode ( 400 , res ) ;
625+ expect ( res . body . error ) . to . have . property ( "message" ) . equal ( "EMAIL_EXISTS" ) ;
626+ } ) ;
627+
628+ const oobs = await inspectOobs ( authApi ( ) ) ;
629+ expect ( oobs ) . to . have . length ( 0 ) ;
630+ } ) ;
398631} ) ;
0 commit comments