Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: throw an error if the front-token header is missing on a successful refresh response #267

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changes

- Added `3.1` and `4.0` to the list of supported FDIs
- Now we throw and log an error if a successful refresh response doesn't have the `front-token` header. This used to break the session state.

## [20.1.4] - 2024-07-11

Expand Down
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.

59 changes: 59 additions & 0 deletions test/cross.auto_refresh.test.js
Original file line number Diff line number Diff line change
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
Loading