voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
231 lines (209 loc) • 7.44 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 { stub } from 'sinon';
import {
OperationType,
ProviderId,
SignInMethod
} from '../../model/public_types';
import { FirebaseError } from '@firebase/util';
import { mockEndpoint } from '../../../test/helpers/api/helper';
import { makeJWT } from '../../../test/helpers/jwt';
import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth';
import { MockAuthCredential } from '../../../test/helpers/mock_auth_credential';
import * as mockFetch from '../../../test/helpers/mock_fetch';
import { Endpoint } from '../../api';
import { APIUserInfo } from '../../api/account_management/account';
import { IdTokenMfaResponse } from '../../api/authentication/mfa';
import { MultiFactorError } from '../../mfa/mfa_error';
import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token';
import { UserInternal, UserCredentialInternal } from '../../model/user';
import { AuthCredential } from '../credentials';
import { AuthErrorCode } from '../errors';
import {
linkWithCredential,
reauthenticateWithCredential,
signInWithCredential,
_signInWithCredential
} from './credential';
import { _createError } from '../util/assert';
use(chaiAsPromised);
describe('core/strategies/credential', () => {
const serverUser: APIUserInfo = {
localId: 'local-id',
displayName: 'display-name',
photoUrl: 'photo-url',
email: 'email',
emailVerified: true,
phoneNumber: 'phone-number',
createdAt: 123,
lastLoginAt: 456
};
const idTokenResponse: IdTokenResponse = {
idToken: 'my-id-token',
refreshToken: 'my-refresh-token',
expiresIn: '1234',
localId: serverUser.localId!,
kind: IdTokenResponseKind.CreateAuthUri
};
let authCredential: AuthCredential;
let auth: TestAuth;
let getAccountInfoEndpoint: mockFetch.Route;
let user: UserInternal;
beforeEach(async () => {
auth = await testAuth();
mockFetch.setUp();
authCredential = new MockAuthCredential(
ProviderId.FIREBASE,
SignInMethod.EMAIL_LINK
);
getAccountInfoEndpoint = mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {
users: [serverUser]
});
user = testUser(auth, 'uid', undefined, true);
});
afterEach(mockFetch.tearDown);
describe('signInWithCredential', () => {
it('should return a valid user credential', async () => {
stub(authCredential, '_getIdTokenResponse').returns(
Promise.resolve(idTokenResponse)
);
const { user, operationType, ...rest } = await signInWithCredential(
auth,
authCredential
);
expect((rest as UserCredentialInternal)._tokenResponse).to.eq(
idTokenResponse
);
expect(user.uid).to.eq('local-id');
expect(user.displayName).to.eq('display-name');
expect(operationType).to.eq(OperationType.SIGN_IN);
});
it('should update the current user', async () => {
stub(authCredential, '_getIdTokenResponse').returns(
Promise.resolve(idTokenResponse)
);
const { user } = await signInWithCredential(auth, authCredential);
expect(auth.currentUser).to.eq(user);
});
it('does not update the current user if bypass is true', async () => {
stub(authCredential, '_getIdTokenResponse').returns(
Promise.resolve(idTokenResponse)
);
const { user } = await _signInWithCredential(auth, authCredential, true);
expect(auth.currentUser).to.be.null;
expect(user).not.to.be.null;
});
it('should handle MFA', async () => {
const serverResponse: IdTokenMfaResponse = {
localId: 'uid',
mfaInfo: [
{
mfaEnrollmentId: 'mfa-enrollment-id',
enrolledAt: Date.now(),
phoneInfo: 'phone-info'
}
],
mfaPendingCredential: 'mfa-pending-credential'
};
stub(authCredential, '_getIdTokenResponse').returns(
Promise.reject(
_createError(auth, AuthErrorCode.MFA_REQUIRED, {
serverResponse
})
)
);
const error = await expect(
signInWithCredential(auth, authCredential)
).to.be.rejectedWith(MultiFactorError);
expect(error.operationType).to.eq(OperationType.SIGN_IN);
expect(error.serverResponse).to.eql(serverResponse);
expect(error.user).to.be.undefined;
});
});
describe('reauthenticateWithCredential', () => {
it('should throw an error if the uid is mismatched', async () => {
stub(authCredential, '_getReauthenticationResolver').returns(
Promise.resolve({
...idTokenResponse,
idToken: makeJWT({ sub: 'not-my-uid' })
})
);
await expect(
reauthenticateWithCredential(user, authCredential)
).to.be.rejectedWith(
FirebaseError,
'Firebase: The supplied credentials do not correspond to the previously signed in user. (auth/user-mismatch).'
);
});
it('should return the expected user credential', async () => {
stub(authCredential, '_getReauthenticationResolver').returns(
Promise.resolve({
...idTokenResponse,
idToken: makeJWT({ sub: 'uid' })
})
);
const {
user: newUser,
operationType,
...rest
} = await reauthenticateWithCredential(user, authCredential);
expect(operationType).to.eq(OperationType.REAUTHENTICATE);
expect(newUser).to.eq(user);
expect((rest as UserCredentialInternal)._tokenResponse).to.eql({
...idTokenResponse,
idToken: makeJWT({ sub: 'uid' })
});
});
});
describe('linkWithCredential', () => {
it('should throw an error if the provider is already linked', async () => {
stub(authCredential, '_linkToIdToken').returns(
Promise.resolve(idTokenResponse)
);
getAccountInfoEndpoint.response = {
users: [
{
...serverUser,
providerUserInfo: [{ providerId: ProviderId.FIREBASE }]
}
]
};
await expect(linkWithCredential(user, authCredential)).to.be.rejectedWith(
FirebaseError,
'Firebase: User can only be linked to one identity for the given provider. (auth/provider-already-linked).'
);
});
it('should return a valid user credential', async () => {
stub(authCredential, '_linkToIdToken').returns(
Promise.resolve(idTokenResponse)
);
const {
user: newUser,
operationType,
...rest
} = await linkWithCredential(user, authCredential);
expect(operationType).to.eq(OperationType.LINK);
expect(newUser).to.eq(user);
expect((rest as UserCredentialInternal)._tokenResponse).to.eq(
idTokenResponse
);
});
});
});