Skip to content

Commit f190613

Browse files
authored
Merge 6d22568 into 4604b3c
2 parents 4604b3c + 6d22568 commit f190613

File tree

6 files changed

+75
-28
lines changed

6 files changed

+75
-28
lines changed

.changeset/chilled-boats-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/auth': patch
3+
---
4+
5+
Expose TOKEN_EXPIRED error when mfa unenroll logs out the user.

packages/auth/demo/public/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@
252252
id="sign-in-with-email-and-password">
253253
Sign In with Email and Password
254254
</button>
255+
<button class="btn btn-block btn-primary"
256+
id="reauth-with-email-and-password">
257+
Reauthenticate with Email and Password
258+
</button>
255259
</form>
256260
<form class="form form-bordered no-submit">
257261
<input type="text" id="user-custom-token" class="form-control"

packages/auth/demo/src/index.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ function showMultiFactorStatus(activeUser) {
247247
const label = info && (info.displayName || info.uid);
248248
if (label) {
249249
$('#enrolled-factors-drop-down').removeClass('open');
250+
// Set the last user, in case the current user is logged out.
251+
// This can happen if the MFA option being unenrolled is the one that was most recently enrolled into.
252+
// See - https://github.com/firebase/firebase-js-sdk/issues/3233
253+
setLastUser(activeUser);
250254
mfaUser.unenroll(info).then(() => {
251255
refreshUserData();
252256
alertSuccess('Multi-factor successfully unenrolled.');
@@ -278,6 +282,9 @@ function onAuthError(error) {
278282
handleMultiFactorSignIn(getMultiFactorResolver(auth, error));
279283
} else {
280284
alertError('Error: ' + error.code);
285+
if (error.code === 'auth/user-token-expired') {
286+
alertError('Token expired, please reauthenticate.');
287+
}
281288
}
282289
}
283290

@@ -403,13 +410,41 @@ function onLinkWithEmailLink() {
403410
* Re-authenticate a user with email link credential.
404411
*/
405412
function onReauthenticateWithEmailLink() {
413+
if (!activeUser()) {
414+
alertError(
415+
'No user logged in. Select the "Last User" tab to reauth the previous user.'
416+
);
417+
return;
418+
}
406419
const email = $('#link-with-email-link-email').val();
407420
const link = $('#link-with-email-link-link').val() || undefined;
408421
const credential = EmailAuthProvider.credentialWithLink(email, link);
422+
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
409423
reauthenticateWithCredential(activeUser(), credential).then(result => {
410424
logAdditionalUserInfo(result);
411425
refreshUserData();
412-
alertSuccess('User reauthenticated!');
426+
alertSuccess('User reauthenticated with email link!');
427+
}, onAuthError);
428+
}
429+
430+
/**
431+
* Re-authenticate a user with email and password.
432+
*/
433+
function onReauthenticateWithEmailAndPassword() {
434+
if (!activeUser()) {
435+
alertError(
436+
'No user logged in. Select the "Last User" tab to reauth the previous user.'
437+
);
438+
return;
439+
}
440+
const email = $('#signin-email').val();
441+
const password = $('#signin-password').val();
442+
const credential = EmailAuthProvider.credential(email, password);
443+
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
444+
reauthenticateWithCredential(activeUser(), credential).then(result => {
445+
logAdditionalUserInfo(result);
446+
refreshUserData();
447+
alertSuccess('User reauthenticated with email/password!');
413448
}, onAuthError);
414449
}
415450

@@ -1264,7 +1299,9 @@ function signInWithPopupRedirect(provider) {
12641299
break;
12651300
case 'reauthenticate':
12661301
if (!activeUser()) {
1267-
alertError('No user logged in.');
1302+
alertError(
1303+
'No user logged in. Select the "Last User" tab to reauth the previous user.'
1304+
);
12681305
return;
12691306
}
12701307
inst = activeUser();
@@ -1860,6 +1897,9 @@ function initApp() {
18601897
// Actions listeners.
18611898
$('#sign-up-with-email-and-password').click(onSignUp);
18621899
$('#sign-in-with-email-and-password').click(onSignInWithEmailAndPassword);
1900+
$('#reauth-with-email-and-password').click(
1901+
onReauthenticateWithEmailAndPassword
1902+
);
18631903
$('.sign-in-with-custom-token').click(onSignInWithCustomToken);
18641904
$('#sign-in-anonymously').click(onSignInAnonymously);
18651905
$('#sign-in-with-generic-idp-credential').click(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export async function linkWithCredential(
9696
*
9797
* @remarks
9898
* Use before operations such as {@link updatePassword} that require tokens from recent sign-in
99-
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error.
99+
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error
100+
* or a `TOKEN_EXPIRED` error.
100101
*
101102
* @param user - The user.
102103
* @param credential - The auth credential.

packages/auth/src/mfa/mfa_user.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,10 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => {
235235
);
236236
});
237237

238-
it('should swallow the error', async () => {
239-
await mfaUser.unenroll(mfaInfo);
238+
it('should throw TOKEN_EXPIRED error', async () => {
239+
await expect(mfaUser.unenroll(mfaInfo)).to.be.rejectedWith(
240+
'auth/user-token-expired'
241+
);
240242
});
241243
});
242244
});

packages/auth/src/mfa/mfa_user.ts

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ import {
2323
} from '../model/public_types';
2424

2525
import { withdrawMfa } from '../api/account_management/mfa';
26-
import { AuthErrorCode } from '../core/errors';
2726
import { _logoutIfInvalidated } from '../core/user/invalidation';
2827
import { UserInternal } from '../model/user';
2928
import { MultiFactorAssertionImpl } from './mfa_assertion';
3029
import { MultiFactorInfoImpl } from './mfa_info';
3130
import { MultiFactorSessionImpl } from './mfa_session';
32-
import { FirebaseError, getModularInstance } from '@firebase/util';
31+
import { getModularInstance } from '@firebase/util';
3332

3433
export class MultiFactorUserImpl implements MultiFactorUser {
3534
enrolledFactors: MultiFactorInfo[] = [];
@@ -78,30 +77,26 @@ export class MultiFactorUserImpl implements MultiFactorUser {
7877
const mfaEnrollmentId =
7978
typeof infoOrUid === 'string' ? infoOrUid : infoOrUid.uid;
8079
const idToken = await this.user.getIdToken();
81-
const idTokenResponse = await _logoutIfInvalidated(
82-
this.user,
83-
withdrawMfa(this.user.auth, {
84-
idToken,
85-
mfaEnrollmentId
86-
})
87-
);
88-
// Remove the second factor from the user's list.
89-
this.enrolledFactors = this.enrolledFactors.filter(
90-
({ uid }) => uid !== mfaEnrollmentId
91-
);
92-
// Depending on whether the backend decided to revoke the user's session,
93-
// the tokenResponse may be empty. If the tokens were not updated (and they
94-
// are now invalid), reloading the user will discover this and invalidate
95-
// the user's state accordingly.
96-
await this.user._updateTokensIfNecessary(idTokenResponse);
9780
try {
81+
const idTokenResponse = await _logoutIfInvalidated(
82+
this.user,
83+
withdrawMfa(this.user.auth, {
84+
idToken,
85+
mfaEnrollmentId
86+
})
87+
);
88+
// Remove the second factor from the user's list.
89+
this.enrolledFactors = this.enrolledFactors.filter(
90+
({ uid }) => uid !== mfaEnrollmentId
91+
);
92+
// Depending on whether the backend decided to revoke the user's session,
93+
// the tokenResponse may be empty. If the tokens were not updated (and they
94+
// are now invalid), reloading the user will discover this and invalidate
95+
// the user's state accordingly.
96+
await this.user._updateTokensIfNecessary(idTokenResponse);
9897
await this.user.reload();
9998
} catch (e) {
100-
if (
101-
(e as FirebaseError)?.code !== `auth/${AuthErrorCode.TOKEN_EXPIRED}`
102-
) {
103-
throw e;
104-
}
99+
throw e;
105100
}
106101
}
107102
}

0 commit comments

Comments
 (0)