veritatisdoloremque
Version:
Monorepo for the Firebase JavaScript SDK
634 lines (586 loc) • 20.8 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 * as sinonChai from 'sinon-chai';
import {
OperationType,
PopupRedirectResolver,
ProviderId
} from '../../model/public_types';
import { FirebaseError } from '@firebase/util';
import { delay } from '../../../test/helpers/delay';
import { BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event';
import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth';
import { makeMockPopupRedirectResolver } from '../../../test/helpers/mock_popup_redirect_resolver';
import { stubTimeouts, TimerMap } from '../../../test/helpers/timeout_stub';
import { AuthEvent, AuthEventType } from '../../model/popup_redirect';
import { UserInternal } from '../../model/user';
import { AuthEventManager } from '../../core/auth/auth_event_manager';
import { AuthErrorCode } from '../../core/errors';
import { OAuthProvider } from '../../core/providers/oauth';
import { UserCredentialImpl } from '../../core/user/user_credential_impl';
import * as eid from '../../core/util/event_id';
import { AuthPopup } from '../util/popup';
import * as idpTasks from '../../core/strategies/idp';
import {
_Timeout,
_POLL_WINDOW_CLOSE_TIMEOUT,
linkWithPopup,
reauthenticateWithPopup,
signInWithPopup
} from './popup';
import { _getInstance } from '../../../internal';
import { _createError } from '../../core/util/assert';
use(sinonChai);
use(chaiAsPromised);
const MATCHING_EVENT_ID = 'matching-event-id';
const OTHER_EVENT_ID = 'wrong-id';
describe('platform_browser/strategies/popup', () => {
let resolver: PopupRedirectResolver;
let provider: OAuthProvider;
let eventManager: AuthEventManager;
let authPopup: AuthPopup;
let underlyingWindow: { closed: boolean };
let auth: TestAuth;
let idpStubs: sinon.SinonStubbedInstance<typeof idpTasks>;
let pendingTimeouts: TimerMap;
beforeEach(async () => {
auth = await testAuth();
eventManager = new AuthEventManager(auth);
underlyingWindow = { closed: false };
authPopup = new AuthPopup(underlyingWindow as Window);
provider = new OAuthProvider(ProviderId.GOOGLE);
resolver = makeMockPopupRedirectResolver(eventManager, authPopup);
idpStubs = sinon.stub(idpTasks);
sinon.stub(eid, '_generateEventId').returns(MATCHING_EVENT_ID);
pendingTimeouts = stubTimeouts();
sinon.stub(window, 'clearTimeout');
});
afterEach(() => {
sinon.restore();
});
function iframeEvent(event: Partial<AuthEvent>): void {
// Push the dispatch out of the synchronous flow
delay(() => {
eventManager.onEvent({
...BASE_AUTH_EVENT,
eventId: MATCHING_EVENT_ID,
...event
});
});
}
context('signInWithPopup', () => {
it('completes the full flow', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider, resolver);
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('completes the full flow with default resolver', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
auth._popupRedirectResolver = _getInstance(resolver);
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider);
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('errors if resolver not provided and not on auth', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
await expect(signInWithPopup(auth, provider)).to.be.rejectedWith(
FirebaseError,
'auth/argument-error'
);
});
it('ignores events for another event id', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider, resolver);
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP,
eventId: OTHER_EVENT_ID,
error: {
code: 'auth/internal-error',
message: '',
name: ''
}
});
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP,
eventId: MATCHING_EVENT_ID
});
expect(await promise).to.eq(cred);
});
it('does not call idp tasks if event is error', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider, resolver);
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP,
eventId: MATCHING_EVENT_ID,
error: {
code: 'auth/invalid-app-credential',
message: '',
name: ''
}
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-credential'
);
expect(idpStubs._signIn).not.to.have.been.called;
});
it('does not error if the poll timeout trips', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
});
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('does error if the poll timeout and event timeout trip', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const promise = signInWithPopup(auth, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
pendingTimeouts[_Timeout.AUTH_EVENT]();
});
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/popup-closed-by-user'
);
});
it('errors if webstorage support comes back negative', async () => {
resolver = makeMockPopupRedirectResolver(eventManager, authPopup, false);
await expect(
signInWithPopup(auth, provider, resolver)
).to.be.rejectedWith(FirebaseError, 'auth/web-storage-unsupported');
});
it('passes any errors from idp task', async () => {
idpStubs._signIn.returns(
Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID))
);
const promise = signInWithPopup(auth, provider, resolver);
iframeEvent({
eventId: MATCHING_EVENT_ID,
type: AuthEventType.SIGN_IN_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-id'
);
});
it('cancels the task if called consecutively', async () => {
const cred = new UserCredentialImpl({
user: testUser(auth, 'uid'),
providerId: ProviderId.GOOGLE,
operationType: OperationType.SIGN_IN
});
idpStubs._signIn.returns(Promise.resolve(cred));
const firstPromise = signInWithPopup(auth, provider, resolver);
const secondPromise = signInWithPopup(auth, provider, resolver);
iframeEvent({
type: AuthEventType.SIGN_IN_VIA_POPUP
});
await expect(firstPromise).to.be.rejectedWith(
FirebaseError,
'auth/cancelled-popup-request'
);
expect(await secondPromise).to.eq(cred);
});
});
context('linkWithPopup', () => {
let user: UserInternal;
beforeEach(() => {
user = testUser(auth, 'uid');
});
it('completes the full flow', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('completes the full flow with default resolver', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
user.auth._popupRedirectResolver = _getInstance(resolver);
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider);
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('errors if resolver not provided and not on auth', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
await expect(linkWithPopup(user, provider)).to.be.rejectedWith(
FirebaseError,
'auth/argument-error'
);
});
it('ignores events for another event id', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP,
eventId: OTHER_EVENT_ID,
error: {
code: 'auth/internal-error',
message: '',
name: ''
}
});
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP,
eventId: MATCHING_EVENT_ID
});
expect(await promise).to.eq(cred);
});
it('does not call idp tasks if event is error', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP,
eventId: MATCHING_EVENT_ID,
error: {
code: 'auth/invalid-app-credential',
message: '',
name: ''
}
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-credential'
);
expect(idpStubs._link).not.to.have.been.called;
});
it('does not error if the poll timeout trips', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
});
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('does error if the poll timeout and event timeout trip', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const promise = linkWithPopup(user, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
pendingTimeouts[_Timeout.AUTH_EVENT]();
});
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/popup-closed-by-user'
);
});
it('errors if webstorage support comes back negative', async () => {
resolver = makeMockPopupRedirectResolver(eventManager, authPopup, false);
await expect(linkWithPopup(user, provider, resolver)).to.be.rejectedWith(
FirebaseError,
'auth/web-storage-unsupported'
);
});
it('passes any errors from idp task', async () => {
idpStubs._link.returns(
Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID))
);
const promise = linkWithPopup(user, provider, resolver);
iframeEvent({
eventId: MATCHING_EVENT_ID,
type: AuthEventType.LINK_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-id'
);
});
it('cancels the task if called consecutively', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.LINK
});
idpStubs._link.returns(Promise.resolve(cred));
const firstPromise = linkWithPopup(user, provider, resolver);
const secondPromise = linkWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.LINK_VIA_POPUP
});
await expect(firstPromise).to.be.rejectedWith(
FirebaseError,
'auth/cancelled-popup-request'
);
expect(await secondPromise).to.eq(cred);
});
});
context('reauthenticateWithPopup', () => {
let user: UserInternal;
beforeEach(() => {
user = testUser(auth, 'uid');
});
it('completes the full flow', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('completes the full flow with default resolver', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
user.auth._popupRedirectResolver = _getInstance(resolver);
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider);
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('errors if resolver not provided and not on auth', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
await expect(reauthenticateWithPopup(user, provider)).to.be.rejectedWith(
FirebaseError,
'auth/argument-error'
);
});
it('ignores events for another event id', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP,
eventId: OTHER_EVENT_ID,
error: {
code: 'auth/internal-error',
message: '',
name: ''
}
});
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP,
eventId: MATCHING_EVENT_ID
});
expect(await promise).to.eq(cred);
});
it('does not call idp tasks if event is error', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP,
eventId: MATCHING_EVENT_ID,
error: {
code: 'auth/invalid-app-credential',
message: '',
name: ''
}
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-credential'
);
expect(idpStubs._reauth).not.to.have.been.called;
});
it('does not error if the poll timeout trips', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
});
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP
});
expect(await promise).to.eq(cred);
});
it('errors if webstorage support comes back negative', async () => {
resolver = makeMockPopupRedirectResolver(eventManager, authPopup, false);
await expect(
reauthenticateWithPopup(user, provider, resolver)
).to.be.rejectedWith(FirebaseError, 'auth/web-storage-unsupported');
});
it('does error if the poll timeout and event timeout trip', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const promise = reauthenticateWithPopup(user, provider, resolver);
delay(() => {
underlyingWindow.closed = true;
pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()]();
pendingTimeouts[_Timeout.AUTH_EVENT]();
});
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/popup-closed-by-user'
);
});
it('passes any errors from idp task', async () => {
idpStubs._reauth.returns(
Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID))
);
const promise = reauthenticateWithPopup(user, provider, resolver);
iframeEvent({
eventId: MATCHING_EVENT_ID,
type: AuthEventType.REAUTH_VIA_POPUP
});
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-app-id'
);
});
it('cancels the task if called consecutively', async () => {
const cred = new UserCredentialImpl({
user,
providerId: ProviderId.GOOGLE,
operationType: OperationType.REAUTHENTICATE
});
idpStubs._reauth.returns(Promise.resolve(cred));
const firstPromise = reauthenticateWithPopup(user, provider, resolver);
const secondPromise = reauthenticateWithPopup(user, provider, resolver);
iframeEvent({
type: AuthEventType.REAUTH_VIA_POPUP
});
await expect(firstPromise).to.be.rejectedWith(
FirebaseError,
'auth/cancelled-popup-request'
);
expect(await secondPromise).to.eq(cred);
});
});
});