supertokens-node
Version:
NodeJS driver for SuperTokens core
240 lines (239 loc) • 10 kB
JavaScript
;
/* Copyright (c) 2021, 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const cookieAndHeaders_1 = require("./cookieAndHeaders");
const url_1 = require("url");
const error_1 = require("./error");
const middleware_1 = require("./middleware");
const constants_1 = require("./constants");
const normalisedURLPath_1 = require("../../normalisedURLPath");
const psl = require("psl");
const utils_1 = require("../../utils");
function normaliseSessionScopeOrThrowError(recipe, sessionScope) {
function helper(sessionScope) {
sessionScope = sessionScope.trim().toLowerCase();
// first we convert it to a URL so that we can use the URL class
if (sessionScope.startsWith(".")) {
sessionScope = sessionScope.substr(1);
}
if (!sessionScope.startsWith("http://") && !sessionScope.startsWith("https://")) {
sessionScope = "http://" + sessionScope;
}
try {
let urlObj = new url_1.URL(sessionScope);
sessionScope = urlObj.hostname;
// remove leading dot
if (sessionScope.startsWith(".")) {
sessionScope = sessionScope.substr(1);
}
return sessionScope;
} catch (err) {
throw new error_1.default(
{
type: error_1.default.GENERAL_ERROR,
payload: new Error("Please provide a valid sessionScope"),
},
recipe
);
}
}
let noDotNormalised = helper(sessionScope);
if (noDotNormalised === "localhost" || utils_1.isAnIpAddress(noDotNormalised)) {
return noDotNormalised;
}
if (sessionScope.startsWith(".")) {
return "." + noDotNormalised;
}
return noDotNormalised;
}
exports.normaliseSessionScopeOrThrowError = normaliseSessionScopeOrThrowError;
function getTopLevelDomainForSameSiteResolution(url, recipeInstance) {
let urlObj = new url_1.URL(url);
let hostname = urlObj.hostname;
if (hostname.startsWith("localhost") || hostname.startsWith("localhost.org") || utils_1.isAnIpAddress(hostname)) {
// we treat these as the same TLDs since we can use sameSite lax for all of them.
return "localhost";
}
let parsedURL = psl.parse(hostname);
if (parsedURL.domain === null) {
throw new error_1.default(
{
type: error_1.default.GENERAL_ERROR,
payload: new Error("Please make sure that the apiDomain and websiteDomain have correct values"),
},
recipeInstance
);
}
return parsedURL.domain;
}
exports.getTopLevelDomainForSameSiteResolution = getTopLevelDomainForSameSiteResolution;
function validateAndNormaliseUserInput(recipeInstance, appInfo, config) {
utils_1.validateTheStructureOfUserInput(config, types_1.InputSchema, "session recipe", recipeInstance);
let cookieDomain =
config === undefined || config.cookieDomain === undefined
? undefined
: normaliseSessionScopeOrThrowError(recipeInstance, config.cookieDomain);
let topLevelAPIDomain = getTopLevelDomainForSameSiteResolution(
appInfo.apiDomain.getAsStringDangerous(),
recipeInstance
);
let topLevelWebsiteDomain = getTopLevelDomainForSameSiteResolution(
appInfo.websiteDomain.getAsStringDangerous(),
recipeInstance
);
let cookieSameSite = topLevelAPIDomain !== topLevelWebsiteDomain ? "none" : "lax";
cookieSameSite =
config === undefined || config.cookieSameSite === undefined
? cookieSameSite
: normaliseSameSiteOrThrowError(recipeInstance, config.cookieSameSite);
let cookieSecure =
config === undefined || config.cookieSecure === undefined
? appInfo.apiDomain.getAsStringDangerous().startsWith("https")
: config.cookieSecure;
let sessionExpiredStatusCode =
config === undefined || config.sessionExpiredStatusCode === undefined ? 401 : config.sessionExpiredStatusCode;
let sessionRefreshFeature = {
disableDefaultImplementation: false,
};
if (
config !== undefined &&
config.sessionRefreshFeature !== undefined &&
config.sessionRefreshFeature.disableDefaultImplementation !== undefined
) {
sessionRefreshFeature.disableDefaultImplementation = config.sessionRefreshFeature.disableDefaultImplementation;
}
let signOutFeature = {
disableDefaultImplementation: false,
};
if (
config !== undefined &&
config.signOutFeature !== undefined &&
config.signOutFeature.disableDefaultImplementation !== undefined
) {
signOutFeature.disableDefaultImplementation = config.signOutFeature.disableDefaultImplementation;
}
let enableAntiCsrf =
config === undefined || config.enableAntiCsrf === undefined ? cookieSameSite === "none" : config.enableAntiCsrf;
let errorHandlers = {
onTokenTheftDetected: (sessionHandle, userId, request, response, next) => {
return middleware_1.sendTokenTheftDetectedResponse(
recipeInstance,
sessionHandle,
userId,
request,
response,
next
);
},
onTryRefreshToken: (message, request, response, next) => {
return middleware_1.sendTryRefreshTokenResponse(recipeInstance, message, request, response, next);
},
onUnauthorised: (message, request, response, next) => {
return middleware_1.sendUnauthorisedResponse(recipeInstance, message, request, response, next);
},
};
if (config !== undefined && config.errorHandlers !== undefined) {
if (config.errorHandlers.onTokenTheftDetected !== undefined) {
errorHandlers.onTokenTheftDetected = config.errorHandlers.onTokenTheftDetected;
}
if (config.errorHandlers.onUnauthorised !== undefined) {
errorHandlers.onUnauthorised = config.errorHandlers.onUnauthorised;
}
}
if (cookieSameSite === "none" && !enableAntiCsrf) {
throw new error_1.default(
{
type: error_1.default.GENERAL_ERROR,
payload: new Error(
'Security error: enableAntiCsrf can\'t be set to false if cookieSameSite value is "none".'
),
},
recipeInstance
);
}
if (
cookieSameSite === "none" &&
!cookieSecure &&
!(topLevelAPIDomain === "localhost" || utils_1.isAnIpAddress(topLevelAPIDomain)) &&
!(topLevelWebsiteDomain === "localhost" || utils_1.isAnIpAddress(topLevelWebsiteDomain))
) {
throw new error_1.default(
{
type: error_1.default.GENERAL_ERROR,
payload: new Error(
"Since your API and website domain are different, for sessions to work, please use https on your apiDomain and dont set cookieSecure to false."
),
},
recipeInstance
);
}
return {
refreshTokenPath: appInfo.apiBasePath.appendPath(
recipeInstance,
new normalisedURLPath_1.default(recipeInstance, constants_1.REFRESH_API_PATH)
),
cookieDomain,
cookieSameSite,
cookieSecure,
sessionExpiredStatusCode,
sessionRefreshFeature,
errorHandlers,
enableAntiCsrf,
signOutFeature,
};
}
exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput;
function normaliseSameSiteOrThrowError(recipe, sameSite) {
sameSite = sameSite.trim();
sameSite = sameSite.toLocaleLowerCase();
if (sameSite !== "strict" && sameSite !== "lax" && sameSite !== "none") {
throw new error_1.default(
{
type: error_1.default.GENERAL_ERROR,
payload: new Error('cookie same site must be one of "strict", "lax", or "none"'),
},
recipe
);
}
return sameSite;
}
exports.normaliseSameSiteOrThrowError = normaliseSameSiteOrThrowError;
function attachCreateOrRefreshSessionResponseToExpressRes(recipeInstance, res, response) {
let accessToken = response.accessToken;
let refreshToken = response.refreshToken;
let idRefreshToken = response.idRefreshToken;
cookieAndHeaders_1.setFrontTokenInHeaders(
recipeInstance,
res,
response.session.userId,
response.accessToken.expiry,
response.session.userDataInJWT
);
cookieAndHeaders_1.attachAccessTokenToCookie(recipeInstance, res, accessToken.token, accessToken.expiry);
cookieAndHeaders_1.attachRefreshTokenToCookie(recipeInstance, res, refreshToken.token, refreshToken.expiry);
cookieAndHeaders_1.setIdRefreshTokenInHeaderAndCookie(
recipeInstance,
res,
idRefreshToken.token,
idRefreshToken.expiry
);
if (response.antiCsrfToken !== undefined) {
cookieAndHeaders_1.setAntiCsrfTokenInHeaders(recipeInstance, res, response.antiCsrfToken);
}
}
exports.attachCreateOrRefreshSessionResponseToExpressRes = attachCreateOrRefreshSessionResponseToExpressRes;
//# sourceMappingURL=utils.js.map