Skip to content

Commit

Permalink
Fix admin action retry method resulting in an infinite loop.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger committed Jan 16, 2025
1 parent 70d70a2 commit f0c562d
Showing 1 changed file with 47 additions and 61 deletions.
108 changes: 47 additions & 61 deletions web/packages/teleport/src/services/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,66 +142,18 @@ const api = {
customOptions: RequestInit,
mfaResponse?: MfaChallengeResponse
): Promise<any> {
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
);
},

/**
Expand Down Expand Up @@ -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
Expand All @@ -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,
});
},
};

Expand Down Expand Up @@ -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'
);
}
Expand Down

0 comments on commit f0c562d

Please sign in to comment.