@@ -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,102 @@ 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+ ( ) => {
1214+ const actionCodeSettingsWithInvalidLinkDomain = deepCopy ( actionCodeSettings ) ;
1215+ actionCodeSettingsWithInvalidLinkDomain . linkDomain = 'invaliddomain.firebaseapp.com' ;
1216+ return getAuth ( ) . generateSignInWithEmailLink ( email , actionCodeSettingsWithInvalidLinkDomain )
1217+ . catch ( ( error ) => {
1218+ expect ( error . code ) . to . equal ( 'auth/invalid-hosting-link-domain' ) ;
1219+ } ) ;
1220+ } ) ;
1221+
1222+ it ( 'generatePasswordResetLink() should return a password reset link with custom domain' , ( ) => {
1223+ // Ensure old password set on created user.
1224+ return getAuth ( ) . updateUser ( uid , { password : 'password' } )
1225+ . then ( ( ) => {
1226+ return getAuth ( ) . generatePasswordResetLink ( email , actionCodeSettingsWithCustomDomain ) ;
1227+ } )
1228+ . then ( ( link ) => {
1229+ const code = getActionCode ( link ) ;
1230+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1231+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1232+ return clientAuth ( ) . confirmPasswordReset ( code , newPassword ) ;
1233+ } )
1234+ . then ( ( ) => {
1235+ return clientAuth ( ) . signInWithEmailAndPassword ( email , newPassword ) ;
1236+ } )
1237+ . then ( ( result ) => {
1238+ expect ( result . user ) . to . exist ;
1239+ expect ( result . user ! . email ) . to . equal ( email ) ;
1240+ // Password reset also verifies the user's email.
1241+ expect ( result . user ! . emailVerified ) . to . be . true ;
1242+ } ) ;
1243+ } ) ;
1244+
1245+ it ( 'generateEmailVerificationLink() should return a verification link with custom domain' , ( ) => {
1246+ // Ensure the user's email is unverified.
1247+ return getAuth ( ) . updateUser ( uid , { password : '123456' , emailVerified : false } )
1248+ . then ( ( userRecord ) => {
1249+ expect ( userRecord . emailVerified ) . to . be . false ;
1250+ return getAuth ( ) . generateEmailVerificationLink ( email , actionCodeSettingsWithCustomDomain ) ;
1251+ } )
1252+ . then ( ( link ) => {
1253+ const code = getActionCode ( link ) ;
1254+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1255+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1256+ return clientAuth ( ) . applyActionCode ( code ) ;
1257+ } )
1258+ . then ( ( ) => {
1259+ return clientAuth ( ) . signInWithEmailAndPassword ( email , userData . password ) ;
1260+ } )
1261+ . then ( ( result ) => {
1262+ expect ( result . user ) . to . exist ;
1263+ expect ( result . user ! . email ) . to . equal ( email ) ;
1264+ expect ( result . user ! . emailVerified ) . to . be . true ;
1265+ } ) ;
1266+ } ) ;
1267+
1268+ it ( 'generateSignInWithEmailLink() should return a sign-in link with custom domain' , ( ) => {
1269+ return getAuth ( ) . generateSignInWithEmailLink ( email , actionCodeSettingsWithCustomDomain )
1270+ . then ( ( link ) => {
1271+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . url ) ;
1272+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1273+ return clientAuth ( ) . signInWithEmailLink ( email , link ) ;
1274+ } )
1275+ . then ( ( result ) => {
1276+ expect ( result . user ) . to . exist ;
1277+ expect ( result . user ! . email ) . to . equal ( email ) ;
1278+ expect ( result . user ! . emailVerified ) . to . be . true ;
1279+ } ) ;
1280+ } ) ;
1281+
1282+ it ( 'generateVerifyAndChangeEmailLink() should return a verification link with custom domain' , function ( ) {
1283+ if ( authEmulatorHost ) {
1284+ return this . skip ( ) ; // Not yet supported in Auth Emulator.
1285+ }
1286+ // Ensure the user's email is verified.
1287+ return getAuth ( ) . updateUser ( uid , { password : '123456' , emailVerified : true } )
1288+ . then ( ( userRecord ) => {
1289+ expect ( userRecord . emailVerified ) . to . be . true ;
1290+ return getAuth ( ) . generateVerifyAndChangeEmailLink ( email , newEmail , actionCodeSettingsWithCustomDomain ) ;
1291+ } )
1292+ . then ( ( link ) => {
1293+ const code = getActionCode ( link ) ;
1294+ expect ( getContinueUrlForInAppRequest ( link ) ) . equal ( actionCodeSettings . url ) ;
1295+ expect ( getHostName ( link ) ) . equal ( actionCodeSettingsWithCustomDomain . linkDomain ) ;
1296+ return clientAuth ( ) . applyActionCode ( code ) ;
1297+ } )
1298+ . then ( ( ) => {
1299+ return clientAuth ( ) . signInWithEmailAndPassword ( newEmail , 'password' ) ;
1300+ } )
1301+ . then ( ( result ) => {
1302+ expect ( result . user ) . to . exist ;
1303+ expect ( result . user ! . email ) . to . equal ( newEmail ) ;
1304+ expect ( result . user ! . emailVerified ) . to . be . true ;
1305+ } ) ;
1306+ } ) ;
12021307 } ) ;
12031308
12041309 describe ( 'Project config management operations' , ( ) => {
@@ -1285,6 +1390,9 @@ describe('admin.auth', () => {
12851390 } ,
12861391 emailPrivacyConfig : {
12871392 enableImprovedEmailPrivacy : true ,
1393+ } ,
1394+ mobileLinksConfig : {
1395+ domain : 'HOSTING_DOMAIN' ,
12881396 }
12891397 } ;
12901398 const projectConfigOption2 : UpdateProjectConfigRequest = {
@@ -1318,6 +1426,9 @@ describe('admin.auth', () => {
13181426 emailPrivacyConfig : {
13191427 enableImprovedEmailPrivacy : true ,
13201428 } ,
1429+ mobileLinksConfig : {
1430+ domain : 'HOSTING_DOMAIN' ,
1431+ } ,
13211432 } ;
13221433 const expectedProjectConfig2 : any = {
13231434 smsRegionConfig : smsRegionAllowlistOnlyConfig ,
@@ -1333,6 +1444,9 @@ describe('admin.auth', () => {
13331444 ] ,
13341445 } ,
13351446 emailPrivacyConfig : { } ,
1447+ mobileLinksConfig : {
1448+ domain : 'HOSTING_DOMAIN' ,
1449+ } ,
13361450 } ;
13371451 const expectedProjectConfigSmsEnabledTotpDisabled : any = {
13381452 smsRegionConfig : smsRegionAllowlistOnlyConfig ,
@@ -1348,6 +1462,9 @@ describe('admin.auth', () => {
13481462 ] ,
13491463 } ,
13501464 emailPrivacyConfig : { } ,
1465+ mobileLinksConfig : {
1466+ domain : 'HOSTING_DOMAIN' ,
1467+ } ,
13511468 } ;
13521469
13531470 it ( 'updateProjectConfig() should resolve with the updated project config' , ( ) => {
@@ -3194,6 +3311,38 @@ function getContinueUrl(link: string): string {
31943311 return continueUrl ! ;
31953312}
31963313
3314+ /**
3315+ * Returns the host name corresponding to the link.
3316+ *
3317+ * @param link The link to parse for hostname
3318+ * @returns Hostname in the link
3319+ */
3320+ function getHostName ( link : string ) : string {
3321+ const parsedUrl = new url . URL ( link ) ;
3322+ return parsedUrl . hostname ;
3323+ }
3324+
3325+ /**
3326+ * Returns continue URL for handling in app requests.
3327+ * URL will be of the form, http://abc/__/auth/link?link=<action link url>
3328+ * Coninue URL will be part of action link url
3329+ *
3330+ * @param link
3331+ * @returns
3332+ */
3333+ function getContinueUrlForInAppRequest ( link : string ) : string {
3334+ // Extract action url from link param
3335+ const parsedUrl = new url . URL ( link ) ;
3336+ const linkParam = parsedUrl . searchParams . get ( 'link' ) ?? '' ;
3337+ expect ( linkParam ) . is . not . empty ;
3338+
3339+ // Extract continueUrl param from action url
3340+ const actionUrl = new url . URL ( linkParam ) ;
3341+ const continueUrl = actionUrl . searchParams . get ( 'continueUrl' ) ;
3342+ expect ( continueUrl ) . to . exist ;
3343+ return continueUrl ! ;
3344+ }
3345+
31973346/**
31983347 * Returns the tenant ID corresponding to the link.
31993348 *
0 commit comments