diff --git a/src/CONST/index.ts b/src/CONST/index.ts index d41a326a1db3..0cae7aeb0684 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2192,6 +2192,7 @@ const CONST = { BAD_REQUEST: 400, INVALID_SEARCH_QUERY: 401, NOT_AUTHENTICATED: 407, + NEEDS_2FA_SETUP: 432, EXP_ERROR: 666, UNABLE_TO_RETRY: 'unableToRetry', UPDATE_REQUIRED: 426, @@ -2236,10 +2237,12 @@ const CONST = { }, ERROR_TYPE: { SOCKET: 'Expensify\\Auth\\Error\\Socket', + NEEDS_2FA_SETUP: 'Expensify\\Error\\Policy\\PolicyRequires2FA', }, ERROR_TITLE: { SOCKET: 'Issue connecting to database', DUPLICATE_RECORD: '400 Unique Constraints Violation', + NEEDS_2FA_SETUP: 'Two-Factor Authentication required', }, NETWORK: { METHOD: { diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts index 1796d06d74e3..fd15fdd3a34b 100644 --- a/src/libs/Middleware/Reauthentication.ts +++ b/src/libs/Middleware/Reauthentication.ts @@ -61,7 +61,13 @@ const Reauthentication: Middleware = (response, request, isFromSequentialQueue) return; } - if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { + // Two server-side signals indicate the user must set up 2FA: + // 1. Server throws PolicyRequires2FA error, with 666 jsonCode with the specefic error title or error type. + // 2. Server rejects a stale normal type token whose account now requires 2FA with jsonCode 432. + const is2FASetupRequired = + (data.jsonCode === CONST.JSON_CODE.EXP_ERROR && (data.type === CONST.ERROR_TYPE.NEEDS_2FA_SETUP || data.title === CONST.ERROR_TITLE.NEEDS_2FA_SETUP)) || + data.jsonCode === CONST.JSON_CODE.NEEDS_2FA_SETUP; + if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED || is2FASetupRequired) { if (getIsOffline()) { // If we are offline and somehow handling this response we do not want to reauthenticate throw new Error('Unable to reauthenticate because we are offline'); @@ -100,6 +106,18 @@ const Reauthentication: Middleware = (response, request, isFromSequentialQueue) return; } + // Replaying the original request after successful re-authentication might hit the same server-side 2FA check and loop indefinitely. So we skip it. + // The re-reauth itself refreshes `account.needsTwoFactorAuthSetup` in Onyx via updates sent by server, which is enough to show the 2FA enforcement overlay. + if (is2FASetupRequired) { + if (isFromSequentialQueue) { + return data; + } + if (request.resolve) { + request.resolve(data); + } + return data; + } + if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { return processWithMiddleware(request, isFromSequentialQueue); }