Skip to content

Commit

Permalink
Expose TOKEN_EXPIRED error upon mfa unenroll. (#6973)
Browse files Browse the repository at this point in the history
* Expose TOKEN_EXPIRED error upon mfa unenroll.

This can be thrown if the MFA option that was most recently enrolled into, was unenrolled.
The user will be logged out to prove the posession of the other second factor.
This error can be handled by reauthenticating the user.

This change also updates the demo app to store the lastUser in case mfa unenroll logs out the user.
From here, the lastUser can be reauthenticated.

* Changeset
  • Loading branch information
prameshj committed Jan 24, 2023
1 parent 4604b3c commit 6439f11
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-boats-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/auth': patch
---

Expose TOKEN_EXPIRED error when mfa unenroll logs out the user.
4 changes: 4 additions & 0 deletions packages/auth/demo/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@
id="sign-in-with-email-and-password">
Sign In with Email and Password
</button>
<button class="btn btn-block btn-primary"
id="reauth-with-email-and-password">
Reauthenticate with Email and Password
</button>
</form>
<form class="form form-bordered no-submit">
<input type="text" id="user-custom-token" class="form-control"
Expand Down
44 changes: 42 additions & 2 deletions packages/auth/demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ function showMultiFactorStatus(activeUser) {
const label = info && (info.displayName || info.uid);
if (label) {
$('#enrolled-factors-drop-down').removeClass('open');
// Set the last user, in case the current user is logged out.
// This can happen if the MFA option being unenrolled is the one that was most recently enrolled into.
// See - https://github.com/firebase/firebase-js-sdk/issues/3233
setLastUser(activeUser);
mfaUser.unenroll(info).then(() => {
refreshUserData();
alertSuccess('Multi-factor successfully unenrolled.');
Expand Down Expand Up @@ -278,6 +282,9 @@ function onAuthError(error) {
handleMultiFactorSignIn(getMultiFactorResolver(auth, error));
} else {
alertError('Error: ' + error.code);
if (error.code === 'auth/user-token-expired') {
alertError('Token expired, please reauthenticate.');
}
}
}

Expand Down Expand Up @@ -403,13 +410,41 @@ function onLinkWithEmailLink() {
* Re-authenticate a user with email link credential.
*/
function onReauthenticateWithEmailLink() {
if (!activeUser()) {
alertError(
'No user logged in. Select the "Last User" tab to reauth the previous user.'
);
return;
}
const email = $('#link-with-email-link-email').val();
const link = $('#link-with-email-link-link').val() || undefined;
const credential = EmailAuthProvider.credentialWithLink(email, link);
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
reauthenticateWithCredential(activeUser(), credential).then(result => {
logAdditionalUserInfo(result);
refreshUserData();
alertSuccess('User reauthenticated!');
alertSuccess('User reauthenticated with email link!');
}, onAuthError);
}

/**
* Re-authenticate a user with email and password.
*/
function onReauthenticateWithEmailAndPassword() {
if (!activeUser()) {
alertError(
'No user logged in. Select the "Last User" tab to reauth the previous user.'
);
return;
}
const email = $('#signin-email').val();
const password = $('#signin-password').val();
const credential = EmailAuthProvider.credential(email, password);
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
reauthenticateWithCredential(activeUser(), credential).then(result => {
logAdditionalUserInfo(result);
refreshUserData();
alertSuccess('User reauthenticated with email/password!');
}, onAuthError);
}

Expand Down Expand Up @@ -1264,7 +1299,9 @@ function signInWithPopupRedirect(provider) {
break;
case 'reauthenticate':
if (!activeUser()) {
alertError('No user logged in.');
alertError(
'No user logged in. Select the "Last User" tab to reauth the previous user.'
);
return;
}
inst = activeUser();
Expand Down Expand Up @@ -1860,6 +1897,9 @@ function initApp() {
// Actions listeners.
$('#sign-up-with-email-and-password').click(onSignUp);
$('#sign-in-with-email-and-password').click(onSignInWithEmailAndPassword);
$('#reauth-with-email-and-password').click(
onReauthenticateWithEmailAndPassword
);
$('.sign-in-with-custom-token').click(onSignInWithCustomToken);
$('#sign-in-anonymously').click(onSignInAnonymously);
$('#sign-in-with-generic-idp-credential').click(
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/src/core/strategies/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export async function linkWithCredential(
*
* @remarks
* Use before operations such as {@link updatePassword} that require tokens from recent sign-in
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error.
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error
* or a `TOKEN_EXPIRED` error.
*
* @param user - The user.
* @param credential - The auth credential.
Expand Down
6 changes: 4 additions & 2 deletions packages/auth/src/mfa/mfa_user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,10 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => {
);
});

it('should swallow the error', async () => {
await mfaUser.unenroll(mfaInfo);
it('should throw TOKEN_EXPIRED error', async () => {
await expect(mfaUser.unenroll(mfaInfo)).to.be.rejectedWith(
'auth/user-token-expired'
);
});
});
});
Expand Down
41 changes: 18 additions & 23 deletions packages/auth/src/mfa/mfa_user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ import {
} from '../model/public_types';

import { withdrawMfa } from '../api/account_management/mfa';
import { AuthErrorCode } from '../core/errors';
import { _logoutIfInvalidated } from '../core/user/invalidation';
import { UserInternal } from '../model/user';
import { MultiFactorAssertionImpl } from './mfa_assertion';
import { MultiFactorInfoImpl } from './mfa_info';
import { MultiFactorSessionImpl } from './mfa_session';
import { FirebaseError, getModularInstance } from '@firebase/util';
import { getModularInstance } from '@firebase/util';

export class MultiFactorUserImpl implements MultiFactorUser {
enrolledFactors: MultiFactorInfo[] = [];
Expand Down Expand Up @@ -78,30 +77,26 @@ export class MultiFactorUserImpl implements MultiFactorUser {
const mfaEnrollmentId =
typeof infoOrUid === 'string' ? infoOrUid : infoOrUid.uid;
const idToken = await this.user.getIdToken();
const idTokenResponse = await _logoutIfInvalidated(
this.user,
withdrawMfa(this.user.auth, {
idToken,
mfaEnrollmentId
})
);
// Remove the second factor from the user's list.
this.enrolledFactors = this.enrolledFactors.filter(
({ uid }) => uid !== mfaEnrollmentId
);
// Depending on whether the backend decided to revoke the user's session,
// the tokenResponse may be empty. If the tokens were not updated (and they
// are now invalid), reloading the user will discover this and invalidate
// the user's state accordingly.
await this.user._updateTokensIfNecessary(idTokenResponse);
try {
const idTokenResponse = await _logoutIfInvalidated(
this.user,
withdrawMfa(this.user.auth, {
idToken,
mfaEnrollmentId
})
);
// Remove the second factor from the user's list.
this.enrolledFactors = this.enrolledFactors.filter(
({ uid }) => uid !== mfaEnrollmentId
);
// Depending on whether the backend decided to revoke the user's session,
// the tokenResponse may be empty. If the tokens were not updated (and they
// are now invalid), reloading the user will discover this and invalidate
// the user's state accordingly.
await this.user._updateTokensIfNecessary(idTokenResponse);
await this.user.reload();
} catch (e) {
if (
(e as FirebaseError)?.code !== `auth/${AuthErrorCode.TOKEN_EXPIRED}`
) {
throw e;
}
throw e;
}
}
}
Expand Down

0 comments on commit 6439f11

Please sign in to comment.