General source code refactoring
This commit is contained in:
153
src/auth/token-relay.js
Normal file
153
src/auth/token-relay.js
Normal file
@@ -0,0 +1,153 @@
|
||||
function createAuthTokenRelay({
|
||||
TOKEN_RELAY_URL,
|
||||
createDebugBody,
|
||||
getXsrfToken,
|
||||
logRequestFailure,
|
||||
logRequestStart,
|
||||
logRequestSuccess,
|
||||
maskToken,
|
||||
readResponseBodyForDebug,
|
||||
refreshXsrfCookie,
|
||||
renderDebugPanel,
|
||||
environment
|
||||
}) {
|
||||
const { fetch } = environment;
|
||||
async function requestTokenRelay({ name = "requestTokenRelay", url = TOKEN_RELAY_URL, includeExpiry = false } = {}) {
|
||||
let refreshResult = null;
|
||||
let refreshAttempted = false;
|
||||
|
||||
while (true) {
|
||||
const xsrfTokenSource = await getXsrfToken();
|
||||
|
||||
if (!xsrfTokenSource.token) {
|
||||
if (refreshAttempted) {
|
||||
const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url);
|
||||
logRequestFailure(requestEntry, "XSRF token cookie not found after automatic refresh.");
|
||||
throw new Error("XSRF token cookie not found after automatic refresh.");
|
||||
}
|
||||
|
||||
refreshResult = await refreshXsrfCookie("missing-cookie");
|
||||
refreshAttempted = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-xsrf-token": xsrfTokenSource.token
|
||||
}
|
||||
});
|
||||
|
||||
const responseBody = await readResponseBodyForDebug(response);
|
||||
|
||||
if (!response.ok) {
|
||||
logRequestSuccess(requestEntry, response, responseBody);
|
||||
|
||||
if (!refreshAttempted && isInvalidXsrfStatus(response.status)) {
|
||||
refreshResult = await refreshXsrfCookie(`tokenrelay-${response.status}`);
|
||||
refreshAttempted = true;
|
||||
requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult);
|
||||
renderDebugPanel();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Token relay failed with status ${response.status}.`);
|
||||
}
|
||||
|
||||
let responseData;
|
||||
|
||||
try {
|
||||
responseData = await response.json();
|
||||
} catch (error) {
|
||||
logRequestSuccess(requestEntry, response, responseBody);
|
||||
|
||||
if (!refreshAttempted) {
|
||||
refreshResult = await refreshXsrfCookie("tokenrelay-invalid-response");
|
||||
refreshAttempted = true;
|
||||
requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult);
|
||||
renderDebugPanel();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!responseData || typeof responseData.access_token !== "string" || !responseData.access_token) {
|
||||
logRequestSuccess(requestEntry, response, responseBody);
|
||||
|
||||
if (!refreshAttempted) {
|
||||
refreshResult = await refreshXsrfCookie("tokenrelay-missing-access-token");
|
||||
refreshAttempted = true;
|
||||
requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult);
|
||||
renderDebugPanel();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error("Token relay response does not contain access_token.");
|
||||
}
|
||||
|
||||
logRequestSuccess(requestEntry, response, createDebugBody(JSON.stringify({
|
||||
...responseData,
|
||||
access_token: maskToken(responseData.access_token)
|
||||
}, null, 2)));
|
||||
|
||||
if (includeExpiry) {
|
||||
const lifetime = Number(responseData.expires_in);
|
||||
return {
|
||||
access_token: responseData.access_token,
|
||||
expiresAt: Number.isFinite(lifetime) && lifetime > 0
|
||||
? requestEntry.startedAt.getTime() + lifetime * 1000 : 0
|
||||
};
|
||||
}
|
||||
return responseData.access_token;
|
||||
} catch (error) {
|
||||
logRequestFailure(requestEntry, error.message || "Request failed.");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name = "requestTokenRelay", url = TOKEN_RELAY_URL) {
|
||||
return logRequestStart({
|
||||
name,
|
||||
method: "GET",
|
||||
url,
|
||||
headers: {
|
||||
"x-xsrf-token": xsrfTokenSource.token ? maskToken(xsrfTokenSource.token) : "(missing)"
|
||||
},
|
||||
metadata: {
|
||||
xsrfCookieName: xsrfTokenSource.cookieName || "(missing)",
|
||||
xsrfTokenSource: xsrfTokenSource.source,
|
||||
xsrfMatchedCookies: xsrfTokenSource.matchedCookieNames && xsrfTokenSource.matchedCookieNames.length
|
||||
? xsrfTokenSource.matchedCookieNames.join(", ")
|
||||
: "(none)",
|
||||
xsrfLookupDetails: xsrfTokenSource.lookupDetails && xsrfTokenSource.lookupDetails.length
|
||||
? xsrfTokenSource.lookupDetails.join(" | ")
|
||||
: "(none)",
|
||||
xsrfTokenError: xsrfTokenSource.error || "",
|
||||
xsrfRefresh: formatXsrfRefreshResult(refreshResult)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isInvalidXsrfStatus(status) {
|
||||
return status === 400 || status === 401 || status === 403;
|
||||
}
|
||||
|
||||
function formatXsrfRefreshResult(result) {
|
||||
if (!result) {
|
||||
return "not-requested";
|
||||
}
|
||||
|
||||
const outcome = result.refreshed ? "refreshed" : "not-refreshed";
|
||||
const cookieName = result.cookieName || "(none)";
|
||||
const duration = Number.isFinite(result.durationMs) ? `${result.durationMs}ms` : "unknown";
|
||||
return `${result.reason}; ${outcome}; cookie=${cookieName}; duration=${duration}; ${result.error || ""}`.trim();
|
||||
}
|
||||
|
||||
return { requestTokenRelay, createTokenRelayRequestEntry, isInvalidXsrfStatus, formatXsrfRefreshResult };
|
||||
}
|
||||
Reference in New Issue
Block a user