supertokens-node
Version:
NodeJS driver for SuperTokens core
472 lines (471 loc) • 18.4 kB
JavaScript
;
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = {
enumerable: true,
get: function () {
return m[k];
},
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __setModuleDefault =
(this && this.__setModuleDefault) ||
(Object.create
? function (o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}
: function (o, v) {
o["default"] = v;
});
var __importStar =
(this && this.__importStar) ||
(function () {
var ownKeys = function (o) {
ownKeys =
Object.getOwnPropertyNames ||
function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null)
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault =
(this && this.__importDefault) ||
function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isBuffer =
exports.decodeBase64 =
exports.encodeBase64 =
exports.isTestEnv =
exports.getBuffer =
exports.getProcess =
void 0;
exports.getLargestVersionFromIntersection = getLargestVersionFromIntersection;
exports.maxVersion = maxVersion;
exports.normaliseInputAppInfoOrThrowError = normaliseInputAppInfoOrThrowError;
exports.normaliseHttpMethod = normaliseHttpMethod;
exports.sendNon200ResponseWithMessage = sendNon200ResponseWithMessage;
exports.sendNon200Response = sendNon200Response;
exports.send200Response = send200Response;
exports.sendRedirectResponse = sendRedirectResponse;
exports.getNormalisedShouldTryLinkingWithSessionUserFlag = getNormalisedShouldTryLinkingWithSessionUserFlag;
exports.getBackwardsCompatibleUserInfo = getBackwardsCompatibleUserInfo;
exports.getLatestFDIVersionFromFDIList = getLatestFDIVersionFromFDIList;
exports.hasGreaterThanEqualToFDI = hasGreaterThanEqualToFDI;
exports.getRidFromHeader = getRidFromHeader;
exports.frontendHasInterceptor = frontendHasInterceptor;
exports.humaniseMilliseconds = humaniseMilliseconds;
exports.makeDefaultUserContextFromAPI = makeDefaultUserContextFromAPI;
exports.getUserContext = getUserContext;
exports.setRequestInUserContextIfNotDefined = setRequestInUserContextIfNotDefined;
exports.getTopLevelDomainForSameSiteResolution = getTopLevelDomainForSameSiteResolution;
exports.getFromObjectCaseInsensitive = getFromObjectCaseInsensitive;
exports.normaliseEmail = normaliseEmail;
exports.toCamelCase = toCamelCase;
exports.toSnakeCase = toSnakeCase;
exports.transformObjectKeys = transformObjectKeys;
exports.getPublicConfig = getPublicConfig;
const tldts_1 = require("tldts");
const types_1 = require("./types");
const normalisedURLDomain_1 = __importStar(require("./normalisedURLDomain"));
const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath"));
const logger_1 = require("./logger");
const constants_1 = require("./constants");
function getLargestVersionFromIntersection(v1, v2) {
let intersection = v1.filter((value) => v2.indexOf(value) !== -1);
if (intersection.length === 0) {
return undefined;
}
let maxVersionSoFar = intersection[0];
for (let i = 1; i < intersection.length; i++) {
maxVersionSoFar = maxVersion(intersection[i], maxVersionSoFar);
}
return maxVersionSoFar;
}
function maxVersion(version1, version2) {
let splittedv1 = version1.split(".");
let splittedv2 = version2.split(".");
let minLength = Math.min(splittedv1.length, splittedv2.length);
for (let i = 0; i < minLength; i++) {
let v1 = Number(splittedv1[i]);
let v2 = Number(splittedv2[i]);
if (v1 > v2) {
return version1;
} else if (v2 > v1) {
return version2;
}
}
if (splittedv1.length >= splittedv2.length) {
return version1;
}
return version2;
}
function normaliseInputAppInfoOrThrowError(appInfo) {
if (appInfo === undefined) {
throw new Error("Please provide the appInfo object when calling supertokens.init");
}
if (appInfo.apiDomain === undefined) {
throw new Error("Please provide your apiDomain inside the appInfo object when calling supertokens.init");
}
if (appInfo.appName === undefined) {
throw new Error("Please provide your appName inside the appInfo object when calling supertokens.init");
}
let apiGatewayPath =
appInfo.apiGatewayPath !== undefined
? new normalisedURLPath_1.default(appInfo.apiGatewayPath)
: new normalisedURLPath_1.default("");
if (appInfo.origin === undefined && appInfo.websiteDomain === undefined) {
throw new Error(
"Please provide either origin or websiteDomain inside the appInfo object when calling supertokens.init"
);
}
let websiteDomainFunction = (input) => {
let origin = appInfo.origin;
if (origin === undefined) {
origin = appInfo.websiteDomain;
}
// This should not be possible because we check for either origin or websiteDomain above
if (origin === undefined) {
throw new Error("Should never come here");
}
if (typeof origin === "function") {
origin = origin(input);
}
return new normalisedURLDomain_1.default(origin);
};
const apiDomain = new normalisedURLDomain_1.default(appInfo.apiDomain);
const topLevelAPIDomain = getTopLevelDomainForSameSiteResolution(apiDomain.getAsStringDangerous());
const topLevelWebsiteDomain = (input) => {
return getTopLevelDomainForSameSiteResolution(websiteDomainFunction(input).getAsStringDangerous());
};
return {
appName: appInfo.appName,
getOrigin: websiteDomainFunction,
apiDomain,
apiBasePath: apiGatewayPath.appendPath(
appInfo.apiBasePath === undefined
? new normalisedURLPath_1.default("/auth")
: new normalisedURLPath_1.default(appInfo.apiBasePath)
),
websiteBasePath:
appInfo.websiteBasePath === undefined
? new normalisedURLPath_1.default("/auth")
: new normalisedURLPath_1.default(appInfo.websiteBasePath),
apiGatewayPath,
topLevelAPIDomain,
getTopLevelWebsiteDomain: topLevelWebsiteDomain,
};
}
function normaliseHttpMethod(method) {
return method.toLowerCase();
}
function sendNon200ResponseWithMessage(res, message, statusCode) {
sendNon200Response(res, statusCode, { message });
}
function sendNon200Response(res, statusCode, body) {
if (statusCode < 300) {
throw new Error("Calling sendNon200Response with status code < 300");
}
(0, logger_1.logDebugMessage)("Sending response to client with status code: " + statusCode);
res.setStatusCode(statusCode);
res.sendJSONResponse(body);
}
function send200Response(res, responseJson) {
(0, logger_1.logDebugMessage)("Sending response to client with status code: 200");
responseJson = deepTransform(responseJson);
res.setStatusCode(200);
res.sendJSONResponse(responseJson);
}
function sendRedirectResponse(res, location) {
(0, logger_1.logDebugMessage)("Sending redirect response with status code 302");
res.setHeader("Location", location, false);
res.setStatusCode(302);
res.sendHTMLResponse("");
}
// this function tries to convert the json response based on the toJson function
// defined in the objects in the input. This is primarily used to convert the RecipeUserId
// type to a string type before sending it to the client.
function deepTransform(obj) {
let out = Array.isArray(obj) ? [] : {};
for (let key in obj) {
let val = obj[key];
if (val && typeof val === "object" && val["toJson"] !== undefined && typeof val["toJson"] === "function") {
out[key] = val.toJson();
} else if (val && typeof val === "object") {
out[key] = deepTransform(val);
} else {
out[key] = val;
}
}
return out;
}
function getNormalisedShouldTryLinkingWithSessionUserFlag(req, body) {
var _a;
if (hasGreaterThanEqualToFDI(req, "3.1")) {
return (_a = body.shouldTryLinkingWithSessionUser) !== null && _a !== void 0 ? _a : false;
}
return undefined;
}
function getBackwardsCompatibleUserInfo(req, result, userContext) {
let resp;
// (>= 1.18 && < 2.0) || >= 3.0: This is because before 1.18, and between 2 and 3, FDI does not
// support account linking.
if (
(hasGreaterThanEqualToFDI(req, "1.18") && !hasGreaterThanEqualToFDI(req, "2.0")) ||
hasGreaterThanEqualToFDI(req, "3.0")
) {
resp = {
user: result.user.toJson(),
};
if (result.createdNewRecipeUser !== undefined) {
resp.createdNewRecipeUser = result.createdNewRecipeUser;
}
return resp;
} else {
let loginMethod = result.user.loginMethods.find(
(lm) => lm.recipeUserId.getAsString() === result.session.getRecipeUserId(userContext).getAsString()
);
if (loginMethod === undefined) {
// we pick the oldest login method here for the user.
// this can happen in case the user is implementing something like
// MFA where the session remains the same during the second factor as well.
for (let i = 0; i < result.user.loginMethods.length; i++) {
if (loginMethod === undefined) {
loginMethod = result.user.loginMethods[i];
} else if (loginMethod.timeJoined > result.user.loginMethods[i].timeJoined) {
loginMethod = result.user.loginMethods[i];
}
}
}
if (loginMethod === undefined) {
throw new Error("This should never happen - user has no login methods");
}
const userObj = {
id: result.user.id, // we purposely use this instead of the loginmethod's recipeUserId because if the oldest login method is deleted, then this userID should remain the same.
timeJoined: loginMethod.timeJoined,
};
if (loginMethod.thirdParty) {
userObj.thirdParty = loginMethod.thirdParty;
}
if (loginMethod.email) {
userObj.email = loginMethod.email;
}
if (loginMethod.phoneNumber) {
userObj.phoneNumber = loginMethod.phoneNumber;
}
resp = {
user: userObj,
};
if (result.createdNewRecipeUser !== undefined) {
resp.createdNewUser = result.createdNewRecipeUser;
}
}
return resp;
}
function getLatestFDIVersionFromFDIList(fdiHeaderValue) {
let versions = fdiHeaderValue.split(",");
let maxVersionStr = versions[0];
for (let i = 1; i < versions.length; i++) {
maxVersionStr = maxVersion(maxVersionStr, versions[i]);
}
return maxVersionStr;
}
function hasGreaterThanEqualToFDI(req, version) {
let requestFDI = req.getHeaderValue(constants_1.HEADER_FDI);
if (requestFDI === undefined) {
// By default we assume they want to use the latest FDI, this also helps with tests
return true;
}
requestFDI = getLatestFDIVersionFromFDIList(requestFDI);
if (requestFDI === version || maxVersion(version, requestFDI) !== version) {
return true;
}
return false;
}
function getRidFromHeader(req) {
return req.getHeaderValue(constants_1.HEADER_RID);
}
function frontendHasInterceptor(req) {
return getRidFromHeader(req) !== undefined;
}
function humaniseMilliseconds(ms) {
let t = Math.floor(ms / 1000);
let suffix = "";
if (t < 60) {
if (t > 1) suffix = "s";
return `${t} second${suffix}`;
} else if (t < 3600) {
const m = Math.floor(t / 60);
if (m > 1) suffix = "s";
return `${m} minute${suffix}`;
} else {
const h = Math.floor(t / 360) / 10;
if (h > 1) suffix = "s";
return `${h} hour${suffix}`;
}
}
function makeDefaultUserContextFromAPI(request) {
return setRequestInUserContextIfNotDefined({}, request);
}
function getUserContext(inputUserContext) {
return inputUserContext !== null && inputUserContext !== void 0 ? inputUserContext : {};
}
function setRequestInUserContextIfNotDefined(userContext, request) {
if (userContext === undefined) {
userContext = {};
}
if (userContext._default === undefined) {
userContext._default = {};
}
if (typeof userContext._default === "object") {
userContext._default.request = request;
userContext._default.keepCacheAlive = true;
}
return userContext;
}
function getTopLevelDomainForSameSiteResolution(url) {
let urlObj = new URL(url);
let hostname = urlObj.hostname;
if (
hostname.startsWith("localhost") ||
hostname.startsWith("localhost.org") ||
(0, normalisedURLDomain_1.isAnIpAddress)(hostname)
) {
// we treat these as the same TLDs since we can use sameSite lax for all of them.
return "localhost";
}
// Before `tldts`, `psl` was being used and that library automatically
// handled parsing private domains. With `tldts`, `allowPrivateDomains` is
// required to be passed to handle that.
//
// This is important for parsing ec2 public URL's that were initially
// reported to be breaking in the following issue:
// https://github.com/supertokens/supertokens-python/issues/394
let parsedURL = (0, tldts_1.parse)(hostname, { allowPrivateDomains: true });
if (!parsedURL.domain) {
// If the URL is an AWS public URL, return the entire URL since it is
// considered a suffix entirely (instead of just amazonaws.com). This
// was initially reported in https://github.com/supertokens/supertokens-python/issues/394
if (hostname.endsWith(".amazonaws.com") && parsedURL.publicSuffix === hostname) {
return hostname;
}
// support for .local domain
if (hostname.endsWith(".local") && !parsedURL.publicSuffix) {
return hostname;
}
throw new Error("Please make sure that the apiDomain and websiteDomain have correct values");
}
return parsedURL.domain;
}
function getFromObjectCaseInsensitive(key, object) {
const matchedKeys = Object.keys(object).filter((i) => i.toLocaleLowerCase() === key.toLocaleLowerCase());
if (matchedKeys.length === 0) {
return undefined;
}
return object[matchedKeys[0]];
}
function normaliseEmail(email) {
email = email.trim();
email = email.toLowerCase();
return email;
}
function toCamelCase(str) {
return str.replace(/([-_][a-z])/gi, (match) => {
return match.toUpperCase().replace("-", "").replace("_", "");
});
}
function toSnakeCase(str) {
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
// Transforms the keys of an object from camelCase to snakeCase or vice versa.
function transformObjectKeys(obj, caseType) {
const transformKey = caseType === "camelCase" ? toCamelCase : toSnakeCase;
return Object.entries(obj).reduce((result, [key, value]) => {
const transformedKey = transformKey(key);
result[transformedKey] = value;
return result;
}, {});
}
const getProcess = () => {
/**
* Return the process instance if it is available falling back
* to one that is compatible where process may not be available
* (like `edge` runtime).
*/
if (typeof process !== "undefined") return process;
const ponyFilledProcess = require("process");
return ponyFilledProcess;
};
exports.getProcess = getProcess;
const getBuffer = () => {
/**
* Return the Buffer instance if it is available falling back
* to one that is compatible where it may not be available
* (like `edge` runtime).
*/
if (typeof Buffer !== "undefined") return Buffer;
const ponyFilledBuffer = require("buffer").Buffer;
return ponyFilledBuffer;
};
exports.getBuffer = getBuffer;
const isTestEnv = () => {
/**
* Check if test mode is enabled by reading the environment variable.
*/
return (0, exports.getProcess)().env.TEST_MODE === "testing";
};
exports.isTestEnv = isTestEnv;
const encodeBase64 = (value) => {
/**
* Encode the passed value to base64 and return the encoded value.
*/
return (0, exports.getBuffer)().from(value).toString("base64");
};
exports.encodeBase64 = encodeBase64;
const decodeBase64 = (value) => {
/**
* Decode the passed value with base64 encoded and return the
* decoded value.
*/
return (0, exports.getBuffer)().from(value, "base64").toString();
};
exports.decodeBase64 = decodeBase64;
const isBuffer = (obj) => {
/**
* Check if the passed object is a buffer or not.
*/
return (0, exports.getBuffer)().isBuffer(obj);
};
exports.isBuffer = isBuffer;
function getPublicConfig(config) {
// `Entries<T>` will work fine assuming there are no extra properties in `TypeInput`.
const configEntries = Object.entries(config);
const publicConfig = Object.fromEntries(
configEntries.filter(([key, _]) => !types_1.nonPublicConfigProperties.includes(key))
);
return publicConfig;
}