supertokens-website
Version:
frontend sdk for website to be used for auth solution.
951 lines • 77.4 kB
JavaScript
"use strict";
var __assign =
(this && this.__assign) ||
function () {
__assign =
Object.assign ||
function (t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter =
(this && this.__awaiter) ||
function (thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P
? value
: new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator =
(this && this.__generator) ||
function (thisArg, body) {
var _ = {
label: 0,
sent: function () {
if (t[0] & 1) throw t[1];
return t[1];
},
trys: [],
ops: []
},
f,
y,
t,
g;
return (
(g = { next: verb(0), throw: verb(1), return: verb(2) }),
typeof Symbol === "function" &&
(g[Symbol.iterator] = function () {
return this;
}),
g
);
function verb(n) {
return function (v) {
return step([n, v]);
};
}
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_)
try {
if (
((f = 1),
y &&
(t =
op[0] & 2
? y["return"]
: op[0]
? y["throw"] || ((t = y["return"]) && t.call(y), 0)
: y.next) &&
!(t = t.call(y, op[1])).done)
)
return t;
if (((y = 0), t)) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return { value: op[1], done: false };
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (
!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
(op[0] === 6 || op[0] === 2)
) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2]) _.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
} catch (e) {
op = [6, e];
y = 0;
} finally {
f = t = 0;
}
if (op[0] & 5) throw op[1];
return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateClockSkewUsingFrontToken =
exports.fireSessionUpdateEventsIfNecessary =
exports.setFrontToken =
exports.getFrontToken =
exports.setAntiCSRF =
exports.saveLastAccessTokenUpdate =
exports.getTokenForHeaderAuth =
exports.setToken =
exports.getStorageNameForToken =
exports.getLocalSessionState =
exports.onInvalidClaimResponse =
exports.onTokenUpdate =
exports.onUnauthorisedResponse =
exports.FrontToken =
exports.AntiCsrfToken =
void 0;
/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
var processState_1 = require("./processState");
var version_1 = require("./version");
var cookieHandler_1 = require("./utils/cookieHandler");
var windowHandler_1 = require("./utils/windowHandler");
var lockFactory_1 = require("./utils/lockFactory");
var logger_1 = require("./logger");
var dateProvider_1 = require("./utils/dateProvider");
var AntiCsrfToken = /** @class */ (function () {
function AntiCsrfToken() {}
AntiCsrfToken.getToken = function (associatedAccessTokenUpdate) {
return __awaiter(this, void 0, void 0, function () {
var antiCsrf;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, logger_1.logDebugMessage)("AntiCsrfToken.getToken: called");
if (associatedAccessTokenUpdate === undefined) {
AntiCsrfToken.tokenInfo = undefined;
(0, logger_1.logDebugMessage)("AntiCsrfToken.getToken: returning undefined");
return [2 /*return*/, undefined];
}
if (!(AntiCsrfToken.tokenInfo === undefined)) return [3 /*break*/, 2];
return [4 /*yield*/, getAntiCSRFToken()];
case 1:
antiCsrf = _a.sent();
if (antiCsrf === null) {
(0, logger_1.logDebugMessage)("AntiCsrfToken.getToken: returning undefined");
return [2 /*return*/, undefined];
}
AntiCsrfToken.tokenInfo = {
antiCsrf: antiCsrf,
associatedAccessTokenUpdate: associatedAccessTokenUpdate
};
return [3 /*break*/, 4];
case 2:
if (!(AntiCsrfToken.tokenInfo.associatedAccessTokenUpdate !== associatedAccessTokenUpdate))
return [3 /*break*/, 4];
// csrf token has changed.
AntiCsrfToken.tokenInfo = undefined;
return [4 /*yield*/, AntiCsrfToken.getToken(associatedAccessTokenUpdate)];
case 3:
return [2 /*return*/, _a.sent()];
case 4:
(0,
logger_1.logDebugMessage)("AntiCsrfToken.getToken: returning: " + AntiCsrfToken.tokenInfo.antiCsrf);
return [2 /*return*/, AntiCsrfToken.tokenInfo.antiCsrf];
}
});
});
};
AntiCsrfToken.removeToken = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, logger_1.logDebugMessage)("AntiCsrfToken.removeToken: called");
AntiCsrfToken.tokenInfo = undefined;
return [4 /*yield*/, setAntiCSRF(undefined)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
AntiCsrfToken.setItem = function (associatedAccessTokenUpdate, antiCsrf) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (associatedAccessTokenUpdate === undefined) {
AntiCsrfToken.tokenInfo = undefined;
return [2 /*return*/];
}
(0, logger_1.logDebugMessage)("AntiCsrfToken.setItem: called");
return [4 /*yield*/, setAntiCSRF(antiCsrf)];
case 1:
_a.sent();
AntiCsrfToken.tokenInfo = {
antiCsrf: antiCsrf,
associatedAccessTokenUpdate: associatedAccessTokenUpdate
};
return [2 /*return*/];
}
});
});
};
return AntiCsrfToken;
})();
exports.AntiCsrfToken = AntiCsrfToken;
// Note: We do not store this in memory because another tab may have
// modified this value, and if so, we may not know about it in this tab
var FrontToken = /** @class */ (function () {
function FrontToken() {}
FrontToken.getTokenInfo = function () {
return __awaiter(this, void 0, void 0, function () {
var frontToken, response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, logger_1.logDebugMessage)("FrontToken.getTokenInfo: called");
return [4 /*yield*/, getFrontToken()];
case 1:
frontToken = _a.sent();
if (!(frontToken === null)) return [3 /*break*/, 5];
return [4 /*yield*/, getLocalSessionState(false)];
case 2:
if (!(_a.sent().status === "EXISTS")) return [3 /*break*/, 4];
// this means that the id refresh token has been set, so we must
// wait for this to be set or removed
return [
4 /*yield*/,
new Promise(function (resolve) {
FrontToken.waiters.push(resolve);
})
];
case 3:
// this means that the id refresh token has been set, so we must
// wait for this to be set or removed
_a.sent();
return [2 /*return*/, FrontToken.getTokenInfo()];
case 4:
return [2 /*return*/, undefined];
case 5:
response = parseFrontToken(frontToken);
(0, logger_1.logDebugMessage)("FrontToken.getTokenInfo: returning ate: " + response.ate);
(0, logger_1.logDebugMessage)("FrontToken.getTokenInfo: returning uid: " + response.uid);
(0, logger_1.logDebugMessage)("FrontToken.getTokenInfo: returning up: " + response.up);
return [2 /*return*/, response];
}
});
});
};
FrontToken.removeToken = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, logger_1.logDebugMessage)("FrontToken.removeToken: called");
return [4 /*yield*/, setFrontToken(undefined)];
case 1:
_a.sent();
// We are clearing all stored tokens here, because:
// 1. removing FrontToken signals that the session is being cleared
// 2. you can only have a single active session - this means that all tokens can be cleared from all auth-modes if one is being cleared
// 3. some proxies remove the empty headers used to clear the other tokens (i.e.: https://github.com/supertokens/supertokens-website/issues/218)
return [4 /*yield*/, setToken("access", "")];
case 2:
// We are clearing all stored tokens here, because:
// 1. removing FrontToken signals that the session is being cleared
// 2. you can only have a single active session - this means that all tokens can be cleared from all auth-modes if one is being cleared
// 3. some proxies remove the empty headers used to clear the other tokens (i.e.: https://github.com/supertokens/supertokens-website/issues/218)
_a.sent();
return [4 /*yield*/, setToken("refresh", "")];
case 3:
_a.sent();
return [4 /*yield*/, AntiCsrfToken.removeToken()];
case 4:
_a.sent();
FrontToken.waiters.forEach(function (f) {
return f(undefined);
});
FrontToken.waiters = [];
return [2 /*return*/];
}
});
});
};
FrontToken.setItem = function (frontToken) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// We update the refresh attempt info here as well, since this means that we've updated the session in some way
// This could be both by a refresh call or if the access token was updated in a custom endpoint
// By saving every time the access token has been updated, we cause an early retry if
// another request has failed with a 401 with the previous access token and the token still exists.
// Check the start and end of onUnauthorisedResponse
// As a side-effect we reload the anti-csrf token to check if it was changed by another tab.
return [4 /*yield*/, saveLastAccessTokenUpdate()];
case 1:
// We update the refresh attempt info here as well, since this means that we've updated the session in some way
// This could be both by a refresh call or if the access token was updated in a custom endpoint
// By saving every time the access token has been updated, we cause an early retry if
// another request has failed with a 401 with the previous access token and the token still exists.
// Check the start and end of onUnauthorisedResponse
// As a side-effect we reload the anti-csrf token to check if it was changed by another tab.
_a.sent();
if (frontToken === "remove") {
return [2 /*return*/, FrontToken.removeToken()];
}
(0, logger_1.logDebugMessage)("FrontToken.setItem: called");
return [4 /*yield*/, setFrontToken(frontToken)];
case 2:
_a.sent();
FrontToken.waiters.forEach(function (f) {
return f(undefined);
});
FrontToken.waiters = [];
return [2 /*return*/];
}
});
});
};
FrontToken.doesTokenExists = function () {
return __awaiter(this, void 0, void 0, function () {
var frontToken;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
return [4 /*yield*/, getFrontTokenFromCookie()];
case 1:
frontToken = _a.sent();
return [2 /*return*/, frontToken !== null];
}
});
});
};
// these are waiters for when the idRefreshToken has been set, but this token has
// not yet been set. Once this token is set or removed, the waiters are resolved.
FrontToken.waiters = [];
return FrontToken;
})();
exports.FrontToken = FrontToken;
/**
* @class AuthHttpRequest
* @description wrapper for common http methods.
*/
var AuthHttpRequest = /** @class */ (function () {
function AuthHttpRequest() {}
AuthHttpRequest.init = function (config, recipeImpl) {
(0, logger_1.logDebugMessage)("init: called");
(0, logger_1.logDebugMessage)("init: Input apiBasePath: " + config.apiBasePath);
(0, logger_1.logDebugMessage)("init: Input apiDomain: " + config.apiDomain);
(0, logger_1.logDebugMessage)("init: Input autoAddCredentials: " + config.autoAddCredentials);
(0, logger_1.logDebugMessage)("init: Input sessionTokenBackendDomain: " + config.sessionTokenBackendDomain);
(0, logger_1.logDebugMessage)("init: Input isInIframe: " + config.isInIframe);
(0, logger_1.logDebugMessage)("init: Input sessionExpiredStatusCode: " + config.sessionExpiredStatusCode);
(0, logger_1.logDebugMessage)("init: Input sessionTokenFrontendDomain: " + config.sessionTokenFrontendDomain);
(0, logger_1.logDebugMessage)("init: Input tokenTransferMethod: " + config.tokenTransferMethod);
var fetchedWindow = windowHandler_1.default.getReferenceOrThrow().windowHandler.getWindowUnsafe();
AuthHttpRequest.env = fetchedWindow === undefined || fetchedWindow.fetch === undefined ? global : fetchedWindow;
AuthHttpRequest.refreshTokenUrl = config.apiDomain + config.apiBasePath + "/session/refresh";
AuthHttpRequest.signOutUrl = config.apiDomain + config.apiBasePath + "/signout";
AuthHttpRequest.rid = "session";
AuthHttpRequest.config = config;
if (AuthHttpRequest.env.__supertokensOriginalFetch === undefined) {
(0, logger_1.logDebugMessage)("init: __supertokensOriginalFetch is undefined");
// this block contains code that is run just once per page load..
// all items in this block are attached to the global env so that
// even if the init function is called more than once (maybe across JS scripts),
// things will not get created multiple times.
AuthHttpRequest.env.__supertokensOriginalFetch = AuthHttpRequest.env.fetch.bind(AuthHttpRequest.env);
AuthHttpRequest.env.__supertokensSessionRecipe = recipeImpl;
AuthHttpRequest.env.fetch =
AuthHttpRequest.env.__supertokensSessionRecipe.addFetchInterceptorsAndReturnModifiedFetch({
originalFetch: AuthHttpRequest.env.__supertokensOriginalFetch,
userContext: {}
});
AuthHttpRequest.env.__supertokensSessionRecipe.addXMLHttpRequestInterceptor({
userContext: {}
});
}
AuthHttpRequest.recipeImpl = AuthHttpRequest.env.__supertokensSessionRecipe;
AuthHttpRequest.initCalled = true;
};
var _a;
_a = AuthHttpRequest;
AuthHttpRequest.initCalled = false;
AuthHttpRequest.doRequest = function (httpCall, config, url) {
return __awaiter(void 0, void 0, void 0, function () {
var doNotDoInterception,
finalURL,
origHeaders,
accessToken,
refreshToken,
sessionRefreshAttempts,
returnObj,
preRequestLSS,
clonedHeaders,
configWithAntiCsrf,
antiCsrfToken,
transferMethod,
response,
errorMessage,
retry;
return __generator(_a, function (_b) {
switch (_b.label) {
case 0:
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
(0, logger_1.logDebugMessage)("doRequest: start of fetch interception");
doNotDoInterception = false;
try {
finalURL = void 0;
if (typeof url === "string") {
finalURL = url;
} else if (typeof url === "object") {
if (typeof url.url === "string") {
finalURL = url.url;
} else if (typeof url.href === "string") {
finalURL = url.href;
}
}
doNotDoInterception = !AuthHttpRequest.recipeImpl.shouldDoInterceptionBasedOnUrl(
finalURL,
AuthHttpRequest.config.apiDomain,
AuthHttpRequest.config.sessionTokenBackendDomain
);
} catch (err) {
if (err.message === "Please provide a valid domain name") {
(0, logger_1.logDebugMessage)(
"doRequest: Trying shouldDoInterceptionBasedOnUrl with location.origin"
);
// .origin gives the port as well..
doNotDoInterception = !AuthHttpRequest.recipeImpl.shouldDoInterceptionBasedOnUrl(
windowHandler_1.default.getReferenceOrThrow().windowHandler.location.getOrigin(),
AuthHttpRequest.config.apiDomain,
AuthHttpRequest.config.sessionTokenBackendDomain
);
} else {
throw err;
}
}
(0,
logger_1.logDebugMessage)("doRequest: Value of doNotDoInterception: " + doNotDoInterception);
if (!doNotDoInterception) return [3 /*break*/, 2];
(0, logger_1.logDebugMessage)("doRequest: Returning without interception");
return [4 /*yield*/, httpCall(config)];
case 1:
return [2 /*return*/, _b.sent()];
case 2:
origHeaders = new Headers(
config !== undefined && config.headers !== undefined ? config.headers : url.headers
);
if (!origHeaders.has("Authorization")) return [3 /*break*/, 5];
return [4 /*yield*/, getTokenForHeaderAuth("access")];
case 3:
accessToken = _b.sent();
return [4 /*yield*/, getTokenForHeaderAuth("refresh")];
case 4:
refreshToken = _b.sent();
if (
accessToken !== undefined &&
refreshToken !== undefined &&
origHeaders.get("Authorization") === "Bearer ".concat(accessToken)
) {
// We are ignoring the Authorization header set by the user in this case, because it would cause issues
// If we do not ignore this, then this header would be used even if the request is being retried after a refresh, even though it contains an outdated access token.
// This causes an infinite refresh loop.
(0, logger_1.logDebugMessage)(
"doRequest: Removing Authorization from user provided headers because it contains our access token"
);
origHeaders.delete("Authorization");
}
_b.label = 5;
case 5:
(0, logger_1.logDebugMessage)("doRequest: Interception started");
processState_1.ProcessState.getInstance().addState(
processState_1.PROCESS_STATE.CALLING_INTERCEPTION_REQUEST
);
sessionRefreshAttempts = 0;
returnObj = undefined;
_b.label = 6;
case 6:
if (!true) return [3 /*break*/, 18];
return [4 /*yield*/, getLocalSessionState(true)];
case 7:
preRequestLSS = _b.sent();
clonedHeaders = new Headers(origHeaders);
configWithAntiCsrf = __assign(__assign({}, config), { headers: clonedHeaders });
if (!(preRequestLSS.status === "EXISTS")) return [3 /*break*/, 9];
return [4 /*yield*/, AntiCsrfToken.getToken(preRequestLSS.lastAccessTokenUpdate)];
case 8:
antiCsrfToken = _b.sent();
if (antiCsrfToken !== undefined) {
(0, logger_1.logDebugMessage)("doRequest: Adding anti-csrf token to request");
clonedHeaders.set("anti-csrf", antiCsrfToken);
}
_b.label = 9;
case 9:
if (AuthHttpRequest.config.autoAddCredentials) {
(0, logger_1.logDebugMessage)("doRequest: Adding credentials include");
if (configWithAntiCsrf === undefined) {
configWithAntiCsrf = {
credentials: "include"
};
} else if (configWithAntiCsrf.credentials === undefined) {
configWithAntiCsrf = __assign(__assign({}, configWithAntiCsrf), {
credentials: "include"
});
}
}
// adding rid for anti-csrf protection: Anti-csrf via custom header
if (!clonedHeaders.has("rid")) {
(0, logger_1.logDebugMessage)("doRequest: Adding rid header: anti-csrf");
clonedHeaders.set("rid", "anti-csrf");
} else {
(0, logger_1.logDebugMessage)("doRequest: rid header was already there in request");
}
transferMethod = AuthHttpRequest.config.tokenTransferMethod;
(0, logger_1.logDebugMessage)("doRequest: Adding st-auth-mode header: " + transferMethod);
clonedHeaders.set("st-auth-mode", transferMethod);
return [4 /*yield*/, setAuthorizationHeaderIfRequired(clonedHeaders)];
case 10:
_b.sent();
(0, logger_1.logDebugMessage)("doRequest: Making user's http call");
return [4 /*yield*/, httpCall(configWithAntiCsrf)];
case 11:
response = _b.sent();
(0, logger_1.logDebugMessage)("doRequest: User's http call ended");
return [4 /*yield*/, saveTokensFromHeaders(response)];
case 12:
_b.sent();
fireSessionUpdateEventsIfNecessary(
preRequestLSS.status === "EXISTS",
response.status,
response.headers.get("front-token")
);
if (!(response.status === AuthHttpRequest.config.sessionExpiredStatusCode))
return [3 /*break*/, 14];
(0, logger_1.logDebugMessage)("doRequest: Status code is: " + response.status);
/**
* An API may return a 401 error response even with a valid session, causing a session refresh loop in the interceptor.
* To prevent this infinite loop, we break out of the loop after retrying the original request a specified number of times.
* The maximum number of retry attempts is defined by maxRetryAttemptsForSessionRefresh config variable.
*/
if (sessionRefreshAttempts >= AuthHttpRequest.config.maxRetryAttemptsForSessionRefresh) {
(0, logger_1.logDebugMessage)(
"doRequest: Maximum session refresh attempts reached. sessionRefreshAttempts: "
.concat(sessionRefreshAttempts, ", maxRetryAttemptsForSessionRefresh: ")
.concat(AuthHttpRequest.config.maxRetryAttemptsForSessionRefresh)
);
errorMessage = "Received a 401 response from "
.concat(
url,
". Attempted to refresh the session and retry the request with the updated session tokens "
)
.concat(
AuthHttpRequest.config.maxRetryAttemptsForSessionRefresh,
" times, but each attempt resulted in a 401 error. The maximum session refresh limit has been reached. Please investigate your API. To increase the session refresh attempts, update maxRetryAttemptsForSessionRefresh in the config."
);
console.error(errorMessage);
throw new Error(errorMessage);
}
return [4 /*yield*/, onUnauthorisedResponse(preRequestLSS)];
case 13:
retry = _b.sent();
sessionRefreshAttempts++;
(0, logger_1.logDebugMessage)("doRequest: sessionRefreshAttempts: " + sessionRefreshAttempts);
if (retry.result !== "RETRY") {
(0, logger_1.logDebugMessage)("doRequest: Not retrying original request");
if (retry.error !== undefined) {
if (retry.error instanceof Response) {
returnObj = retry.error;
} else {
throw retry.error;
}
} else {
returnObj = response;
}
return [3 /*break*/, 18];
}
(0, logger_1.logDebugMessage)("doRequest: Retrying original request");
return [3 /*break*/, 17];
case 14:
if (!(response.status === AuthHttpRequest.config.invalidClaimStatusCode))
return [3 /*break*/, 16];
return [4 /*yield*/, onInvalidClaimResponse(response)];
case 15:
_b.sent();
_b.label = 16;
case 16:
return [2 /*return*/, response];
case 17:
return [3 /*break*/, 6];
case 18:
// if it comes here, means we breaked. which happens only if we have logged out.
return [2 /*return*/, returnObj];
}
});
});
};
AuthHttpRequest.attemptRefreshingSession = function () {
return __awaiter(void 0, void 0, void 0, function () {
var preRequestLSS, refresh;
return __generator(_a, function (_b) {
switch (_b.label) {
case 0:
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
return [4 /*yield*/, getLocalSessionState(false)];
case 1:
preRequestLSS = _b.sent();
return [4 /*yield*/, onUnauthorisedResponse(preRequestLSS)];
case 2:
refresh = _b.sent();
if (refresh.result === "API_ERROR") {
throw refresh.error;
}
return [2 /*return*/, refresh.result === "RETRY"];
}
});
});
};
return AuthHttpRequest;
})();
exports.default = AuthHttpRequest;
var LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update";
var REFRESH_TOKEN_NAME = "st-refresh-token";
var ACCESS_TOKEN_NAME = "st-access-token";
var ANTI_CSRF_NAME = "sAntiCsrf";
var FRONT_TOKEN_NAME = "sFrontToken";
/**
* @description attempts to call the refresh token API each time we are sure the session has expired, or it throws an error or,
* or the ID_COOKIE_NAME has changed value -> which may mean that we have a new set of tokens.
*/
function onUnauthorisedResponse(preRequestLSS) {
return __awaiter(this, void 0, void 0, function () {
var lock,
postLockLSS,
postLockSessionExists,
preRequestSessionExists,
sessionStatusChanged,
accessTokenTimestampChanged,
headers,
antiCsrfToken,
transferMethod,
preAPIResult,
response,
isUnauthorised,
errorMessage,
error_1,
postRequestLSS;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
return [4 /*yield*/, lockFactory_1.default.getReferenceOrThrow().lockFactory()];
case 1:
lock = _b.sent();
_b.label = 2;
case 2:
if (!true) return [3 /*break*/, 23];
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: trying to acquire lock");
return [4 /*yield*/, lock.acquireLock("REFRESH_TOKEN_USE", 1000)];
case 3:
if (!_b.sent()) return [3 /*break*/, 21];
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: lock acquired");
_b.label = 4;
case 4:
_b.trys.push([4, 17, 19, 21]);
return [4 /*yield*/, getLocalSessionState(false)];
case 5:
postLockLSS = _b.sent();
if (postLockLSS.status === "NOT_EXISTS") {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: Not refreshing because local session state is NOT_EXISTS"
);
// if it comes here, it means a request was made thinking
// that the session exists, but it doesn't actually exist.
AuthHttpRequest.config.onHandleEvent({
action: "UNAUTHORISED",
sessionExpiredOrRevoked: false,
userContext: {}
});
return [2 /*return*/, { result: "SESSION_EXPIRED" }];
}
postLockSessionExists = postLockLSS.status === "EXISTS";
preRequestSessionExists = preRequestLSS.status === "EXISTS";
sessionStatusChanged = postLockLSS.status !== preRequestLSS.status;
accessTokenTimestampChanged =
"lastAccessTokenUpdate" in postLockLSS &&
"lastAccessTokenUpdate" in preRequestLSS &&
postLockLSS.lastAccessTokenUpdate !== preRequestLSS.lastAccessTokenUpdate;
// If the session status has changed, we should return early and retry the request
// only if postLockLSS.status is "EXISTS".
if (sessionStatusChanged && postLockSessionExists) {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: Retrying early because session status has changed and postLockLSS.status is EXISTS"
);
return [2 /*return*/, { result: "RETRY" }];
}
// If the session exists in both postLockLSS and preRequestLSS, we should return early
// and retry the request only if the access token timestamp has changed.
// This indicates that another process has already called this API and succeeded,
// so we don't need to call it again.
if (postLockSessionExists && preRequestSessionExists && accessTokenTimestampChanged) {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: Retrying early because pre and post lastAccessTokenUpdate don't match"
);
return [2 /*return*/, { result: "RETRY" }];
}
headers = new Headers();
if (!(preRequestLSS.status === "EXISTS")) return [3 /*break*/, 7];
return [4 /*yield*/, AntiCsrfToken.getToken(preRequestLSS.lastAccessTokenUpdate)];
case 6:
antiCsrfToken = _b.sent();
if (antiCsrfToken !== undefined) {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: Adding anti-csrf token to refresh API call"
);
headers.set("anti-csrf", antiCsrfToken);
}
_b.label = 7;
case 7:
(0,
logger_1.logDebugMessage)("onUnauthorisedResponse: Adding rid and fdi-versions to refresh call header");
headers.set("rid", AuthHttpRequest.rid);
headers.set("fdi-version", version_1.supported_fdi.join(","));
transferMethod = AuthHttpRequest.config.tokenTransferMethod;
(0,
logger_1.logDebugMessage)("onUnauthorisedResponse: Adding st-auth-mode header: " + transferMethod);
headers.set("st-auth-mode", transferMethod);
return [4 /*yield*/, setAuthorizationHeaderIfRequired(headers, true)];
case 8:
_b.sent();
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Calling refresh pre API hook");
return [
4 /*yield*/,
AuthHttpRequest.config.preAPIHook({
action: "REFRESH_SESSION",
requestInit: {
method: "post",
credentials: "include",
headers: headers
},
url: AuthHttpRequest.refreshTokenUrl,
userContext: {}
})
];
case 9:
preAPIResult = _b.sent();
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Making refresh call");
return [
4 /*yield*/,
AuthHttpRequest.env.__supertokensOriginalFetch(preAPIResult.url, preAPIResult.requestInit)
];
case 10:
response = _b.sent();
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Refresh call ended");
return [4 /*yield*/, saveTokensFromHeaders(response)];
case 11:
_b.sent();
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Refresh status code is: " + response.status);
isUnauthorised = response.status === AuthHttpRequest.config.sessionExpiredStatusCode;
if (!(response.headers.get("front-token") === null)) return [3 /*break*/, 14];
if (!isUnauthorised) return [3 /*break*/, 13];
// 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
return [4 /*yield*/, FrontToken.setItem("remove")];
case 12:
// 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
_b.sent();
return [3 /*break*/, 14];
case 13:
if (response.status === 200) {
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);
}
_b.label = 14;
case 14:
fireSessionUpdateEventsIfNecessary(
preRequestLSS.status === "EXISTS",
response.status,
isUnauthorised && response.headers.get("front-token") === null
? "remove"
: response.headers.get("front-token")
);
if (response.status >= 300) {
throw response;
}
return [
4 /*yield*/,
AuthHttpRequest.config.postAPIHook({
action: "REFRESH_SESSION",
fetchResponse: response.clone(),
requestInit: preAPIResult.requestInit,
url: preAPIResult.url,
userContext: {}
})
];
case 15:
_b.sent();
return [4 /*yield*/, getLocalSessionState(false)];
case 16:
if (_b.sent().status === "NOT_EXISTS") {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: local session doesn't exist, so returning session expired"
);
// The execution should never come here.. but just in case.
// removed by server during refresh. So we logout
// we do not send "UNAUTHORISED" event here because
// this is a result of the refresh API returning a session expiry, which
// means that the frontend did not know for sure that the session existed
// in the first place.
return [2 /*return*/, { result: "SESSION_EXPIRED" }];
}
AuthHttpRequest.config.onHandleEvent({
action: "REFRESH_SESSION",
userContext: {}
});
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Sending RETRY signal");
return [2 /*return*/, { result: "RETRY" }];
case 17:
error_1 = _b.sent();
return [4 /*yield*/, getLocalSessionState(false)];
case 18:
if (_b.sent().status === "NOT_EXISTS") {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: local session doesn't exist, so returning session expired"
);
// removed by server.
// we do not send "UNAUTHORISED" event here because
// this is a result of the refresh API returning a session expiry, which
// means that the frontend did not know for sure that the session existed
// in the first place.
return [2 /*return*/, { result: "SESSION_EXPIRED", error: error_1 }];
}
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: sending API_ERROR");
return [2 /*return*/, { result: "API_ERROR", error: error_1 }];
case 19:
return [4 /*yield*/, lock.releaseLock("REFRESH_TOKEN_USE")];
case 20:
_b.sent();
(0, logger_1.logDebugMessage)("onUnauthorisedResponse: Released lock");
return [7 /*endfinally*/];
case 21:
return [4 /*yield*/, getLocalSessionState(false)];
case 22:
postRequestLSS = _b.sent();
if (postRequestLSS.status === "NOT_EXISTS") {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: lock acquired failed and local session doesn't exist, so sending SESSION_EXPIRED"
);
// removed by server. So we logout
return [2 /*return*/, { result: "SESSION_EXPIRED" }];
} else {
if (
postRequestLSS.status !== preRequestLSS.status ||
(postRequestLSS.status === "EXISTS" &&
preRequestLSS.status === "EXISTS" &&
postRequestLSS.lastAccessTokenUpdate !== preRequestLSS.lastAccessTokenUpdate)
) {
(0, logger_1.logDebugMessage)(
"onUnauthorisedResponse: lock acquired failed and retrying early because pre and post lastAccessTokenUpdate don't match"
);
return [2 /*return*/, { result: "RETRY" }];
}
// here we try to call the API again since we probably failed to acquire lock and nothing has changed.
}
return [3 /*break*/, 2];
case 23:
return [2 /*return*/];
}
});
});
}
exports.onUnauthorisedResponse = onUnauthorisedResponse;
function onTokenUpdate() {
(0, logger_1.logDebugMessage)("onTokenUpdate: firing ACCESS_TOKEN_PAYLOAD_UPDATED event");
AuthHttpRequest.config.onHandleEvent({
action: "ACCESS_TOKEN_PAYLOAD_UPDATED",
userContext: {}
});
}
exports.onTokenUpdate = onTokenUpdate;
function onInvalidClaimResponse(response) {
return __awaiter(this, void 0, void 0, function () {
var