Skip to content

Commit

Permalink
feat: throw an error if the front-token header is missing on a succes…
Browse files Browse the repository at this point in the history
…sful refresh response
  • Loading branch information
porcellus committed Oct 4, 2024
1 parent 97c5a3e commit 3402cb2
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 28 deletions.
2 changes: 1 addition & 1 deletion bundle/bundle.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion lib/build/axios.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 37 additions & 15 deletions lib/build/fetch.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion lib/ts/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ export default class AuthHttpRequest {
logDebugMessage(
"doRequest: sessionRefreshAttempts: " + config.__supertokensSessionRefreshAttempts
);
console.log("!!!!", JSON.stringify(refreshResult));

if (refreshResult.result !== "RETRY") {
logDebugMessage("doRequest: Not retrying original request");
Expand Down
27 changes: 22 additions & 5 deletions lib/ts/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,15 @@ export default class AuthHttpRequest {

if (retry.result !== "RETRY") {
logDebugMessage("doRequest: Not retrying original request");
returnObj = retry.error !== undefined ? retry.error : response;
if (retry.error !== undefined) {
if (retry.error instanceof Response) {
returnObj = retry.error;
} else {
throw retry.error;
}
} else {
returnObj = response;
}
break;
}
logDebugMessage("doRequest: Retrying original request");
Expand Down Expand Up @@ -502,10 +510,19 @@ export async function onUnauthorisedResponse(

const isUnauthorised = response.status === AuthHttpRequest.config.sessionExpiredStatusCode;

// There is a case where the FE thinks the session is valid, but backend doesn't get the tokens.
// In this event, session expired error will be thrown and the frontend should remove this token
if (isUnauthorised && response.headers.get("front-token") === null) {
await FrontToken.setItem("remove");
if (response.headers.get("front-token") === null) {
if (isUnauthorised) {
// There is a case where the FE thinks the session is valid, but backend doesn't get the tokens.
// In this event, session expired error will be thrown and the frontend should remove this token
await FrontToken.setItem("remove");
} else if (response.status === 200) {
// For all 200 responses, the BE is supposed to set a front-token.
// If there was some issue with the header and the FE doesn't get the error, then we may go into refresh loops.
const errorMessage =
"The 'front-token' header is missing from a successful refresh-session response. The most likely causes are proxy settings (e.g.: 'front-token' missing from 'access-control-expose-headers' or a proxy stripping this header). Please investigate your API.";
console.error(errorMessage);
throw new Error(errorMessage);
}
}

fireSessionUpdateEventsIfNecessary(
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 60 additions & 1 deletion test/cross.auto_refresh.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => {
let v3AccessTokenSupported;

function setup(config = {}) {
// page.on("console", c => console.log(c.text()));
page.on("console", c => console.log(c.text()));
return page.evaluate(
setupFunc,
{
Expand Down Expand Up @@ -664,6 +664,65 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => {
await page.setRequestInterception(false);
});

it("test refresh session with removed front-token header in the response", async function () {
await startST(3);
await setup();
const logs = [];
page.on("console", message => {
logs.push(message.text());
});
await page.evaluate(async () => {
const originalFetch = window.__supertokensOriginalFetch;
// We can't use normal request interception here, because we can't modify the response headers there
window.__supertokensOriginalFetch = async (...args) => {
/** @type {Response} */
const res = await originalFetch(...args);
if (res.url === `${BASE_URL}/auth/session/refresh`) {
const moddedHeaders = new Headers(Array.from(res.headers.entries()));
moddedHeaders.delete("front-token");
const text = await res.text();
return new Response(text, {
status: res.status,
statusText: res.statusText,
headers: moddedHeaders
});
}
return res;
};
const userId = "testing-supertokens-website";
const loginResponse = await toTest({
url: `${BASE_URL}/login`,
method: "post",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({ userId })
});

assert.strictEqual(loginResponse.statusCode, 200);
assert.strictEqual(loginResponse.responseText, userId);
//delay for 5 seconds for access token validity expiry\
await delay(5);

//check that the number of times the refreshAPI was called is 0
assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0);

try {
await toTest({ url: `${BASE_URL}/` });
} catch (err) {
// In some cases (angular), the response doesn't fully error out, it reports as a 401.
// in all other cases, we throw a proper error.
// We check that the root-cause is logged to the console.
}
});
assert(
logs.some(msg =>
msg.startsWith("The 'front-token' header is missing from a successful refresh-session response")
)
);
});

it("should break out of session refresh loop after default maxRetryAttemptsForSessionRefresh value", async function () {
await startST();
await setup();
Expand Down
7 changes: 4 additions & 3 deletions test/interception.testgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ module.exports.addGenericTestCases = function (getTestCases) {
}
const loaded = new Promise((res, rej) => {
request.onloadend = res;
request.onerror = rej;
request.ontimeout = rej;
request.onabort = rej;
request.onerror = ev => rej(ev.error ?? ev);
request.ontimeout = ev => rej(ev.error ?? ev);
request.onabort = ev => rej(ev.error ?? ev);
});
request.send(config.body);
await loaded;
Expand Down Expand Up @@ -388,6 +388,7 @@ module.exports.addGenericTestCases = function (getTestCases) {
// A client-side or network error occurred. Handle it accordingly.
throw error;
} else {
console.log("$$1$", error, typeof error, JSON.stringify(error));
// This mains we have a wrong error code, and this is about the same as resp above
resp = error;
}
Expand Down

0 comments on commit 3402cb2

Please sign in to comment.