Skip to content

[Auth] Fix errors when cookies are disabled in Chrome (compat layer) #5923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-geckos-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@firebase/auth-compat": patch
"@firebase/auth": patch
---

Fix errors in compatibility layer when cookies are fully disabled in Chrome
71 changes: 70 additions & 1 deletion packages/auth-compat/src/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { Auth } from './auth';
import { CompatPopupRedirectResolver } from './popup_redirect';
import * as platform from './platform';

use(sinonChai);

function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

// For the most part, the auth methods just call straight through. Some parts
// of the auth compat layer are more complicated: these tests cover those
describe('auth compat', () => {
Expand All @@ -45,7 +50,7 @@ describe('auth compat', () => {
});

afterEach(() => {
sinon.restore;
sinon.restore();
});

it('saves the persistence into session storage if available', async () => {
Expand Down Expand Up @@ -75,6 +80,40 @@ describe('auth compat', () => {
}
});

it('does not save persistence if property throws DOMException', async () => {
if (typeof self !== 'undefined') {
sinon.stub(platform, '_getSelfWindow').returns({
get sessionStorage(): Storage {
throw new DOMException('Nope!');
}
} as unknown as Window);
const setItemSpy = sinon.spy(sessionStorage, 'setItem');
sinon.stub(underlyingAuth, '_getPersistence').returns('TEST');
sinon
.stub(underlyingAuth, '_initializationPromise')
.value(Promise.resolve());
sinon.stub(
exp._getInstance<exp.PopupRedirectResolverInternal>(
CompatPopupRedirectResolver
),
'_openRedirect'
);
providerStub.isInitialized.returns(true);
providerStub.getImmediate.returns(underlyingAuth);
const authCompat = new Auth(
app,
providerStub as unknown as Provider<'auth'>
);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
await authCompat.signInWithRedirect(new exp.GoogleAuthProvider());
await delay(50);
expect(setItemSpy).not.to.have.been.calledWith(
'firebase:persistence:api-key:undefined',
'TEST'
);
}
});

it('pulls the persistence and sets as the main persitsence if set', () => {
if (typeof self !== 'undefined') {
sessionStorage.setItem(
Expand All @@ -98,5 +137,35 @@ describe('auth compat', () => {
});
}
});

it('does not die if sessionStorage errors', async () => {
if (typeof self !== 'undefined') {
sinon.stub(platform, '_getSelfWindow').returns({
get sessionStorage(): Storage {
throw new DOMException('Nope!');
}
} as unknown as Window);
sessionStorage.setItem(
'firebase:persistence:api-key:undefined',
'none'
);
providerStub.isInitialized.returns(false);
providerStub.initialize.returns(underlyingAuth);
new Auth(app, providerStub as unknown as Provider<'auth'>);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
await delay(50);
expect(providerStub.initialize).to.have.been.calledWith({
options: {
popupRedirectResolver: CompatPopupRedirectResolver,
persistence: [
exp.indexedDBLocalPersistence,
exp.browserLocalPersistence,
exp.browserSessionPersistence,
exp.inMemoryPersistence
]
}
});
}
});
});
});
24 changes: 14 additions & 10 deletions packages/auth-compat/src/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import * as exp from '@firebase/auth/internal';
import { isIndexedDBAvailable, isNode, isReactNative } from '@firebase/util';
import { _isWebStorageSupported, _isWorker } from './platform';
import { _getSelfWindow, _isWebStorageSupported, _isWorker } from './platform';

export const Persistence = {
LOCAL: 'local',
Expand Down Expand Up @@ -84,29 +84,28 @@ export async function _savePersistenceForRedirect(
auth: exp.AuthInternal
): Promise<void> {
await auth._initializationPromise;

const win = getSelfWindow();
const session = getSessionStorageIfAvailable();
const key = exp._persistenceKeyName(
PERSISTENCE_KEY,
auth.config.apiKey,
auth.name
);
if (win?.sessionStorage) {
win.sessionStorage.setItem(key, auth._getPersistence());
if (session) {
session.setItem(key, auth._getPersistence());
}
}

export function _getPersistencesFromRedirect(
apiKey: string,
appName: string
): exp.Persistence[] {
const win = getSelfWindow();
if (!win?.sessionStorage) {
const session = getSessionStorageIfAvailable();
if (!session) {
return [];
}

const key = exp._persistenceKeyName(PERSISTENCE_KEY, apiKey, appName);
const persistence = win.sessionStorage.getItem(key);
const persistence = session.getItem(key);

switch (persistence) {
case Persistence.NONE:
Expand All @@ -120,6 +119,11 @@ export function _getPersistencesFromRedirect(
}
}

function getSelfWindow(): Window | null {
return typeof window !== 'undefined' ? window : null;
/** Returns session storage, or null if the property access errors */
function getSessionStorageIfAvailable(): Storage | null {
try {
return _getSelfWindow()?.sessionStorage || null;
} catch (e) {
return null;
}
}
4 changes: 4 additions & 0 deletions packages/auth-compat/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,7 @@ export async function _isCordova(): Promise<boolean> {
});
});
}

export function _getSelfWindow(): Window | null {
return typeof window !== 'undefined' ? window : null;
}