Skip to content

Commit 56e24c2

Browse files
authored
Merge a4f9873 into 4f6ee70
2 parents 4f6ee70 + a4f9873 commit 56e24c2

File tree

14 files changed

+147
-32
lines changed

14 files changed

+147
-32
lines changed

packages/auth-compat/src/popup_redirect.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
175175

176176
class FakeResolver implements exp.PopupRedirectResolverInternal {
177177
_completeRedirectFn = async (): Promise<null> => null;
178+
_overrideRedirectResult = (): void => {};
178179
_redirectPersistence = exp.inMemoryPersistence;
179180
_shouldInitProactively = true;
180181

packages/auth-compat/src/popup_redirect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class CompatPopupRedirectResolver
3838
resolver: exp.PopupRedirectResolver,
3939
bypassAuthState: boolean
4040
) => Promise<exp.UserCredential | null> = exp._getRedirectResult;
41+
_overrideRedirectResult = exp._overrideRedirectResult;
4142

4243
async _initialize(auth: exp.AuthImpl): Promise<exp.EventManager> {
4344
await this.selectUnderlyingResolver();

packages/auth/internal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export { TaggedWithTokenResponse } from '../src/model/id_token';
4545
export { _fail, _assert } from '../src/core/util/assert';
4646
export { AuthPopup } from '../src/platform_browser/util/popup';
4747
export { _getRedirectResult } from '../src/platform_browser/strategies/redirect';
48+
export { _overrideRedirectResult } from '../src/core/strategies/redirect';
4849
export { cordovaPopupRedirectResolver } from '../src/platform_cordova/popup_redirect/popup_redirect';
4950
export { FetchProvider } from '../src/core/util/fetch_provider';
5051
export { SAMLAuthCredential } from '../src/core/credentials/saml';

packages/auth/src/core/auth/auth_impl.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,14 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
192192
popupRedirectResolver?: PopupRedirectResolver
193193
): Promise<void> {
194194
// First check to see if we have a pending redirect event.
195-
let storedUser =
195+
const previouslyStoredUser =
196196
(await this.assertedPersistence.getCurrentUser()) as UserInternal | null;
197+
let futureCurrentUser = previouslyStoredUser;
198+
let needsTocheckMiddleware = false;
197199
if (popupRedirectResolver && this.config.authDomain) {
198200
await this.getOrInitRedirectPersistenceManager();
199201
const redirectUserEventId = this.redirectUser?._redirectEventId;
200-
const storedUserEventId = storedUser?._redirectEventId;
202+
const storedUserEventId = futureCurrentUser?._redirectEventId;
201203
const result = await this.tryRedirectSignIn(popupRedirectResolver);
202204

203205
// If the stored user (i.e. the old "currentUser") has a redirectId that
@@ -208,44 +210,51 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
208210
(!redirectUserEventId || redirectUserEventId === storedUserEventId) &&
209211
result?.user
210212
) {
211-
storedUser = result.user as UserInternal;
213+
futureCurrentUser = result.user as UserInternal;
214+
needsTocheckMiddleware = true;
212215
}
213216
}
214217

215218
// If no user in persistence, there is no current user. Set to null.
216-
if (!storedUser) {
219+
if (!futureCurrentUser) {
217220
return this.directlySetCurrentUser(null);
218221
}
219222

220-
if (!storedUser._redirectEventId) {
221-
// This isn't a redirect user, we can reload and bail
222-
// This will also catch the redirected user, if available, as that method
223-
// strips the _redirectEventId
224-
return this.reloadAndSetCurrentUserOrClear(storedUser);
223+
if (!futureCurrentUser._redirectEventId) {
224+
// This isn't a redirect link operation, we can reload and bail.
225+
// First though, ensure that we check the middleware is happy.
226+
if (needsTocheckMiddleware) {
227+
try {
228+
await this._runBeforeStateCallbacks(futureCurrentUser);
229+
} catch(e) {
230+
futureCurrentUser = previouslyStoredUser;
231+
// We know this is available since the bit is only set when the
232+
// resolver is available
233+
this._popupRedirectResolver!._overrideRedirectResult(this, () => Promise.reject(e));
234+
}
235+
}
236+
237+
if (futureCurrentUser) {
238+
return this.reloadAndSetCurrentUserOrClear(futureCurrentUser);
239+
} else {
240+
return this.directlySetCurrentUser(null);
241+
}
225242
}
226243

227244
_assert(this._popupRedirectResolver, this, AuthErrorCode.ARGUMENT_ERROR);
228245
await this.getOrInitRedirectPersistenceManager();
229246

230-
// At this point in the flow, this is a redirect user. Run blocking
231-
// middleware callbacks before setting the user.
232-
try {
233-
await this._runBeforeStateCallbacks(storedUser);
234-
} catch(e) {
235-
return;
236-
}
237-
238247
// If the redirect user's event ID matches the current user's event ID,
239248
// DO NOT reload the current user, otherwise they'll be cleared from storage.
240249
// This is important for the reauthenticateWithRedirect() flow.
241250
if (
242251
this.redirectUser &&
243-
this.redirectUser._redirectEventId === storedUser._redirectEventId
252+
this.redirectUser._redirectEventId === futureCurrentUser._redirectEventId
244253
) {
245-
return this.directlySetCurrentUser(storedUser);
254+
return this.directlySetCurrentUser(futureCurrentUser);
246255
}
247256

248-
return this.reloadAndSetCurrentUserOrClear(storedUser);
257+
return this.reloadAndSetCurrentUserOrClear(futureCurrentUser);
249258
}
250259

251260
private async tryRedirectSignIn(

packages/auth/src/core/auth/initialize.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ describe('core/auth/initialize', () => {
126126
): Promise<UserCredential | null> {
127127
return null;
128128
}
129+
async _overrideRedirectResult(): Promise<void> {
130+
}
129131
}
130132

131133
const fakePopupRedirectResolver: PopupRedirectResolver =

packages/auth/src/core/strategies/redirect.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ export function _clearRedirectOutcomes(): void {
139139
redirectOutcomeMap.clear();
140140
}
141141

142+
export function _overrideRedirectResult(auth: AuthInternal, result: () => Promise<UserCredentialInternal | null>): void {
143+
redirectOutcomeMap.set(auth._key(), result);
144+
}
145+
142146
function resolverPersistence(
143147
resolver: PopupRedirectResolverInternal
144148
): PersistenceInternal {

packages/auth/src/model/popup_redirect.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { FirebaseError } from '@firebase/util';
2626

2727
import { AuthPopup } from '../platform_browser/util/popup';
2828
import { AuthInternal } from './auth';
29+
import { UserCredentialInternal } from './user';
2930

3031
export const enum EventFilter {
3132
POPUP,
@@ -121,10 +122,11 @@ export interface PopupRedirectResolverInternal extends PopupRedirectResolver {
121122
_redirectPersistence: Persistence;
122123
_originValidation(auth: Auth): Promise<void>;
123124

124-
// This is needed so that auth does not have a hard dependency on redirect
125+
// These are needed so that auth does not have a hard dependency on redirect
125126
_completeRedirectFn: (
126127
auth: Auth,
127128
resolver: PopupRedirectResolver,
128129
bypassAuthState: boolean
129130
) => Promise<UserCredential | null>;
131+
_overrideRedirectResult: (auth: AuthInternal, resultGetter: () => Promise<UserCredentialInternal|null>) => void;
130132
}

packages/auth/src/platform_browser/auth.test.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ describe('core/auth/initializeAuth', () => {
130130
async function initAndWait(
131131
persistence: Persistence | Persistence[],
132132
popupRedirectResolver?: PopupRedirectResolver,
133-
authDomain = FAKE_APP.options.authDomain
133+
authDomain = FAKE_APP.options.authDomain,
134+
blockMiddleware = false,
134135
): Promise<Auth> {
135136
const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
136137
apiKey: FAKE_APP.options.apiKey!,
@@ -146,6 +147,12 @@ describe('core/auth/initializeAuth', () => {
146147
persistence,
147148
popupRedirectResolver
148149
});
150+
151+
if (blockMiddleware) {
152+
auth.beforeAuthStateChanged(() => {
153+
throw new Error('blocked');
154+
});
155+
}
149156
// Auth initializes async. We can make sure the initialization is
150157
// flushed by awaiting a method on the queue.
151158
await auth.setPersistence(inMemoryPersistence);
@@ -408,6 +415,84 @@ describe('core/auth/initializeAuth', () => {
408415
expect(user).not.to.be.null;
409416
expect(auth.currentUser).to.eq(user);
410417
});
418+
419+
it('does not halt old user load if middleware throws', async () => {
420+
const stub = sinon.stub(
421+
_getInstance<PersistenceInternal>(inMemoryPersistence)
422+
);
423+
const oldUser = testUser(oldAuth, 'old-uid');
424+
stub._get.returns(Promise.resolve(oldUser.toJSON()));
425+
const overrideSpy = sinon.spy(_getInstance<PopupRedirectResolverInternal>(browserPopupRedirectResolver), '_overrideRedirectResult');
426+
const auth = await initAndWait(
427+
[inMemoryPersistence],
428+
browserPopupRedirectResolver,
429+
FAKE_APP.options.authDomain,
430+
/* blockMiddleware */ true
431+
);
432+
433+
expect(auth.currentUser!.uid).to.eq(oldUser.uid);
434+
expect(reload._reloadWithoutSaving).to.have.been.called;
435+
expect(overrideSpy).not.to.have.been.called;
436+
});
437+
438+
it('Reloads and uses old user if middleware throws', async () => {
439+
const stub = sinon.stub(
440+
_getInstance<PersistenceInternal>(inMemoryPersistence)
441+
);
442+
const oldUser = testUser(oldAuth, 'old-uid');
443+
stub._get.returns(Promise.resolve(oldUser.toJSON()));
444+
const overrideSpy = sinon.spy(_getInstance<PopupRedirectResolverInternal>(browserPopupRedirectResolver), '_overrideRedirectResult');
445+
446+
let user: UserInternal | null = null;
447+
completeRedirectFnStub.callsFake((auth: AuthInternal) => {
448+
user = testUser(auth, 'uid', '[email protected]');
449+
return Promise.resolve(
450+
new UserCredentialImpl({
451+
operationType: OperationType.SIGN_IN,
452+
user,
453+
providerId: null
454+
})
455+
);
456+
});
457+
458+
const auth = await initAndWait(
459+
[inMemoryPersistence],
460+
browserPopupRedirectResolver,
461+
FAKE_APP.options.authDomain,
462+
/* blockMiddleware */ true
463+
);
464+
expect(user).not.to.be.null;
465+
expect(auth.currentUser!.uid).to.eq(oldUser.uid);
466+
expect(reload._reloadWithoutSaving).to.have.been.called;
467+
expect(overrideSpy).to.have.been.called;
468+
});
469+
470+
it('Nulls current user if redirect blocked by middleware', async () => {
471+
const stub = sinon.stub(
472+
_getInstance<PersistenceInternal>(inMemoryPersistence)
473+
);
474+
stub._get.returns(Promise.resolve(null));
475+
completeRedirectFnStub.callsFake((auth: AuthInternal) => {
476+
const user = testUser(auth, 'uid', '[email protected]');
477+
return Promise.resolve(
478+
new UserCredentialImpl({
479+
operationType: OperationType.SIGN_IN,
480+
user,
481+
providerId: null
482+
})
483+
);
484+
});
485+
486+
const auth = await initAndWait(
487+
[inMemoryPersistence],
488+
browserPopupRedirectResolver,
489+
FAKE_APP.options.authDomain,
490+
/* blockMiddleware */ true
491+
);
492+
expect(completeRedirectFnStub).to.have.been.called;
493+
expect(auth.currentUser).to.be.null;
494+
expect(reload._reloadWithoutSaving).not.to.have.been.called;
495+
});
411496
});
412497
});
413498
});

packages/auth/src/platform_browser/popup_redirect.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { _open, AuthPopup } from './util/popup';
3838
import { _getRedirectResult } from './strategies/redirect';
3939
import { _getRedirectUrl } from '../core/util/handler';
4040
import { _isIOS, _isMobileBrowser, _isSafari } from '../core/util/browser';
41+
import { _overrideRedirectResult } from '../core/strategies/redirect';
4142

4243
/**
4344
* The special web storage event
@@ -176,6 +177,8 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal {
176177
}
177178

178179
_completeRedirectFn = _getRedirectResult;
180+
181+
_overrideRedirectResult = _overrideRedirectResult;
179182
}
180183

181184
/**

packages/auth/src/platform_cordova/popup_redirect/popup_redirect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
} from './events';
4343
import { AuthEventManager } from '../../core/auth/auth_event_manager';
4444
import { _getRedirectResult } from '../../platform_browser/strategies/redirect';
45-
import { _clearRedirectOutcomes } from '../../core/strategies/redirect';
45+
import { _clearRedirectOutcomes, _overrideRedirectResult } from '../../core/strategies/redirect';
4646
import { _cordovaWindow } from '../plugins';
4747

4848
/**
@@ -58,6 +58,7 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
5858
private readonly originValidationPromises: Record<string, Promise<void>> = {};
5959

6060
_completeRedirectFn = _getRedirectResult;
61+
_overrideRedirectResult = _overrideRedirectResult;
6162

6263
async _initialize(auth: AuthInternal): Promise<CordovaAuthEventManager> {
6364
const key = auth._key();

packages/auth/test/helpers/mock_popup_redirect_resolver.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export function makeMockPopupRedirectResolver(
6060

6161
async _completeRedirectFn(): Promise<void> {}
6262

63+
async _overrideRedirectResult(): Promise<void> {}
64+
6365
async _originValidation(): Promise<void> {}
6466
};
6567
}

packages/auth/test/integration/webdriver/persistence.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,13 @@ browserDescribe('WebDriver persistence test', (driver, browser) => {
284284
.that.contains({ uid: user.uid });
285285
});
286286

287-
it('migrates user when switching from indexedDB to localStorage', async () => {
287+
it('migrates user when switching from indexedDB to localStorage', async function() {
288288
// This test only works in the modular SDK: the compat package does not
289289
// make the distinction between indexedDB and local storage (both are just
290290
// 'local').
291291
if (driver.isCompatLayer()) {
292292
console.warn('Skipping indexedDB to local migration in compat test');
293-
return;
293+
this.skip();
294294
}
295295

296296
await driver.call(AnonFunction.SIGN_IN_ANONYMOUSLY);
@@ -462,11 +462,11 @@ browserDescribe('WebDriver persistence test', (driver, browser) => {
462462
expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 });
463463
});
464464

465-
it('middleware does not block tab sync', async () => {
465+
it('middleware does not block tab sync', async function() {
466466
if (driver.isCompatLayer()) {
467467
// Compat layer is skipped because it doesn't support middleware
468468
console.warn('Skipping middleware tabs in compat test');
469-
return;
469+
this.skip();
470470
}
471471

472472
// Blocking middleware in main page

packages/auth/test/integration/webdriver/popup.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ browserDescribe('Popup IdP tests', driver => {
6464
expect(result.user).to.eql(currentUser);
6565
});
6666

67-
it('is blocked by auth middleware', async () => {
67+
it('is blocked by auth middleware', async function () {
6868
if (driver.isCompatLayer()) {
6969
// Compat layer doesn't support middleware yet
70-
return;
70+
this.skip();
7171
}
7272

7373
await driver.call(MiddlewareFunction.ATTACH_BLOCKING_MIDDLEWARE);

packages/auth/test/integration/webdriver/redirect.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ import { START_FUNCTION } from './util/auth_driver';
3838

3939
use(chaiAsPromised);
4040

41-
declare const xit: typeof it;
42-
4341
browserDescribe('WebDriver redirect IdP test', driver => {
4442
beforeEach(async () => {
4543
await driver.pause(200); // Race condition on auth init
@@ -76,7 +74,12 @@ browserDescribe('WebDriver redirect IdP test', driver => {
7674
});
7775

7876
// Redirect works with middleware for now
79-
xit('is blocked by middleware', async () => {
77+
it('is blocked by middleware', async function () {
78+
if (driver.isCompatLayer()) {
79+
console.warn('Skipping middleware tests in compat');
80+
this.skip();
81+
}
82+
8083
await driver.callNoWait(RedirectFunction.IDP_REDIRECT);
8184
const widget = new IdPPage(driver.webDriver);
8285

@@ -92,6 +95,7 @@ browserDescribe('WebDriver redirect IdP test', driver => {
9295
await driver.call(MiddlewareFunction.ATTACH_BLOCKING_MIDDLEWARE_ON_START);
9396

9497
await driver.reinitOnRedirect();
98+
await expect(driver.call(RedirectFunction.REDIRECT_RESULT)).to.be.rejectedWith('auth/login-blocked');
9599
expect(await driver.getUserSnapshot()).to.be.null;
96100
});
97101

0 commit comments

Comments
 (0)