mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2020 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 {
|
|
ActionCodeSettings,
|
|
applyActionCode,
|
|
Auth,
|
|
confirmPasswordReset,
|
|
createUserWithEmailAndPassword,
|
|
deleteUser,
|
|
EmailAuthProvider,
|
|
fetchSignInMethodsForEmail,
|
|
linkWithCredential,
|
|
OperationType,
|
|
reauthenticateWithCredential,
|
|
sendEmailVerification,
|
|
sendPasswordResetEmail,
|
|
sendSignInLinkToEmail,
|
|
signInAnonymously,
|
|
SignInMethod,
|
|
signInWithCredential,
|
|
signInWithCustomToken,
|
|
signInWithEmailAndPassword,
|
|
signInWithEmailLink,
|
|
updatePassword,
|
|
verifyBeforeUpdateEmail,
|
|
verifyPasswordResetCode
|
|
} from '@firebase/auth';
|
|
import { FirebaseError } from '@firebase/util';
|
|
import { expect, use } from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import {
|
|
getOobCodes,
|
|
OobCodeSession
|
|
} from '../../helpers/integration/emulator_rest_helpers';
|
|
import {
|
|
cleanUpTestInstance,
|
|
getTestInstance,
|
|
randomEmail
|
|
} from '../../helpers/integration/helpers';
|
|
import { generateMiddlewareTests } from './middleware_test_generator';
|
|
|
|
use(chaiAsPromised);
|
|
|
|
declare const xit: typeof it;
|
|
|
|
const BASE_SETTINGS: ActionCodeSettings = {
|
|
url: 'http://localhost/action_code_return',
|
|
handleCodeInApp: true
|
|
};
|
|
|
|
describe('Integration test: oob codes', () => {
|
|
let auth: Auth;
|
|
let email: string;
|
|
|
|
beforeEach(() => {
|
|
auth = getTestInstance(/* requireEmulator */ true);
|
|
email = randomEmail();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanUpTestInstance(auth);
|
|
});
|
|
|
|
async function code(toEmail: string): Promise<OobCodeSession> {
|
|
const codes = await getOobCodes();
|
|
return codes.reverse().find(({ email }) => email === toEmail)!;
|
|
}
|
|
|
|
context('flows beginning with sendSignInLinkToEmail', () => {
|
|
let oobSession: OobCodeSession;
|
|
|
|
beforeEach(async () => {
|
|
oobSession = await sendEmailLink();
|
|
});
|
|
|
|
async function sendEmailLink(toEmail = email): Promise<OobCodeSession> {
|
|
await sendSignInLinkToEmail(auth, toEmail, BASE_SETTINGS);
|
|
|
|
// An email has been sent to the user. Normally you'd detect this state
|
|
// when the app redirects back. We will ask the emulator for the results
|
|
// and force the state instead.
|
|
return code(toEmail);
|
|
}
|
|
|
|
it('allows user to sign in', async () => {
|
|
const { user, operationType } = await signInWithEmailLink(
|
|
auth,
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
|
|
expect(operationType).to.eq(OperationType.SIGN_IN);
|
|
expect(user).to.eq(auth.currentUser);
|
|
expect(user.uid).to.be.a('string');
|
|
expect(user.email).to.eq(email);
|
|
expect(user.emailVerified).to.be.true;
|
|
expect(user.isAnonymous).to.be.false;
|
|
});
|
|
|
|
it('sign in works with an email credential', async () => {
|
|
const cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user, operationType } = await signInWithCredential(auth, cred);
|
|
|
|
expect(operationType).to.eq(OperationType.SIGN_IN);
|
|
expect(user).to.eq(auth.currentUser);
|
|
expect(user.uid).to.be.a('string');
|
|
expect(user.email).to.eq(email);
|
|
expect(user.emailVerified).to.be.true;
|
|
expect(user.isAnonymous).to.be.false;
|
|
});
|
|
|
|
it('reauthenticate works with email credential', async () => {
|
|
let cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user: oldUser } = await signInWithCredential(auth, cred);
|
|
|
|
const reauthSession = await sendEmailLink();
|
|
cred = EmailAuthProvider.credentialWithLink(email, reauthSession.oobLink);
|
|
const { user: newUser, operationType } =
|
|
await reauthenticateWithCredential(oldUser, cred);
|
|
|
|
expect(newUser.uid).to.eq(oldUser.uid);
|
|
expect(operationType).to.eq(OperationType.REAUTHENTICATE);
|
|
expect(auth.currentUser).to.eq(newUser);
|
|
});
|
|
|
|
it('reauthenticate throws with different email', async () => {
|
|
let cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user: oldUser } = await signInWithCredential(auth, cred);
|
|
|
|
const newEmail = randomEmail();
|
|
const reauthSession = await sendEmailLink(newEmail);
|
|
cred = EmailAuthProvider.credentialWithLink(
|
|
newEmail,
|
|
reauthSession.oobLink
|
|
);
|
|
await expect(
|
|
reauthenticateWithCredential(oldUser, cred)
|
|
).to.be.rejectedWith(FirebaseError, 'auth/user-mismatch');
|
|
expect(auth.currentUser).to.eq(oldUser);
|
|
});
|
|
|
|
it('reauthenticate throws if user is deleted', async () => {
|
|
let cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user: oldUser } = await signInWithCredential(auth, cred);
|
|
|
|
await deleteUser(oldUser);
|
|
const reauthSession = await sendEmailLink(email);
|
|
cred = EmailAuthProvider.credentialWithLink(email, reauthSession.oobLink);
|
|
await expect(
|
|
reauthenticateWithCredential(oldUser, cred)
|
|
).to.be.rejectedWith(FirebaseError, 'auth/user-mismatch');
|
|
expect(auth.currentUser).to.be.null;
|
|
});
|
|
|
|
it('other accounts can be linked', async () => {
|
|
const cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user: original } = await signInAnonymously(auth);
|
|
|
|
expect(original.isAnonymous).to.be.true;
|
|
const { user: linked, operationType } = await linkWithCredential(
|
|
original,
|
|
cred
|
|
);
|
|
|
|
expect(operationType).to.eq(OperationType.LINK);
|
|
expect(linked.uid).to.eq(original.uid);
|
|
expect(linked.isAnonymous).to.be.false;
|
|
expect(auth.currentUser).to.eq(linked);
|
|
expect(linked.email).to.eq(email);
|
|
expect(linked.emailVerified).to.be.true;
|
|
});
|
|
|
|
it('can be linked to a custom token', async () => {
|
|
const { user: original } = await signInWithCustomToken(
|
|
auth,
|
|
JSON.stringify({
|
|
uid: 'custom-uid'
|
|
})
|
|
);
|
|
|
|
const cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user: linked } = await linkWithCredential(original, cred);
|
|
|
|
expect(linked.uid).to.eq(original.uid);
|
|
expect(auth.currentUser).to.eq(linked);
|
|
expect(linked.email).to.eq(email);
|
|
expect(linked.emailVerified).to.be.true;
|
|
});
|
|
|
|
it('cannot link if original account is deleted', async () => {
|
|
const cred = EmailAuthProvider.credentialWithLink(
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
const { user } = await signInAnonymously(auth);
|
|
|
|
expect(user.isAnonymous).to.be.true;
|
|
await deleteUser(user);
|
|
await expect(linkWithCredential(user, cred)).to.be.rejectedWith(
|
|
FirebaseError,
|
|
'auth/user-token-expired'
|
|
);
|
|
});
|
|
|
|
it('code can only be used once', async () => {
|
|
const link = oobSession.oobLink;
|
|
await signInWithEmailLink(auth, email, link);
|
|
await expect(signInWithEmailLink(auth, email, link)).to.be.rejectedWith(
|
|
FirebaseError,
|
|
'auth/invalid-action-code'
|
|
);
|
|
});
|
|
|
|
it('fetchSignInMethodsForEmail returns the correct values', async () => {
|
|
const { user } = await signInWithEmailLink(
|
|
auth,
|
|
email,
|
|
oobSession.oobLink
|
|
);
|
|
expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([
|
|
SignInMethod.EMAIL_LINK
|
|
]);
|
|
|
|
await updatePassword(user, 'password');
|
|
const updatedMethods = await fetchSignInMethodsForEmail(auth, email);
|
|
expect(updatedMethods).to.have.length(2);
|
|
expect(updatedMethods).to.include(SignInMethod.EMAIL_LINK);
|
|
expect(updatedMethods).to.include(SignInMethod.EMAIL_PASSWORD);
|
|
});
|
|
|
|
it('throws an error if the wrong code is provided', async () => {
|
|
const otherSession = await sendEmailLink(randomEmail());
|
|
await expect(
|
|
signInWithEmailLink(auth, email, otherSession.oobLink)
|
|
).to.be.rejectedWith(FirebaseError, 'auth/invalid-email');
|
|
});
|
|
|
|
generateMiddlewareTests(
|
|
() => auth,
|
|
() => {
|
|
return signInWithEmailLink(auth, email, oobSession.oobLink);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('can be used to verify email', async () => {
|
|
// Create an unverified user
|
|
const { user } = await createUserWithEmailAndPassword(
|
|
auth,
|
|
email,
|
|
'password'
|
|
);
|
|
expect(user.emailVerified).to.be.false;
|
|
expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([
|
|
SignInMethod.EMAIL_PASSWORD
|
|
]);
|
|
await sendEmailVerification(user);
|
|
|
|
// Apply the email verification code
|
|
await applyActionCode(auth, (await code(email)).oobCode);
|
|
await user.reload();
|
|
expect(user.emailVerified).to.be.true;
|
|
});
|
|
|
|
it('can be used to initiate password reset', async () => {
|
|
const { user: original } = await createUserWithEmailAndPassword(
|
|
auth,
|
|
email,
|
|
'password'
|
|
);
|
|
await sendEmailVerification(original); // Can only reset verified user emails
|
|
await applyActionCode(auth, (await code(email)).oobCode);
|
|
|
|
// Send and confirm the password reset
|
|
await sendPasswordResetEmail(auth, email);
|
|
const oobCode = (await code(email)).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(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 () => {
|
|
const updatedEmail = randomEmail();
|
|
|
|
// Create an initial user with the basic email
|
|
await sendSignInLinkToEmail(auth, email, BASE_SETTINGS);
|
|
const { user } = await signInWithEmailLink(
|
|
auth,
|
|
email,
|
|
(
|
|
await code(email)
|
|
).oobLink
|
|
);
|
|
await verifyBeforeUpdateEmail(user, updatedEmail, BASE_SETTINGS);
|
|
expect(user.email).to.eq(email);
|
|
|
|
// Finish the update email flow
|
|
await applyActionCode(auth, (await code(updatedEmail)).oobCode);
|
|
await user.reload();
|
|
expect(user.emailVerified).to.be.true;
|
|
expect(user.email).to.eq(updatedEmail);
|
|
expect(auth.currentUser).to.eq(user);
|
|
|
|
// Old email doesn't work but new one does
|
|
await expect(
|
|
signInWithEmailAndPassword(auth, email, 'password')
|
|
).to.be.rejectedWith(FirebaseError, 'auth/alskdjf');
|
|
const { user: newSignIn } = await signInWithEmailAndPassword(
|
|
auth,
|
|
updatedEmail,
|
|
'password'
|
|
);
|
|
expect(newSignIn.uid).to.eq(user.uid);
|
|
});
|
|
});
|