@@ -36,6 +36,7 @@ import {
3636} from '../../lib/auth/index' ;
3737import * as sinon from 'sinon' ;
3838import * as sinonChai from 'sinon-chai' ;
39+ import { ActionCodeSettings } from '../../src/auth/action-code-settings-builder' ;
3940
4041const chalk = require ( 'chalk' ) ; // eslint-disable-line @typescript-eslint/no-var-requires
4142
@@ -75,10 +76,18 @@ const mockUserData = {
7576 photoURL : 'http://www.example.com/' + newUserUid + '/photo.png' ,
7677 disabled : false ,
7778} ;
78- const actionCodeSettings = {
79+ const actionCodeSettings : ActionCodeSettings = {
7980 url : 'http://localhost/?a=1&b=2#c=3' ,
8081 handleCodeInApp : false ,
8182} ;
83+ const actionCodeSettingsWithCustomDomain : ActionCodeSettings = {
84+ url : 'http://localhost/?a=1&b=2#c=3' ,
85+ handleCodeInApp : true ,
86+ linkDomain : 'liubin-dev2.testdomaindonotuse.com' ,
87+ iOS : {
88+ bundleId : "com.google.experimental1.dev.extension0" ,
89+ } ,
90+ }
8291let deleteQueue = Promise . resolve ( ) ;
8392
8493interface UserImportTest {
@@ -1199,6 +1208,101 @@ describe('admin.auth', () => {
11991208 expect ( result . user ! . emailVerified ) . to . be . true ;
12001209 } ) ;
12011210 } ) ;
1211+
1212+ it ( 'generateSignInWithEmailLink() with custom linkDomain should return error in case of invalid hosting domain' , ( ) => {
1213+ let actionCodeSettingsWithLinkDomain = deepCopy ( actionCodeSettings ) ;
1214+ actionCodeSettingsWithLinkDomain . linkDomain = 'invaliddomain.firebaseapp.com' ;
1215+ return getAuth ( ) . generateSignInWithEmailLink ( email , actionCodeSettingsWithLinkDomain )
1216+ . catch ( ( error ) => {
1217+ expect ( error . code ) . to . equal ( 'auth/invalid-hosting-link-domain' ) ;
1218+ } ) ;
1219+ } ) ;
1220+
1221+ it ( 'generatePasswordResetLink() should return a password reset link with custom domain' , ( ) => {
1222+ // Ensure old password set on created user.
1223+ return getAuth ( ) . updateUser ( uid , { password : 'password' } )
1224+ . then ( ( ) => {
1225+ return getAuth ( ) . generatePasswordResetLink ( email , actionCodeSettingsWithCustomDomain ) ;
1226+ } )
1227+ . then ( ( link ) => {
1228+ const code = getActionCode ( link ) ;
1229+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1230+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1231+ return clientAuth ( ) . confirmPasswordReset ( code , newPassword ) ;
1232+ } )
1233+ . then ( ( ) => {
1234+ return clientAuth ( ) . signInWithEmailAndPassword ( email , newPassword ) ;
1235+ } )
1236+ . then ( ( result ) => {
1237+ expect ( result . user ) . to . exist ;
1238+ expect ( result . user ! . email ) . to . equal ( email ) ;
1239+ // Password reset also verifies the user's email.
1240+ expect ( result . user ! . emailVerified ) . to . be . true ;
1241+ } ) ;
1242+ } ) ;
1243+
1244+ it ( 'generateEmailVerificationLink() should return a verification link with custom domain' , ( ) => {
1245+ // Ensure the user's email is unverified.
1246+ return getAuth ( ) . updateUser ( uid , { password : '123456' , emailVerified : false } )
1247+ . then ( ( userRecord ) => {
1248+ expect ( userRecord . emailVerified ) . to . be . false ;
1249+ return getAuth ( ) . generateEmailVerificationLink ( email , actionCodeSettingsWithCustomDomain ) ;
1250+ } )
1251+ . then ( ( link ) => {
1252+ const code = getActionCode ( link ) ;
1253+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1254+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1255+ return clientAuth ( ) . applyActionCode ( code ) ;
1256+ } )
1257+ . then ( ( ) => {
1258+ return clientAuth ( ) . signInWithEmailAndPassword ( email , userData . password ) ;
1259+ } )
1260+ . then ( ( result ) => {
1261+ expect ( result . user ) . to . exist ;
1262+ expect ( result . user ! . email ) . to . equal ( email ) ;
1263+ expect ( result . user ! . emailVerified ) . to . be . true ;
1264+ } ) ;
1265+ } ) ;
1266+
1267+ it ( 'generateSignInWithEmailLink() should return a sign-in link with custom domain' , ( ) => {
1268+ return getAuth ( ) . generateSignInWithEmailLink ( email , actionCodeSettingsWithCustomDomain )
1269+ . then ( ( link ) => {
1270+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . url ) ;
1271+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1272+ return clientAuth ( ) . signInWithEmailLink ( email , link ) ;
1273+ } )
1274+ . then ( ( result ) => {
1275+ expect ( result . user ) . to . exist ;
1276+ expect ( result . user ! . email ) . to . equal ( email ) ;
1277+ expect ( result . user ! . emailVerified ) . to . be . true ;
1278+ } ) ;
1279+ } ) ;
1280+
1281+ it ( 'generateVerifyAndChangeEmailLink() should return a verification link with custom domain' , function ( ) {
1282+ if ( authEmulatorHost ) {
1283+ return this . skip ( ) ; // Not yet supported in Auth Emulator.
1284+ }
1285+ // Ensure the user's email is verified.
1286+ return getAuth ( ) . updateUser ( uid , { password : '123456' , emailVerified : true } )
1287+ . then ( ( userRecord ) => {
1288+ expect ( userRecord . emailVerified ) . to . be . true ;
1289+ return getAuth ( ) . generateVerifyAndChangeEmailLink ( email , newEmail , actionCodeSettingsWithCustomDomain ) ;
1290+ } )
1291+ . then ( ( link ) => {
1292+ const code = getActionCode ( link ) ;
1293+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1294+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1295+ return clientAuth ( ) . applyActionCode ( code ) ;
1296+ } )
1297+ . then ( ( ) => {
1298+ return clientAuth ( ) . signInWithEmailAndPassword ( newEmail , 'password' ) ;
1299+ } )
1300+ . then ( ( result ) => {
1301+ expect ( result . user ) . to . exist ;
1302+ expect ( result . user ! . email ) . to . equal ( newEmail ) ;
1303+ expect ( result . user ! . emailVerified ) . to . be . true ;
1304+ } ) ;
1305+ } ) ;
12021306 } ) ;
12031307
12041308 describe ( 'Project config management operations' , ( ) => {
@@ -1285,6 +1389,9 @@ describe('admin.auth', () => {
12851389 } ,
12861390 emailPrivacyConfig : {
12871391 enableImprovedEmailPrivacy : true ,
1392+ } ,
1393+ mobileLinksConfig : {
1394+ domain : 'HOSTING_DOMAIN' ,
12881395 }
12891396 } ;
12901397 const projectConfigOption2 : UpdateProjectConfigRequest = {
@@ -1318,6 +1425,9 @@ describe('admin.auth', () => {
13181425 emailPrivacyConfig : {
13191426 enableImprovedEmailPrivacy : true ,
13201427 } ,
1428+ mobileLinksConfig : {
1429+ domain : 'HOSTING_DOMAIN' ,
1430+ } ,
13211431 } ;
13221432 const expectedProjectConfig2 : any = {
13231433 smsRegionConfig : smsRegionAllowlistOnlyConfig ,
@@ -1333,6 +1443,9 @@ describe('admin.auth', () => {
13331443 ] ,
13341444 } ,
13351445 emailPrivacyConfig : { } ,
1446+ mobileLinksConfig : {
1447+ domain : 'HOSTING_DOMAIN' ,
1448+ } ,
13361449 } ;
13371450 const expectedProjectConfigSmsEnabledTotpDisabled : any = {
13381451 smsRegionConfig : smsRegionAllowlistOnlyConfig ,
@@ -1348,6 +1461,9 @@ describe('admin.auth', () => {
13481461 ] ,
13491462 } ,
13501463 emailPrivacyConfig : { } ,
1464+ mobileLinksConfig : {
1465+ domain : 'HOSTING_DOMAIN' ,
1466+ } ,
13511467 } ;
13521468
13531469 it ( 'updateProjectConfig() should resolve with the updated project config' , ( ) => {
@@ -3194,6 +3310,38 @@ function getContinueUrl(link: string): string {
31943310 return continueUrl ! ;
31953311}
31963312
3313+ /**
3314+ * Returns the host name corresponding to the link.
3315+ *
3316+ * @param link The link to parse for hostname
3317+ * @returns Hostname in the link
3318+ */
3319+ function getHostName ( link : string ) : string {
3320+ const parsedUrl = new url . URL ( link ) ;
3321+ return parsedUrl . hostname ;
3322+ }
3323+
3324+ /**
3325+ * Returns continue URL for handling in app requests.
3326+ * URL will be of the form, http://abc/__/auth/link?link=<action link url>
3327+ * Coninue URL will be part of action link url
3328+ *
3329+ * @param link
3330+ * @returns
3331+ */
3332+ function getContinueUrlForInAppRequest ( link : string ) : string {
3333+ // Extract action url from link param
3334+ const parsedUrl = new url . URL ( link ) ;
3335+ const linkParam = parsedUrl . searchParams . get ( 'link' ) ?? "" ;
3336+ expect ( linkParam ) . is . not . empty ;
3337+
3338+ // Extract continueUrl param from action url
3339+ const actionUrl = new url . URL ( linkParam ) ;
3340+ const continueUrl = actionUrl . searchParams . get ( 'continueUrl' ) ;
3341+ expect ( continueUrl ) . to . exist ;
3342+ return continueUrl ! ;
3343+ }
3344+
31973345/**
31983346 * Returns the tenant ID corresponding to the link.
31993347 *
0 commit comments