UNPKG

veritatisdoloremque

Version:
634 lines (586 loc) 20.8 kB
/** * @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); }); }); });