voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
237 lines (214 loc) • 7.57 kB
text/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.
*/
import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import { FactorId, OperationType, ProviderId } from '../model/public_types';
import { FirebaseError } from '@firebase/util';
import { mockEndpoint } from '../../test/helpers/api/helper';
import { testAuth, testUser, TestAuth } from '../../test/helpers/mock_auth';
import * as mockFetch from '../../test/helpers/mock_fetch';
import { Endpoint } from '../api';
import { APIUserInfo } from '../api/account_management/account';
import { PhoneAuthCredential } from '../core/credentials/phone';
import { AuthErrorCode } from '../core/errors';
import { UserInternal, UserCredentialInternal } from '../model/user';
import { MultiFactorAssertionImpl } from './mfa_assertion';
import { PhoneMultiFactorAssertionImpl } from '../platform_browser/mfa/assertions/phone';
import { MultiFactorError } from './mfa_error';
import {
getMultiFactorResolver,
MultiFactorResolverImpl
} from './mfa_resolver';
import { _createError } from '../core/util/assert';
import { makeJWT } from '../../test/helpers/jwt';
use(chaiAsPromised);
describe('core/mfa/mfa_resolver/MultiFactorResolver', () => {
const finalIdToken = makeJWT({ 'exp': '3600', 'iat': '1200' });
let auth: TestAuth;
let underlyingError: FirebaseError;
let error: MultiFactorError;
let clock: sinon.SinonFakeTimers;
beforeEach(async () => {
clock = sinon.useFakeTimers();
auth = await testAuth();
auth.tenantId = 'tenant-id';
underlyingError = _createError(auth, AuthErrorCode.MFA_REQUIRED, {
serverResponse: {
localId: 'local-id',
mfaPendingCredential: 'mfa-pending-credential',
mfaInfo: [
{
mfaEnrollmentId: 'mfa-enrollment-id',
enrolledAt: Date.now(),
phoneInfo: '+*******1234',
displayName: ''
}
]
}
});
});
afterEach(() => {
sinon.restore();
});
describe('MultiFactorResolver', () => {
let assertion: MultiFactorAssertionImpl;
let secondFactorCredential: PhoneAuthCredential;
let resolver: MultiFactorResolverImpl;
beforeEach(() => {
mockFetch.setUp();
secondFactorCredential = PhoneAuthCredential._fromVerification(
'verification-id',
'verification-code'
);
assertion = PhoneMultiFactorAssertionImpl._fromCredential(
secondFactorCredential
);
});
afterEach(mockFetch.tearDown);
describe('resolveSignIn', () => {
let mock: mockFetch.Route;
const serverUser: APIUserInfo = {
localId: 'local-id',
displayName: 'display-name',
photoUrl: 'photo-url',
email: 'email',
emailVerified: true,
phoneNumber: 'phone-number',
tenantId: 'tenant-id',
createdAt: 123,
lastLoginAt: 456,
mfaInfo: [
{
mfaEnrollmentId: 'mfa-id',
enrolledAt: Date.now(),
phoneInfo: 'phone-number'
}
]
};
beforeEach(() => {
mock = mockEndpoint(Endpoint.FINALIZE_PHONE_MFA_SIGN_IN, {
idToken: finalIdToken,
refreshToken: 'final-refresh-token'
});
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {
users: [serverUser]
});
});
context('sign in', () => {
beforeEach(() => {
error = MultiFactorError._fromErrorAndOperation(
auth,
underlyingError,
OperationType.SIGN_IN
);
resolver = MultiFactorResolverImpl._fromError(auth, error);
});
it('finalizes the sign in flow', async () => {
const userCredential = (await resolver.resolveSignIn(
assertion
)) as UserCredentialInternal;
expect(userCredential.user.uid).to.eq('local-id');
expect(await userCredential.user.getIdToken()).to.eq(finalIdToken);
expect(userCredential.user.stsTokenManager.expirationTime).to.eq(
clock.now + 2400 * 1000
);
expect(userCredential._tokenResponse).to.eql({
localId: 'local-id',
idToken: finalIdToken,
refreshToken: 'final-refresh-token'
});
expect(mock.calls[0].request).to.eql({
tenantId: auth.tenantId,
mfaPendingCredential: 'mfa-pending-credential',
phoneVerificationInfo: {
sessionInfo: 'verification-id',
code: 'verification-code'
}
});
});
});
context('reauthentication', () => {
let user: UserInternal;
beforeEach(() => {
user = testUser(auth, 'local-id', undefined, true);
error = MultiFactorError._fromErrorAndOperation(
auth,
underlyingError,
OperationType.REAUTHENTICATE,
user
);
resolver = MultiFactorResolverImpl._fromError(auth, error);
});
it('finalizes the reauth flow', async () => {
const userCredential = (await resolver.resolveSignIn(
assertion
)) as UserCredentialInternal;
expect(userCredential.user).to.eq(user);
expect(await userCredential.user.getIdToken()).to.eq(finalIdToken);
expect(userCredential.user.stsTokenManager.expirationTime).to.eq(
clock.now + 2400 * 1000
);
expect(userCredential._tokenResponse).to.eql({
localId: 'local-id',
idToken: finalIdToken,
refreshToken: 'final-refresh-token'
});
expect(mock.calls[0].request).to.eql({
tenantId: auth.tenantId,
mfaPendingCredential: 'mfa-pending-credential',
phoneVerificationInfo: {
sessionInfo: 'verification-id',
code: 'verification-code'
}
});
});
});
});
});
describe('getMultiFactorResolver', () => {
context('sign in', () => {
beforeEach(() => {
error = MultiFactorError._fromErrorAndOperation(
auth,
underlyingError,
OperationType.SIGN_IN
);
});
it('can be used to obtain a resolver', () => {
const resolver = getMultiFactorResolver(auth, error);
expect(resolver.hints[0].factorId).to.eq(ProviderId.PHONE);
});
});
context('reauthentication', () => {
let user: UserInternal;
beforeEach(() => {
user = testUser(auth, 'local-id', undefined, true);
error = MultiFactorError._fromErrorAndOperation(
auth,
underlyingError,
OperationType.REAUTHENTICATE,
user
);
});
it('can be used to obtain a resolver', () => {
const resolver = getMultiFactorResolver(auth, error);
expect(resolver.hints[0].factorId).to.eq(FactorId.PHONE);
});
});
});
});