Skip to content

Commit be0dcaf

Browse files
authored
Add redirect strategy (#3416)
* Redirect strategy * Redirect strategy updates & test * Formatting * Moving the ts files in demo/ to 'broken' since they cause 'yarn test' to break and Alex is unavailable this week
1 parent a72ccab commit be0dcaf

15 files changed

+681
-81
lines changed

packages-exp/auth-exp/demo/src/index.js

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
createUserWithEmailAndPassword,
3232
EmailAuthProvider,
3333
fetchSignInMethodsForEmail,
34-
getMultiFactorResolver,
3534
indexedDBLocalPersistence,
3635
initializeAuth,
3736
inMemoryPersistence,
@@ -54,10 +53,15 @@ import {
5453
updatePassword,
5554
updateProfile,
5655
verifyPasswordResetCode,
56+
getMultiFactorResolver,
5757
OAuthProvider,
5858
signInWithPopup,
5959
linkWithPopup,
6060
reauthenticateWithPopup,
61+
signInWithRedirect,
62+
linkWithRedirect,
63+
reauthenticateWithRedirect,
64+
getRedirectResult,
6165
browserPopupRedirectResolver
6266
} from '@firebase/auth-exp/dist/index.browser';
6367

@@ -1240,12 +1244,16 @@ function signInWithPopupRedirect(provider) {
12401244
const glob = {
12411245
signInWithPopup,
12421246
linkWithPopup,
1243-
reauthenticateWithPopup
1247+
reauthenticateWithPopup,
1248+
signInWithRedirect,
1249+
linkWithRedirect,
1250+
reauthenticateWithRedirect
12441251
};
12451252
let action = $('input[name=popup-redirect-action]:checked').val();
12461253
let type = $('input[name=popup-redirect-type]:checked').val();
12471254
let method = null;
12481255
let inst = null;
1256+
12491257
if (action == 'link' || action == 'reauthenticate') {
12501258
if (!activeUser()) {
12511259
alertError('No user logged in.');
@@ -1310,13 +1318,14 @@ function signInWithPopupRedirect(provider) {
13101318
onAuthError
13111319
);
13121320
} else {
1313-
alertNotImplemented();
1314-
// try {
1315-
// inst[method](provider).catch(onAuthError);
1316-
// } catch (error) {
1317-
// console.log('Error while calling ' + method);
1318-
// console.error(error);
1319-
// }
1321+
try {
1322+
glob[method](inst, provider, browserPopupRedirectResolver).catch(
1323+
onAuthError
1324+
);
1325+
} catch (error) {
1326+
console.log('Error while calling ' + method);
1327+
console.error(error);
1328+
}
13201329
}
13211330
}
13221331

@@ -1334,24 +1343,26 @@ function onAuthUserCredentialSuccess(result) {
13341343
* Displays redirect result.
13351344
*/
13361345
function onGetRedirectResult() {
1337-
alertNotImplemented();
1338-
// auth.getRedirectResult().then(function(response) {
1339-
// log('Redirect results:');
1340-
// if (response.credential) {
1341-
// log('Credential:');
1342-
// log(response.credential);
1343-
// } else {
1344-
// log('No credential');
1345-
// }
1346-
// if (response.user) {
1347-
// log('User\'s id:');
1348-
// log(response.user.uid);
1349-
// } else {
1350-
// log('No user');
1351-
// }
1352-
// logAdditionalUserInfo(response);
1353-
// console.log(response);
1354-
// }, onAuthError);
1346+
getRedirectResult(auth, browserPopupRedirectResolver).then(function(
1347+
response
1348+
) {
1349+
log('Redirect results:');
1350+
if (response.credential) {
1351+
log('Credential:');
1352+
log(response.credential);
1353+
} else {
1354+
log('No credential');
1355+
}
1356+
if (response.user) {
1357+
log("User's id:");
1358+
log(response.user.uid);
1359+
} else {
1360+
log('No user');
1361+
}
1362+
logAdditionalUserInfo(response);
1363+
console.log(response);
1364+
},
1365+
onAuthError);
13551366
}
13561367

13571368
/**
@@ -1632,7 +1643,8 @@ function initApp() {
16321643
log('Initializing app...');
16331644
app = initializeApp(config);
16341645
auth = initializeAuth(app, {
1635-
persistence: browserSessionPersistence
1646+
persistence: browserSessionPersistence,
1647+
popupRedirectResolver: browserPopupRedirectResolver
16361648
});
16371649

16381650
// tempApp = initializeApp({
@@ -1764,11 +1776,13 @@ function initApp() {
17641776
}
17651777

17661778
// We check for redirect result to refresh user's data.
1767-
// TODO: redirect result
1768-
// auth.getRedirectResult().then(function(response) {
1769-
// refreshUserData();
1770-
// logAdditionalUserInfo(response);
1771-
// }, onAuthError);
1779+
getRedirectResult(auth, browserPopupRedirectResolver).then(function(
1780+
response
1781+
) {
1782+
refreshUserData();
1783+
logAdditionalUserInfo(response);
1784+
},
1785+
onAuthError);
17721786

17731787
// Bootstrap tooltips.
17741788
$('[data-toggle="tooltip"]').tooltip();

packages-exp/auth-exp/index.browser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ export {
3838
linkWithPopup,
3939
reauthenticateWithPopup
4040
} from './src/core/strategies/popup';
41+
export {
42+
signInWithRedirect,
43+
linkWithRedirect,
44+
reauthenticateWithRedirect,
45+
getRedirectResult
46+
} from './src/core/strategies/redirect';
4147

4248
// platform_browser
4349
export { RecaptchaVerifier } from './src/platform_browser/recaptcha/recaptcha_verifier';

packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ describe('src/core/auth/auth_event_manager', () => {
145145
consumer = makeConsumer([
146146
AuthEventType.SIGN_IN_VIA_REDIRECT,
147147
AuthEventType.LINK_VIA_REDIRECT,
148-
AuthEventType.REAUTH_VIA_REDIRECT
148+
AuthEventType.REAUTH_VIA_REDIRECT,
149+
AuthEventType.UNKNOWN
149150
]);
150151
});
151152

@@ -181,15 +182,13 @@ describe('src/core/auth/auth_event_manager', () => {
181182
expect(consumerB.onAuthEvent).not.to.have.been.called;
182183
});
183184

184-
it('unknown auth error prevents consumption of future redirect events', () => {
185+
it('queues unknown events', () => {
185186
const event = makeEvent(AuthEventType.UNKNOWN);
186187
event.error = { code: 'auth/no-auth-event' } as AuthEventError;
187188
expect(manager.onEvent(event)).to.be.true;
188-
expect(manager.onEvent(makeEvent(AuthEventType.SIGN_IN_VIA_REDIRECT))).to
189-
.be.false;
190189

191190
manager.registerConsumer(consumer);
192-
expect(consumer.onAuthEvent).not.to.have.been.called;
191+
expect(consumer.onAuthEvent).to.have.been.calledWith(event);
193192
});
194193
});
195194
});

packages-exp/auth-exp/src/core/auth/auth_event_manager.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,6 @@ export class AuthEventManager implements EventManager {
4747
}
4848

4949
onEvent(event: AuthEvent): boolean {
50-
if (isNullRedirectEvent(event)) {
51-
// A null redirect event comes through as the first event when no pending
52-
// redirect actions are in the auth domain
53-
this.hasHandledPotentialRedirect = true;
54-
return true;
55-
}
56-
5750
let handled = false;
5851
this.consumers.forEach(consumer => {
5952
if (this.isEventForConsumer(event, consumer)) {
@@ -62,7 +55,7 @@ export class AuthEventManager implements EventManager {
6255
}
6356
});
6457

65-
if (this.hasHandledPotentialRedirect || !isRedirectEvent(event.type)) {
58+
if (this.hasHandledPotentialRedirect || !isRedirectEvent(event)) {
6659
// If we've already seen a redirect before, or this is a popup event,
6760
// bail now
6861
return handled;
@@ -80,7 +73,7 @@ export class AuthEventManager implements EventManager {
8073
}
8174

8275
private sendToConsumer(event: AuthEvent, consumer: AuthEventConsumer): void {
83-
if (event.error) {
76+
if (event.error && !isNullRedirectEvent(event)) {
8477
const code =
8578
(event.error.code?.split('auth/')[1] as AuthErrorCode) ||
8679
AuthErrorCode.INTERNAL_ERROR;
@@ -112,12 +105,14 @@ function isNullRedirectEvent({ type, error }: AuthEvent): boolean {
112105
);
113106
}
114107

115-
function isRedirectEvent(type: AuthEventType): boolean {
116-
switch (type) {
108+
function isRedirectEvent(event: AuthEvent): boolean {
109+
switch (event.type) {
117110
case AuthEventType.SIGN_IN_VIA_REDIRECT:
118111
case AuthEventType.LINK_VIA_REDIRECT:
119112
case AuthEventType.REAUTH_VIA_REDIRECT:
120113
return true;
114+
case AuthEventType.UNKNOWN:
115+
return isNullRedirectEvent(event);
121116
default:
122117
return false;
123118
}

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

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class AuthImpl implements Auth {
6060
private idTokenSubscription = new Subscription<User>(this);
6161
private redirectUser: User | null = null;
6262
_isInitialized = false;
63+
_popupRedirectResolver: PopupRedirectResolver | null = null;
6364

6465
// Tracks the last notified UID for state change listeners to prevent
6566
// repeated calls to the callbacks
@@ -79,21 +80,23 @@ export class AuthImpl implements Auth {
7980
popupRedirectResolver?: externs.PopupRedirectResolver
8081
): Promise<void> {
8182
return this.queue(async () => {
83+
if (popupRedirectResolver) {
84+
this._popupRedirectResolver = _getInstance(popupRedirectResolver);
85+
}
86+
8287
this.persistenceManager = await PersistenceUserManager.create(
8388
this,
8489
persistenceHierarchy
8590
);
8691

87-
await this.initializeCurrentUser(popupRedirectResolver);
92+
await this.initializeCurrentUser();
8893

8994
this._isInitialized = true;
9095
this.notifyAuthListeners();
9196
});
9297
}
9398

94-
private async initializeCurrentUser(
95-
popupRedirectResolver?: externs.PopupRedirectResolver
96-
): Promise<void> {
99+
private async initializeCurrentUser(): Promise<void> {
97100
const storedUser = await this.assertedPersistence.getCurrentUser();
98101
if (!storedUser) {
99102
return this.directlySetCurrentUser(storedUser);
@@ -104,16 +107,12 @@ export class AuthImpl implements Auth {
104107
return this.reloadAndSetCurrentUserOrClear(storedUser);
105108
}
106109

107-
assert(popupRedirectResolver, this.name, AuthErrorCode.ARGUMENT_ERROR);
108-
const resolver: PopupRedirectResolver = _getInstance(popupRedirectResolver);
109-
110-
this.redirectPersistenceManager = await PersistenceUserManager.create(
111-
this,
112-
[_getInstance(resolver._redirectPersistence)],
113-
_REDIRECT_USER_KEY_NAME
110+
assert(
111+
this._popupRedirectResolver,
112+
this.name,
113+
AuthErrorCode.ARGUMENT_ERROR
114114
);
115-
116-
this.redirectUser = await this.redirectPersistenceManager.getCurrentUser();
115+
await this.getOrInitRedirectPersistenceManager();
117116

118117
// If the redirect user's event ID matches the current user's event ID,
119118
// DO NOT reload the current user, otherwise they'll be cleared from storage.
@@ -189,11 +188,41 @@ export class AuthImpl implements Auth {
189188
);
190189
}
191190

192-
async _setRedirectUser(user: User): Promise<void> {
193-
return this.redirectPersistenceManager?.setCurrentUser(user);
191+
async _setRedirectUser(
192+
user: User | null,
193+
popupRedirectResolver?: externs.PopupRedirectResolver
194+
): Promise<void> {
195+
const redirectManager = await this.getOrInitRedirectPersistenceManager(
196+
popupRedirectResolver
197+
);
198+
return user === null
199+
? redirectManager.removeCurrentUser()
200+
: redirectManager.setCurrentUser(user);
194201
}
195202

196-
_redirectUserForId(id: string): User | null {
203+
private async getOrInitRedirectPersistenceManager(
204+
popupRedirectResolver?: externs.PopupRedirectResolver
205+
): Promise<PersistenceUserManager> {
206+
if (!this.redirectPersistenceManager) {
207+
const resolver: PopupRedirectResolver | null =
208+
(popupRedirectResolver && _getInstance(popupRedirectResolver)) ||
209+
this._popupRedirectResolver;
210+
assert(resolver, this.name, AuthErrorCode.ARGUMENT_ERROR);
211+
this.redirectPersistenceManager = await PersistenceUserManager.create(
212+
this,
213+
[_getInstance(resolver._redirectPersistence)],
214+
_REDIRECT_USER_KEY_NAME
215+
);
216+
this.redirectUser = await this.redirectPersistenceManager.getCurrentUser();
217+
}
218+
219+
return this.redirectPersistenceManager;
220+
}
221+
222+
async _redirectUserForId(id: string): Promise<User | null> {
223+
// Make sure we've cleared any pending ppersistence actions
224+
await this.queue(async () => {});
225+
197226
if (this.currentUser?._redirectEventId === id) {
198227
return this.currentUser;
199228
}

packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ describe('src/core/strategies/abstract_popup_redirect_operation', () => {
144144

145145
it('emits the user credential returned from idp task', async () => {
146146
finishPromise(authEvent());
147-
const cred = await operation.execute();
147+
const cred = (await operation.execute())!;
148148
expect(cred.user.uid).to.eq('uid');
149149
expect(cred.credential).to.be.null;
150150
expect(cred.operationType).to.eq(OperationType.SIGN_IN);

packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { debugAssert, fail } from '../util/assert';
3131
import { _link, _reauth, _signIn, IdpTask, IdpTaskParams } from './idp';
3232

3333
interface PendingPromise {
34-
resolve: (cred: UserCredential) => void;
34+
resolve: (cred: UserCredential | null) => void;
3535
reject: (error: Error) => void;
3636
}
3737

@@ -58,8 +58,8 @@ export abstract class AbstractPopupRedirectOperation
5858

5959
abstract onExecution(): Promise<void>;
6060

61-
execute(): Promise<UserCredential> {
62-
return new Promise<UserCredential>(async (resolve, reject) => {
61+
execute(): Promise<UserCredential | null> {
62+
return new Promise<UserCredential | null>(async (resolve, reject) => {
6363
this.pendingPromise = { resolve, reject };
6464

6565
try {
@@ -115,7 +115,7 @@ export abstract class AbstractPopupRedirectOperation
115115
}
116116
}
117117

118-
protected resolve(cred: UserCredential): void {
118+
protected resolve(cred: UserCredential | null): void {
119119
debugAssert(this.pendingPromise, 'Pending promise was never set');
120120
this.pendingPromise.resolve(cred);
121121
this.unregisterAndCleanUp();

0 commit comments

Comments
 (0)