Skip to content

[Auth] Add some headless IdP tests to round out our IdP WebDriver tests #4609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 11, 2021
Merged
Changes from all 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
288 changes: 288 additions & 0 deletions packages-exp/auth-exp/test/integration/flows/idp.local.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
/**
* @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 {
Auth,
createUserWithEmailAndPassword,
FacebookAuthProvider,
getAdditionalUserInfo,
GithubAuthProvider,
GoogleAuthProvider,
linkWithCredential,
OperationType,
ProviderId,
signInWithCredential,
signInWithEmailAndPassword,
unlink,
updateEmail,
updatePassword,
updateProfile
// eslint-disable-next-line import/no-extraneous-dependencies
} from '@firebase/auth-exp';
import { FirebaseError } from '@firebase/util';
import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import {
cleanUpTestInstance,
getTestInstance,
randomEmail
} from '../../helpers/integration/helpers';

use(chaiAsPromised);

// These tests handle OAuth sign in, but they're totally headless (they don't
// use the popup/redirect flows). For testing of the popup/redirect flows, look
// under the test/integration/webdriver directory.

describe('Integration test: headless IdP', () => {
let auth: Auth;
let oauthIdToken: string;
let email: string;

beforeEach(() => {
auth = getTestInstance(/* requireEmulator */ true);
email = randomEmail();
oauthIdToken = JSON.stringify({
email,
'email_verified': true,
sub: `oauthidp--${email}--oauthidp`
});
});

afterEach(async () => {
await cleanUpTestInstance(auth);
});

it('signs in with an OAuth token', async () => {
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);

// 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);

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');
});

it('allows the user to change the email', async () => {
const credential = FacebookAuthProvider.credential(oauthIdToken);
const { user } = await signInWithCredential(auth, credential);

expect(user.email).to.eq(email);
expect(user.emailVerified).to.be.true;

const newEmail = randomEmail();
await updateEmail(user, newEmail);

// Check everything first
expect(user.email).to.eq(newEmail);
expect(user.emailVerified).to.be.false;

await auth.signOut();

// Sign in again
await signInWithCredential(auth, credential);
expect(auth.currentUser!.email).to.eq(newEmail);
});

it('allows the user to set a password', async () => {
const credential = GoogleAuthProvider.credential(oauthIdToken);
const { user } = await signInWithCredential(auth, credential);

expect(user.providerData.length).to.eq(1);
expect(user.providerData[0].providerId).to.eq('google.com');

// Set the password and check provider data
await updatePassword(user, 'password');
expect(user.providerData.length).to.eq(2);
expect(user.providerData.map(p => p.providerId)).to.contain.members([
'google.com',
'password'
]);

// Sign out and sign in again
await auth.signOut();
await signInWithEmailAndPassword(auth, email, 'password');
expect(auth.currentUser!.providerData.length).to.eq(2);
expect(
auth.currentUser!.providerData.map(p => p.providerId)
).to.contain.members(['google.com', 'password']);

// Update email, then sign out/sign in again
const newEmail = randomEmail();
await updateEmail(auth.currentUser!, newEmail);
await auth.signOut();
await signInWithEmailAndPassword(auth, newEmail, 'password');
expect(auth.currentUser!.providerData.length).to.eq(2);
expect(
auth.currentUser!.providerData.map(p => p.providerId)
).to.contain.members(['google.com', 'password']);
});

it('can link with multiple idps', async () => {
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);

// 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');
});

it('IdP account takes over unverified email', async () => {
const credential = GoogleAuthProvider.credential(oauthIdToken);
const { user: emailUser } = await createUserWithEmailAndPassword(
auth,
email,
'password'
);

// Check early state
expect(emailUser.emailVerified).to.be.false;

// Sign in with the credential and expect auto-linking
const { user: googleUser } = await signInWithCredential(auth, credential);
expect(googleUser.uid).to.eq(emailUser.uid);
expect(googleUser.emailVerified).to.be.true;
expect(auth.currentUser).to.eq(googleUser);
console.log(googleUser.providerData);
expect(googleUser.providerData.length).to.eq(1);
expect(auth.currentUser!.providerData[0].providerId).to.eq('google.com');

// Signing in with password no longer works
await expect(
signInWithEmailAndPassword(auth, email, 'password')
).to.be.rejectedWith(FirebaseError, 'auth/wrong-password');
});

it('IdP accounts automatically link with verified emails', async () => {
const googleCredential = GoogleAuthProvider.credential(
JSON.stringify({
sub: email,
email,
'email_verified': true
})
);

const githubCredential = GithubAuthProvider.credential(
JSON.stringify({
sub: email,
email,
'email_verified': true
})
);

// First sign in with Google
const { user: initialUser } = await signInWithCredential(
auth,
googleCredential
);
expect(initialUser.providerData.length).to.eq(1);
expect(initialUser.providerData[0].providerId).to.eq('google.com');

await auth.signOut();

// Now with GitHub
const { user: githubUser } = await signInWithCredential(
auth,
githubCredential
);
expect(githubUser.uid).to.eq(initialUser.uid);
expect(githubUser.providerData.length).to.eq(2);
expect(githubUser.providerData.map(p => p.providerId)).to.have.members([
'google.com',
'github.com'
]);

await auth.signOut();

// Sign in once again with the initial credential
const { user: googleUser } = await signInWithCredential(
auth,
googleCredential
);
expect(googleUser.uid).to.eq(initialUser.uid);
expect(googleUser.providerData.length).to.eq(2);
expect(googleUser.providerData.map(p => p.providerId)).to.have.members([
'google.com',
'github.com'
]);
});
});