From f0c562dfdbcc7bd9c3f533e1677c618847811608 Mon Sep 17 00:00:00 2001 From: joerger Date: Thu, 16 Jan 2025 11:08:57 -0800 Subject: [PATCH] Fix admin action retry method resulting in an infinite loop. --- web/packages/teleport/src/services/api/api.ts | 108 ++++++++---------- 1 file changed, 47 insertions(+), 61 deletions(-) diff --git a/web/packages/teleport/src/services/api/api.ts b/web/packages/teleport/src/services/api/api.ts index 9c75858a05e58..904f4d5ac93ae 100644 --- a/web/packages/teleport/src/services/api/api.ts +++ b/web/packages/teleport/src/services/api/api.ts @@ -142,66 +142,18 @@ const api = { customOptions: RequestInit, mfaResponse?: MfaChallengeResponse ): Promise { - const response = await api.fetch(url, customOptions, mfaResponse); - - let json; try { - json = await response.json(); + return await api.fetch(url, customOptions, mfaResponse); } catch (err) { - // error reading JSON - const message = response.ok - ? err.message - : `${response.status} - ${response.url}`; - throw new ApiError({ message, response, opts: { cause: err } }); - } - - if (response.ok) { - return json; - } - - /** This error can occur in the edge case where a role in the user's certificate was deleted during their session. */ - const isRoleNotFoundErr = isRoleNotFoundError(parseError(json)); - if (isRoleNotFoundErr) { - websession.logoutWithoutSlo({ - /* Don't remember location after login, since they may no longer have access to the page they were on. */ - rememberLocation: false, - /* Show "access changed" notice on login page. */ - withAccessChangedMessage: true, - }); - return; - } - - // Retry with MFA if we get an admin action missing MFA error. - const isAdminActionMfaError = isAdminActionRequiresMfaError( - parseError(json) - ); - const shouldRetry = isAdminActionMfaError && !mfaResponse; - if (!shouldRetry) { - throw new ApiError({ - message: parseError(json), - response, - proxyVersion: parseProxyVersion(json), - messages: json.messages, - }); - } - - let mfaResponseForRetry; - try { - const challenge = await auth.getMfaChallenge({ - scope: MfaChallengeScope.ADMIN_ACTION, - }); - mfaResponseForRetry = await auth.getMfaChallengeResponse(challenge); - } catch { - throw new Error( - 'Failed to fetch MFA challenge. Please connect a registered hardware key and try again. If you do not have a hardware key registered, you can add one from your account settings page.' - ); + // Retry with MFA if we get an admin action MFA error. + if (!mfaResponse && isAdminActionRequiresMfaError(err)) { + const challenge = await auth.getMfaChallenge({ + scope: MfaChallengeScope.ADMIN_ACTION, + }); + mfaResponse = await auth.getMfaChallengeResponse(challenge); + return await api.fetch(url, customOptions, mfaResponse); + } } - - return api.fetchJsonWithMfaAuthnRetry( - url, - customOptions, - mfaResponseForRetry - ); }, /** @@ -246,7 +198,7 @@ const api = { * @param mfaResponse if defined (eg: `fetchJsonWithMfaAuthnRetry`) * will add a custom MFA header field that will hold the mfaResponse. */ - fetch( + async fetch( url: string, customOptions: RequestInit = {}, mfaResponse?: MfaChallengeResponse @@ -272,7 +224,41 @@ const api = { } // native call - return fetch(url, options); + const response = await fetch(url, options); + + let json; + try { + json = await response.json(); + } catch (err) { + // error reading JSON + const message = response.ok + ? err.message + : `${response.status} - ${response.url}`; + throw new ApiError({ message, response, opts: { cause: err } }); + } + + if (response.ok) { + return json; + } + + /** This error can occur in the edge case where a role in the user's certificate was deleted during their session. */ + const isRoleNotFoundErr = isRoleNotFoundError(parseError(json)); + if (isRoleNotFoundErr) { + websession.logoutWithoutSlo({ + /* Don't remember location after login, since they may no longer have access to the page they were on. */ + rememberLocation: false, + /* Show "access changed" notice on login page. */ + withAccessChangedMessage: true, + }); + return; + } + + throw new ApiError({ + message: parseError(json), + response, + proxyVersion: parseProxyVersion(json), + messages: json.messages, + }); }, }; @@ -318,8 +304,8 @@ export function getHostName() { return location.hostname + (location.port ? ':' + location.port : ''); } -function isAdminActionRequiresMfaError(errMessage) { - return errMessage.includes( +function isAdminActionRequiresMfaError(err: Error) { + return err.message.includes( 'admin-level API request requires MFA verification' ); }