diff --git a/packages/auth-compat/test/helpers/helpers.ts b/packages/auth-compat/test/helpers/helpers.ts index 005f8efa61a..18e0cc2c571 100644 --- a/packages/auth-compat/test/helpers/helpers.ts +++ b/packages/auth-compat/test/helpers/helpers.ts @@ -25,6 +25,7 @@ import { getEmulatorUrl } from '../../../auth/test/helpers/integration/settings'; import { resetEmulator } from '../../../auth/test/helpers/integration/emulator_rest_helpers'; +export { createNewTenant } from '../../../auth/test/helpers/integration/emulator_rest_helpers'; export function initializeTestInstance(): void { firebase.initializeApp(getAppConfig()); @@ -34,6 +35,7 @@ export function initializeTestInstance(): void { } export async function cleanUpTestInstance(): Promise { + await firebase.auth().signOut(); for (const app of firebase.apps) { await app.delete(); } diff --git a/packages/auth-compat/test/integration/flows/multi_tenant.test.ts b/packages/auth-compat/test/integration/flows/multi_tenant.test.ts new file mode 100644 index 00000000000..fed540fca48 --- /dev/null +++ b/packages/auth-compat/test/integration/flows/multi_tenant.test.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase from '@firebase/app-compat'; +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { + createNewTenant, + initializeTestInstance, + cleanUpTestInstance +} from '../../helpers/helpers'; + +use(chaiAsPromised); + +describe('Integration test: multi-tenant', () => { + let tenantA: string; + let tenantB: string; + + beforeEach(async () => { + initializeTestInstance(); + tenantA = await createNewTenant(); + tenantB = await createNewTenant(); + }); + + afterEach(async () => { + await cleanUpTestInstance(); + }); + + it('sets the correct tenantId on the underlying user', async () => { + firebase.auth().tenantId = tenantA; + const { user } = await firebase.auth().signInAnonymously(); + expect(user!.tenantId).to.eq(tenantA); + }); + + it('allows updateCurrentUser to be called when TID matches', async () => { + firebase.auth().tenantId = tenantA; + const { user } = await firebase.auth().signInAnonymously(); + await expect(firebase.auth().updateCurrentUser(user)).not.to.be.rejected; + }); + + it('throws for mismatched TID', async () => { + firebase.auth().tenantId = tenantA; + const { user } = await firebase.auth().signInAnonymously(); + firebase.auth().tenantId = tenantB; + await expect(firebase.auth().updateCurrentUser(user)).to.be.rejectedWith( + 'auth/tenant-id-mismatch' + ); + }); + + it('allows users to be deleted', async () => { + firebase.auth().tenantId = tenantA; + const { user } = await firebase.auth().signInAnonymously(); + await user!.delete(); + expect(firebase.auth().currentUser).to.be.null; + }); + + // The rest of the tenantId tests are in the respective flow tests +}); diff --git a/packages/auth-compat/test/integration/webdriver/static/core.js b/packages/auth-compat/test/integration/webdriver/static/core.js index 7b75cbb2fbc..7deb1ad9fc2 100644 --- a/packages/auth-compat/test/integration/webdriver/static/core.js +++ b/packages/auth-compat/test/integration/webdriver/static/core.js @@ -34,6 +34,10 @@ export function legacyAuthInit() { }); } +export async function setTenantId(tid) { + compat.auth().tenantId = tid; +} + export async function userSnap() { return compat.auth().currentUser; } diff --git a/packages/auth/test/helpers/integration/emulator_rest_helpers.ts b/packages/auth/test/helpers/integration/emulator_rest_helpers.ts index 5172eb3d6c4..48d57563166 100644 --- a/packages/auth/test/helpers/integration/emulator_rest_helpers.ts +++ b/packages/auth/test/helpers/integration/emulator_rest_helpers.ts @@ -16,7 +16,7 @@ */ import * as fetchImpl from 'node-fetch'; -import { getAppConfig, getEmulatorUrl } from './settings'; +import { API_KEY, getAppConfig, getEmulatorUrl } from './settings'; export interface VerificationSession { code: string; @@ -51,8 +51,8 @@ export async function getPhoneVerificationCodes(): Promise< }, {} as Record); } -export async function getOobCodes(): Promise { - const url = buildEmulatorUrlForPath('oobCodes'); +export async function getOobCodes(tenantId?: string): Promise { + const url = buildEmulatorUrlForPath('oobCodes', tenantId); const response: OobCodesResponse = await (await doFetch(url)).json(); return response.oobCodes; } @@ -78,10 +78,31 @@ export async function createAnonAccount(): Promise<{ return response; } -function buildEmulatorUrlForPath(endpoint: string): string { +export async function createNewTenant(): Promise { + const projectId = getAppConfig().projectId; + const url = `${getEmulatorUrl()}/identitytoolkit.googleapis.com/v2/projects/${projectId}/tenants?key=${API_KEY}`; + const request = { + name: 'tenant', + allowPasswordSignup: true, + enableEmailLinkSignin: true, + disableAuth: false, + enableAnonymousUser: true, + }; + const response = await ( + await doFetch(url, { + method: 'POST', + body: JSON.stringify(request), + headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer owner'} + }) + ).json(); + return response.tenantId; +} + +function buildEmulatorUrlForPath(endpoint: string, tenantId?: string): string { const emulatorBaseUrl = getEmulatorUrl(); const projectId = getAppConfig().projectId; - return `${emulatorBaseUrl}/emulator/v1/projects/${projectId}/${endpoint}`; + const tenantScope = tenantId ? `/tenants/${tenantId}` : ''; + return `${emulatorBaseUrl}/emulator/v1/projects/${projectId}${tenantScope}/${endpoint}`; } function doFetch(url: string, request?: RequestInit): ReturnType { diff --git a/packages/auth/test/integration/flows/anonymous.test.ts b/packages/auth/test/integration/flows/anonymous.test.ts index 1c744b251fc..bba48c36720 100644 --- a/packages/auth/test/integration/flows/anonymous.test.ts +++ b/packages/auth/test/integration/flows/anonymous.test.ts @@ -55,6 +55,8 @@ describe('Integration test: anonymous auth', () => { expect(user.uid).to.be.a('string'); }); + // Multi-tenancy for anon auth covered by multi_tenant.local.test.ts + it('second sign in on the same device yields same user', async () => { const { user: userA } = await signInAnonymously(auth); const { user: userB } = await signInAnonymously(auth); diff --git a/packages/auth/test/integration/flows/custom.local.test.ts b/packages/auth/test/integration/flows/custom.local.test.ts index 6ffbb18afc7..fd3113921cc 100644 --- a/packages/auth/test/integration/flows/custom.local.test.ts +++ b/packages/auth/test/integration/flows/custom.local.test.ts @@ -34,6 +34,7 @@ import { import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { createNewTenant } from '../../helpers/integration/emulator_rest_helpers'; import { cleanUpTestInstance, getTestInstance, @@ -79,6 +80,26 @@ describe('Integration test: custom auth', () => { expect(additionalUserInfo.isNewUser).to.be.true; }); + it('signs in with custom token in tenant project', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const cred = await signInWithCustomToken(auth, customToken); + expect(auth.currentUser).to.eq(cred.user); + expect(cred.operationType).to.eq(OperationType.SIGN_IN); + + const { user } = cred; + expect(user.isAnonymous).to.be.false; + expect(user.uid).to.eq(uid); + expect(user.tenantId).to.eq(tenantId); + expect((await user.getIdTokenResult(false)).claims.customClaim).to.eq( + 'some-claim' + ); + expect(user.providerId).to.eq('firebase'); + const additionalUserInfo = await getAdditionalUserInfo(cred)!; + expect(additionalUserInfo.providerId).to.be.null; + expect(additionalUserInfo.isNewUser).to.be.true; + }); + it('uid will overwrite existing user, joining accounts', async () => { const { user: anonUser } = await signInAnonymously(auth); const customCred = await signInWithCustomToken( diff --git a/packages/auth/test/integration/flows/email.test.ts b/packages/auth/test/integration/flows/email.test.ts index bb73d95c383..dec400d742c 100644 --- a/packages/auth/test/integration/flows/email.test.ts +++ b/packages/auth/test/integration/flows/email.test.ts @@ -38,6 +38,7 @@ import { getTestInstance, randomEmail } from '../../helpers/integration/helpers'; +import { createNewTenant } from '../../helpers/integration/emulator_rest_helpers'; use(chaiAsPromised); @@ -74,6 +75,32 @@ describe('Integration test: email/password auth', () => { expect(additionalUserInfo.providerId).to.eq('password'); }); + it('allows user to sign up in tenant project', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const userCred = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + + const user = userCred.user; + expect(user.isAnonymous).to.be.false; + expect(user.uid).to.be.a('string'); + expect(user.email).to.eq(email); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].providerId).to.eq('password'); + expect(user.providerData[0].email).to.eq(email); + expect(user.tenantId).to.eq(tenantId); + + const additionalUserInfo = getAdditionalUserInfo(userCred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('password'); + }); + it('errors when createUser called twice', async () => { await createUserWithEmailAndPassword(auth, email, 'password'); await expect( diff --git a/packages/auth/test/integration/flows/idp.local.test.ts b/packages/auth/test/integration/flows/idp.local.test.ts index 9262c84c17c..f3062d437b7 100644 --- a/packages/auth/test/integration/flows/idp.local.test.ts +++ b/packages/auth/test/integration/flows/idp.local.test.ts @@ -36,6 +36,7 @@ import { import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { createNewTenant } from '../../helpers/integration/emulator_rest_helpers'; import { cleanUpTestInstance, getTestInstance, @@ -89,6 +90,31 @@ describe('Integration test: headless IdP', () => { expect(additionalUserInfo.providerId).to.eq('google.com'); }); + it('signs in with an OAuth token in a tenant', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const cred = await signInWithCredential( + auth, + GoogleAuthProvider.credential(oauthIdToken) + ); + expect(auth.currentUser).to.eq(cred.user); + expect(cred.operationType).to.eq(OperationType.SIGN_IN); + + // Make sure the user is setup correctly + const { user } = cred; + expect(user.isAnonymous).to.be.false; + expect(user.emailVerified).to.be.true; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].providerId).to.eq('google.com'); + expect(user.providerData[0].email).to.eq(email); + expect(user.tenantId).to.eq(tenantId); + + // Make sure the additional user info is good + const additionalUserInfo = getAdditionalUserInfo(cred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('google.com'); + }); + it('allows the user to update profile', async () => { const credential = GithubAuthProvider.credential(oauthIdToken); const { user } = await signInWithCredential(auth, credential); @@ -110,6 +136,30 @@ describe('Integration test: headless IdP', () => { expect(auth.currentUser!.photoURL).to.eq('http://photo.test/david.png'); }); + it('allows the user to update profile in a tenant', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const credential = GithubAuthProvider.credential(oauthIdToken); + const { user } = await signInWithCredential(auth, credential); + + await updateProfile(user, { + displayName: 'David Copperfield', + photoURL: 'http://photo.test/david.png' + }); + + // Check everything first + expect(user.displayName).to.eq('David Copperfield'); + expect(user.photoURL).to.eq('http://photo.test/david.png'); + + await auth.signOut(); + + // Sign in again and double check; look at current user this time + await signInWithCredential(auth, credential); + expect(auth.currentUser!.displayName).to.eq('David Copperfield'); + expect(auth.currentUser!.photoURL).to.eq('http://photo.test/david.png'); + expect(auth.currentUser!.tenantId).to.eq(tenantId); + }); + it('allows the user to change the email', async () => { const credential = FacebookAuthProvider.credential(oauthIdToken); const { user } = await signInWithCredential(auth, credential); @@ -206,6 +256,51 @@ describe('Integration test: headless IdP', () => { expect(user.providerData[0].providerId).to.eq('facebook.com'); }); + it('can link with multiple idps within a tenant', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const googleEmail = randomEmail(); + const facebookEmail = randomEmail(); + + const googleCredential = GoogleAuthProvider.credential( + JSON.stringify({ + sub: googleEmail, + email: googleEmail, + 'email_verified': true + }) + ); + + const facebookCredential = FacebookAuthProvider.credential( + JSON.stringify({ + sub: facebookEmail, + email: facebookEmail + }) + ); + + // Link and then test everything + const { user } = await signInWithCredential(auth, facebookCredential); + await linkWithCredential(user, googleCredential); + expect(user.email).to.eq(facebookEmail); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(2); + expect( + user.providerData.find(p => p.providerId === 'google.com')!.email + ).to.eq(googleEmail); + expect( + user.providerData.find(p => p.providerId === 'facebook.com')!.email + ).to.eq(facebookEmail); + expect(user.tenantId).to.eq(tenantId); + + // Unlink Google and check everything again + await unlink(user, ProviderId.GOOGLE); + expect(user.email).to.eq(facebookEmail); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].email).to.eq(facebookEmail); + expect(user.providerData[0].providerId).to.eq('facebook.com'); + expect(user.tenantId).to.eq(tenantId); + }); + it('IdP account takes over unverified email', async () => { const credential = GoogleAuthProvider.credential(oauthIdToken); const { user: emailUser } = await createUserWithEmailAndPassword( diff --git a/packages/auth/test/integration/flows/multi_tenant.local.test.ts b/packages/auth/test/integration/flows/multi_tenant.local.test.ts new file mode 100644 index 00000000000..38b0551087b --- /dev/null +++ b/packages/auth/test/integration/flows/multi_tenant.local.test.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + Auth, + signInAnonymously, +} from '@firebase/auth'; + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { createNewTenant } from '../../helpers/integration/emulator_rest_helpers'; +import { + cleanUpTestInstance, + getTestInstance, +} from '../../helpers/integration/helpers'; + +use(chaiAsPromised); + +describe('Integration test: multi-tenant', () => { + let auth: Auth; + let tenantA: string; + let tenantB: string; + + beforeEach(async () => { + auth = getTestInstance(/* requireEmulator */ true); + tenantA = await createNewTenant(); + tenantB = await createNewTenant(); + }); + + afterEach(async () => { + await cleanUpTestInstance(auth); + }); + + it('sets the correct tenantId on the underlying user', async () => { + auth.tenantId = tenantA; + const {user} = await signInAnonymously(auth); + expect(user.tenantId).to.eq(tenantA); + }); + + it('allows updateCurrentUser to be called when TID matches', async () => { + auth.tenantId = tenantA; + const {user} = await signInAnonymously(auth); + await expect(auth.updateCurrentUser(user)).not.to.be.rejected; + }); + + it('throws for mismatched TID', async () => { + auth.tenantId = tenantA; + const {user} = await signInAnonymously(auth); + auth.tenantId = tenantB; + await expect(auth.updateCurrentUser(user)).to.be.rejectedWith('auth/tenant-id-mismatch'); + }); + + it('allows users to be deleted', async () => { + auth.tenantId = tenantA; + const {user} = await signInAnonymously(auth); + await user.delete(); + expect(auth.currentUser).to.be.null; + }); + + // The rest of the tenantId tests are in the respective flow tests +}); \ No newline at end of file diff --git a/packages/auth/test/integration/flows/oob.local.test.ts b/packages/auth/test/integration/flows/oob.local.test.ts index e7fd54a0e8b..108819b3202 100644 --- a/packages/auth/test/integration/flows/oob.local.test.ts +++ b/packages/auth/test/integration/flows/oob.local.test.ts @@ -45,6 +45,7 @@ import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { + createNewTenant, getOobCodes, OobCodeSession } from '../../helpers/integration/emulator_rest_helpers'; @@ -76,8 +77,8 @@ describe('Integration test: oob codes', () => { await cleanUpTestInstance(auth); }); - async function code(toEmail: string): Promise { - const codes = await getOobCodes(); + async function code(toEmail: string, tenant?: string): Promise { + const codes = await getOobCodes(tenant); return codes.reverse().find(({ email }) => email === toEmail)!; } @@ -288,6 +289,30 @@ describe('Integration test: oob codes', () => { expect(user.emailVerified).to.be.true; }); + it('can be used to verify email in tenant', async () => { + // Create an unverified user + const tenantId =await createNewTenant(); + auth.tenantId = tenantId; + const { user } = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(user.emailVerified).to.be.false; + console.log(user.email); + console.log(tenantId); + expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([ + SignInMethod.EMAIL_PASSWORD + ]); + await sendEmailVerification(user); + + // Apply the email verification code + await applyActionCode(auth, (await code(email, tenantId)).oobCode); + await user.reload(); + expect(user.emailVerified).to.be.true; + expect(user.tenantId).to.eq(tenantId); + }); + it('can be used to initiate password reset', async () => { const { user: original } = await createUserWithEmailAndPassword( auth, @@ -320,6 +345,41 @@ describe('Integration test: oob codes', () => { ).to.be.rejectedWith(FirebaseError, 'auth/wrong-password'); }); + it('can be used to initiate password reset in tenant', async () => { + const tenantId = await createNewTenant(); + auth.tenantId = tenantId; + const { user: original } = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + await sendEmailVerification(original); // Can only reset verified user emails + await applyActionCode(auth, (await code(email, tenantId)).oobCode); + + // Send and confirm the password reset + await sendPasswordResetEmail(auth, email); + const oobCode = (await code(email, tenantId)).oobCode; + expect(await verifyPasswordResetCode(auth, oobCode)).to.eq(email); + await confirmPasswordReset(auth, oobCode, 'new-password'); + + // Make sure the new password works and the old one doesn't + const { user } = await signInWithEmailAndPassword( + auth, + email, + 'new-password' + ); + expect(user.uid).to.eq(original.uid); + expect(user.emailVerified).to.be.true; + expect(user.tenantId).to.eq(tenantId); + expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([ + SignInMethod.EMAIL_PASSWORD + ]); + + await expect( + signInWithEmailAndPassword(auth, email, 'password') + ).to.be.rejectedWith(FirebaseError, 'auth/wrong-password'); + }); + // Test is ignored for now as the emulator does not currently support the // verify-and-change-email operation. xit('verifyBeforeUpdateEmail waits until flow completes', async () => { diff --git a/packages/auth/test/integration/webdriver/compat/firebaseui.test.ts b/packages/auth/test/integration/webdriver/compat/firebaseui.test.ts index 4daa6f0bb5b..536a5da25ee 100644 --- a/packages/auth/test/integration/webdriver/compat/firebaseui.test.ts +++ b/packages/auth/test/integration/webdriver/compat/firebaseui.test.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; import { browserDescribe } from '../util/test_runner'; import { UiPage } from '../util/ui_page'; import { IdPPage } from '../util/idp_page'; -import { getPhoneVerificationCodes } from '../../../helpers/integration/emulator_rest_helpers'; +import { createNewTenant, getPhoneVerificationCodes } from '../../../helpers/integration/emulator_rest_helpers'; // These tests only run using the compat layer. Due to npm dependency issues, // this code needs to stay with the modular tests @@ -51,6 +51,16 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => { expect(snap.uid).to.be.a('string'); }); + it('can handle multi-tenancy', async () => { + const tenantId = await createNewTenant(); + await driver.call(CoreFunction.SET_TENANT_ID, tenantId); + const page = await startUi(); + await page.clickGuestSignIn(); + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.tenantId).to.eq(tenantId); + }); + it('allows google redirect sign in', async () => { const page = await startUi(); await page.clickGoogleSignIn(); @@ -106,6 +116,36 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => { expect(snap.providerData[0]!.providerId).to.eq('google.com'); }); + it('allows google popup sign in with multi-tenancy', async () => { + const tenantId = await createNewTenant(); + await driver.call(CoreFunction.SET_TENANT_ID, tenantId); + const page = await startUi('popup'); + await page.clickGoogleSignIn(); + const widget = new IdPPage(driver.webDriver); + await driver.selectPopupWindow(); + + // We're now on the widget page; wait for load + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.fillDisplayName('Bob Test'); + await widget.fillScreenName('bob.test'); + await widget.fillProfilePhoto('http://bob.test/bob.png'); + await widget.clickSignIn(); + + // Now we're back. Firebase UI should handle the redirect result handoff + await driver.selectMainWindow(); + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.false; + expect(snap.displayName).to.eq('Bob Test'); + expect(snap.email).to.eq('bob@bob.test'); + expect(snap.photoURL).to.eq('http://bob.test/bob.png'); + expect(snap.uid).to.be.a('string'); + expect(snap.tenantId).to.eq(tenantId); + expect(snap.providerData[0]!.providerId).to.eq('google.com'); + }); + it('allows phone sign in', async () => { const page = await startUi(); diff --git a/packages/auth/test/integration/webdriver/persistence.test.ts b/packages/auth/test/integration/webdriver/persistence.test.ts index 3ec3f553281..7d052618b1e 100644 --- a/packages/auth/test/integration/webdriver/persistence.test.ts +++ b/packages/auth/test/integration/webdriver/persistence.test.ts @@ -18,7 +18,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { UserCredential } from '@firebase/auth'; import { expect } from 'chai'; -import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers'; +import { createAnonAccount, createNewTenant } from '../../helpers/integration/emulator_rest_helpers'; import { API_KEY } from '../../helpers/integration/settings'; import { START_FUNCTION } from './util/auth_driver'; import { @@ -429,6 +429,8 @@ browserDescribe('WebDriver persistence test', (driver, browser) => { context('persistence sync across windows and tabs', () => { it('sync current user across windows with indexedDB', async () => { + const tenantId = await createNewTenant(); + await driver.call(CoreFunction.SET_TENANT_ID, tenantId); const cred: UserCredential = await driver.call( AnonFunction.SIGN_IN_ANONYMOUSLY ); @@ -438,9 +440,11 @@ browserDescribe('WebDriver persistence test', (driver, browser) => { await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION)); await driver.injectConfigAndInitAuth(); await driver.waitForAuthInit(); + await driver.call(CoreFunction.SET_TENANT_ID, tenantId); const userInPopup = await driver.getUserSnapshot(); expect(userInPopup).not.to.be.null; expect(userInPopup.uid).to.equal(uid); + expect(userInPopup.tenantId).to.equal(tenantId); await driver.call(CoreFunction.SIGN_OUT); expect(await driver.getUserSnapshot()).to.be.null; @@ -495,4 +499,30 @@ browserDescribe('WebDriver persistence test', (driver, browser) => { expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 }); }); }); + + it('tenant user persists across reloads', async () => { + const tenantId = await createNewTenant(); + await driver.call(CoreFunction.SET_TENANT_ID, tenantId); + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + expect(await driver.getUserSnapshot()).to.eql(cred.user); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect( + await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP) + ).to.eql({}); + + const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid }); + + // Persistence should survive a refresh: + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.contain({ uid, tenantId }); + }); }); diff --git a/packages/auth/test/integration/webdriver/static/core.js b/packages/auth/test/integration/webdriver/static/core.js index c4a748ba1f5..af1bef624ef 100644 --- a/packages/auth/test/integration/webdriver/static/core.js +++ b/packages/auth/test/integration/webdriver/static/core.js @@ -47,3 +47,7 @@ export async function authSnap() { export function signOut() { return auth.signOut(); } + +export async function setTenantId(tid) { + auth.tenantId = tid; +} \ No newline at end of file diff --git a/packages/auth/test/integration/webdriver/util/functions.ts b/packages/auth/test/integration/webdriver/util/functions.ts index 6350f59e9f9..816b7f4643a 100644 --- a/packages/auth/test/integration/webdriver/util/functions.ts +++ b/packages/auth/test/integration/webdriver/util/functions.ts @@ -59,7 +59,8 @@ export enum CoreFunction { AUTH_SNAPSHOT = 'core.authSnap', SIGN_OUT = 'core.signOut', AWAIT_LEGACY_AUTH_INIT = 'core.legacyAuthInit', - LEGACY_USER_SNAPSHOT = 'core.legacyUserSnap' + LEGACY_USER_SNAPSHOT = 'core.legacyUserSnap', + SET_TENANT_ID = 'core.setTenantId', } /** Available persistence functions within the browser. See static/persistence.js */