Skip to content
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 239 additions & 1 deletion test/integration/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from '../../lib/auth/index';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { ActionCodeSettings } from '../../src/auth/action-code-settings-builder';

const chalk = require('chalk'); // eslint-disable-line @typescript-eslint/no-var-requires

Expand Down Expand Up @@ -75,10 +76,25 @@ const mockUserData = {
photoURL: 'http://www.example.com/' + newUserUid + '/photo.png',
disabled: false,
};
const actionCodeSettings = {
const actionCodeSettings : ActionCodeSettings = {
url: 'http://localhost/?a=1&b=2#c=3',
handleCodeInApp: false,
};
const actionCodeSettingsWithCustomDomain: ActionCodeSettings = {
url: 'http://localhost/?a=1&b=2#c=3',
handleCodeInApp: true,
linkDomain: 'kobayashimaru.testdomaindonotuse.com',
iOS: {
bundleId: 'testBundleId',
},
}
const actionCodeSettingsForFdlLinks: ActionCodeSettings = {
url: 'http://localhost/?a=1&b=2#c=3',
handleCodeInApp: true,
iOS: {
bundleId: 'testBundleId',
},
}
let deleteQueue = Promise.resolve();

interface UserImportTest {
Expand Down Expand Up @@ -1105,6 +1121,13 @@ describe('admin.auth', () => {

// Create the test user before running this suite of tests.
before(() => {
// Update project config to have HOSTING_DOMAIN as mobileLinksConfig after each test
const updateMobileLinksRequest: UpdateProjectConfigRequest = {
mobileLinksConfig: {
domain: 'HOSTING_DOMAIN',
},
};
getAuth().projectConfigManager().updateProjectConfig(updateMobileLinksRequest);
return getAuth().createUser(userData);
});

Expand Down Expand Up @@ -1199,6 +1222,162 @@ describe('admin.auth', () => {
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateSignInWithEmailLink() with custom linkDomain should return error in case of invalid hosting domain',
function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}
const actionCodeSettingsWithInvalidLinkDomain = deepCopy(actionCodeSettings);
actionCodeSettingsWithInvalidLinkDomain.linkDomain = 'invaliddomain.firebaseapp.com';
return getAuth().generateSignInWithEmailLink(email, actionCodeSettingsWithInvalidLinkDomain)
.catch((error) => {
expect(error.code).to.equal('auth/invalid-hosting-link-domain');
});
});

it('generatePasswordResetLink() should return a password reset link with custom domain', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}
// Ensure old password set on created user.
return getAuth().updateUser(uid, { password: 'password' })
.then(() => {
return getAuth().generatePasswordResetLink(email, actionCodeSettingsWithCustomDomain);
})
.then((link) => {
const code = getActionCode(link);
expect(getContinueUrlForInAppRequest(link)).equal(actionCodeSettings.url);
expect(getHostName(link)).equal(actionCodeSettingsWithCustomDomain.linkDomain);
return clientAuth().confirmPasswordReset(code, newPassword);
})
.then(() => {
return clientAuth().signInWithEmailAndPassword(email, newPassword);
})
.then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(email);
// Password reset also verifies the user's email.
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateEmailVerificationLink() should return a verification link with custom domain', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}
// Ensure the user's email is unverified.
return getAuth().updateUser(uid, { password: '123456', emailVerified: false })
.then((userRecord) => {
expect(userRecord.emailVerified).to.be.false;
return getAuth().generateEmailVerificationLink(email, actionCodeSettingsWithCustomDomain);
})
.then((link) => {
const code = getActionCode(link);
expect(getContinueUrlForInAppRequest(link)).equal(actionCodeSettings.url);
expect(getHostName(link)).equal(actionCodeSettingsWithCustomDomain.linkDomain);
return clientAuth().applyActionCode(code);
})
.then(() => {
return clientAuth().signInWithEmailAndPassword(email, userData.password);
})
.then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(email);
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateSignInWithEmailLink() should return a sign-in link with custom domain', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}
return getAuth().generateSignInWithEmailLink(email, actionCodeSettingsWithCustomDomain)
.then((link) => {
expect(getContinueUrlForInAppRequest(link)).equal(actionCodeSettingsWithCustomDomain.url);
expect(getHostName(link)).equal(actionCodeSettingsWithCustomDomain.linkDomain);
return clientAuth().signInWithEmailLink(email, link);
})
.then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(email);
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateVerifyAndChangeEmailLink() should return a verification link with custom domain', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}
// Ensure the user's email is verified.
return getAuth().updateUser(uid, { password: '123456', emailVerified: true })
.then((userRecord) => {
expect(userRecord.emailVerified).to.be.true;
return getAuth().generateVerifyAndChangeEmailLink(email, newEmail, actionCodeSettingsWithCustomDomain);
})
.then((link) => {
const code = getActionCode(link);
expect(getContinueUrlForInAppRequest(link)).equal(actionCodeSettings.url);
expect(getHostName(link)).equal(actionCodeSettingsWithCustomDomain.linkDomain);
return clientAuth().applyActionCode(code);
})
.then(() => {
return clientAuth().signInWithEmailAndPassword(newEmail, 'password');
})
.then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(newEmail);
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateSignInWithEmailLink() should return a FDL sign-in'
+ 'link with mobileLinksConfig set to FIREBASE_DYNAMIC_LINK_DOMAIN', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}

const updateMobileLinksRequest: UpdateProjectConfigRequest = {
mobileLinksConfig: {
domain: 'FIREBASE_DYNAMIC_LINK_DOMAIN',
}
};
return getAuth().projectConfigManager().updateProjectConfig(updateMobileLinksRequest)
.then((projectConfig) => {
expect(projectConfig?.mobileLinksConfig?.domain).equal('FIREBASE_DYNAMIC_LINK_DOMAIN');
return getAuth().generateSignInWithEmailLink(email, actionCodeSettingsForFdlLinks);
}).then((link) => {
expectFDLLink(link);
return clientAuth().signInWithEmailLink(email, link);
}).then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(email);
expect(result.user!.emailVerified).to.be.true;
});
});

it('generateSignInWithEmailLink() should return a FDL sign-in link with empty mobileLinksConfig', function () {
if (authEmulatorHost) {
return this.skip(); // Not yet supported in Auth Emulator.
}

const updateMobileLinksRequest: UpdateProjectConfigRequest = {
mobileLinksConfig: {
}
};
return getAuth().projectConfigManager().updateProjectConfig(updateMobileLinksRequest)
.then((projectConfig) => {
expect(projectConfig.mobileLinksConfig).is.empty;
return getAuth().generateSignInWithEmailLink(email, actionCodeSettingsForFdlLinks);
}).then((link) => {
expectFDLLink(link);
return clientAuth().signInWithEmailLink(email, link);
}).then((result) => {
expect(result.user).to.exist;
expect(result.user!.email).to.equal(email);
expect(result.user!.emailVerified).to.be.true;
});
});
});

describe('Project config management operations', () => {
Expand Down Expand Up @@ -1285,6 +1464,9 @@ describe('admin.auth', () => {
},
emailPrivacyConfig: {
enableImprovedEmailPrivacy: true,
},
mobileLinksConfig: {
domain: 'HOSTING_DOMAIN',
}
};
const projectConfigOption2: UpdateProjectConfigRequest = {
Expand Down Expand Up @@ -1318,6 +1500,9 @@ describe('admin.auth', () => {
emailPrivacyConfig: {
enableImprovedEmailPrivacy: true,
},
mobileLinksConfig: {
domain: 'HOSTING_DOMAIN',
},
};
const expectedProjectConfig2: any = {
smsRegionConfig: smsRegionAllowlistOnlyConfig,
Expand All @@ -1333,6 +1518,9 @@ describe('admin.auth', () => {
],
},
emailPrivacyConfig: {},
mobileLinksConfig: {
domain: 'HOSTING_DOMAIN',
},
};
const expectedProjectConfigSmsEnabledTotpDisabled: any = {
smsRegionConfig: smsRegionAllowlistOnlyConfig,
Expand All @@ -1348,6 +1536,9 @@ describe('admin.auth', () => {
],
},
emailPrivacyConfig: {},
mobileLinksConfig: {
domain: 'HOSTING_DOMAIN',
},
};

it('updateProjectConfig() should resolve with the updated project config', () => {
Expand Down Expand Up @@ -3194,6 +3385,53 @@ function getContinueUrl(link: string): string {
return continueUrl!;
}

/**
* Returns the host name corresponding to the link.
*
* @param link The link to parse for hostname
* @returns Hostname in the link
*/
function getHostName(link: string): string {
const parsedUrl = new url.URL(link);
return parsedUrl.hostname;
}

/**
* Returns continue URL for handling in app requests.
* URL will be of the form, http://abc/__/auth/link?link=<action link url>
* Coninue URL will be part of action link url
*
* @param link The link to parse for continue url
* @returns
*/
function getContinueUrlForInAppRequest(link: string): string {
// Extract action url from link param
const parsedUrl = new url.URL(link);
const linkParam = parsedUrl.searchParams.get('link') ?? '';
expect(linkParam).is.not.empty;

// Extract continueUrl param from action url
const actionUrl = new url.URL(linkParam);
const continueUrl = actionUrl.searchParams.get('continueUrl');
expect(continueUrl).to.exist;
return continueUrl!;
}

/**
* Verify if the generated link is generated by FDL
* We leverage the params created by FDL to test whether a given link is FDL
*
* @param link Link to check whether it is FDL
*/
function expectFDLLink(link: string): void {
const parsedUrl = new url.URL(link);
// For ios, FDL creates a fallback url with param ifl
// We leverage that to test whether a given link is FDL link
// Note: This param does not exist when the link is generated for HOSTING_DOMAIN
const iflParam = parsedUrl.searchParams.get('ifl');
expect(iflParam).is.not.null;
}

/**
* Returns the tenant ID corresponding to the link.
*
Expand Down