UNPKG

supertokens-node

Version:
287 lines (286 loc) 13.4 kB
"use strict"; /* 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. */ 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = require("./error"); const types_1 = require("./types"); const axios_1 = require("axios"); const utils_1 = require("./utils"); const querier_1 = require("./querier"); const constants_1 = require("./constants"); const normalisedURLDomain_1 = require("./normalisedURLDomain"); const normalisedURLPath_1 = require("./normalisedURLPath"); const error_2 = require("./error"); class SuperTokens { constructor(config) { this.sendTelemetry = () => __awaiter(this, void 0, void 0, function* () { try { let querier = querier_1.Querier.getInstanceOrThrowError(this.isInServerlessEnv, undefined); let response = yield querier.sendGetRequest( new normalisedURLPath_1.default(undefined, "/telemetry"), {} ); let telemetryId; if (response.exists) { telemetryId = response.telemetryId; } yield axios_1.default({ method: "POST", url: "https://api.supertokens.io/0/st/telemetry", data: { appName: this.appInfo.appName, websiteDomain: this.appInfo.websiteDomain.getAsStringDangerous(), telemetryId, }, headers: { "api-version": 2, }, }); } catch (ignored) {} }); // instance functions below...... this.middleware = () => { return (request, response, next) => __awaiter(this, void 0, void 0, function* () { let path = this.apiWebProxyPath.appendPath( undefined, new normalisedURLPath_1.default( undefined, request.originalUrl === undefined ? request.url : request.originalUrl ) ); let method = utils_1.normaliseHttpMethod(request.method); // if the prefix of the URL doesn't match the base path, we skip if (!path.startsWith(this.appInfo.apiBasePath)) { return next(); } let requestRID = utils_1.getRIDFromRequest(request); if (requestRID !== undefined) { let matchedRecipe = undefined; // we loop through all recipe modules to find the one with the matching rId for (let i = 0; i < this.recipeModules.length; i++) { if (this.recipeModules[i].getRecipeId() === requestRID) { matchedRecipe = this.recipeModules[i]; break; } } if (matchedRecipe === undefined) { // we could not find one, so we skip return next(); } let id = matchedRecipe.returnAPIIdIfCanHandleRequest(path, method); if (id === undefined) { // the matched recipe doesn't handle this path and http method return next(); } // give task to the matched recipe return yield this.handleAPI(matchedRecipe, id, request, response, next, path, method); } else { // we loop through all recipe modules to find the one with the matching path and method for (let i = 0; i < this.recipeModules.length; i++) { let id = this.recipeModules[i].returnAPIIdIfCanHandleRequest(path, method); if (id !== undefined) { return yield this.handleAPI( this.recipeModules[i], id, request, response, next, path, method ); } } return next(); } }); }; this.handleAPI = (matchedRecipe, id, request, response, next, path, method) => __awaiter(this, void 0, void 0, function* () { try { yield utils_1.assertThatBodyParserHasBeenUsed(matchedRecipe, request, response); return yield matchedRecipe.handleAPIRequest(id, request, response, next, path, method); } catch (err) { if (!error_1.default.isErrorFromSuperTokens(err)) { err = new error_1.default({ type: error_1.default.GENERAL_ERROR, payload: err, recipe: matchedRecipe, }); } return next(err); } }); this.errorHandler = () => { return (err, request, response, next) => __awaiter(this, void 0, void 0, function* () { if (error_1.default.isErrorFromSuperTokens(err)) { // if it's a general error, we extract the actual error and call the user's error handler if (err.type === error_1.default.GENERAL_ERROR) { return next(err.payload); } if (err.type === error_1.default.BAD_INPUT_ERROR) { return utils_1.sendNon200Response(err.recipe, response, err.message, 400); } // we loop through all the recipes and pass the error to the one that matches the rId for (let i = 0; i < this.recipeModules.length; i++) { if (this.recipeModules[i].isErrorFromThisRecipeBasedOnRid(err)) { try { return this.recipeModules[i].handleError(err, request, response, next); } catch (error) { return next(error); } } } } return next(err); }); }; this.getAllCORSHeaders = () => { let headerSet = new Set(); headerSet.add(constants_1.HEADER_RID); headerSet.add(constants_1.HEADER_FDI); this.recipeModules.forEach((recipe) => { let headers = recipe.getAllCORSHeaders(); headers.forEach((h) => { headerSet.add(h); }); }); return Array.from(headerSet); }; utils_1.validateTheStructureOfUserInput(config, types_1.InputSchema, "init function", undefined); this.apiWebProxyPath = config.apiWebProxyPath !== undefined ? new normalisedURLPath_1.default(undefined, config.apiWebProxyPath) : new normalisedURLPath_1.default(undefined, ""); this.appInfo = utils_1.normaliseInputAppInfoOrThrowError(undefined, config.appInfo, this.apiWebProxyPath); querier_1.Querier.init( config.supertokens.connectionURI .split(";") .filter((h) => h !== "") .map((h) => new normalisedURLDomain_1.default(undefined, h.trim())), config.supertokens.apiKey ); if (config.recipeList === undefined || config.recipeList.length === 0) { throw new error_2.default({ recipe: undefined, type: "GENERAL_ERROR", payload: new Error("Please provide at least one recipe to the supertokens.init function call"), }); } this.isInServerlessEnv = config.isInServerlessEnv === undefined ? false : config.isInServerlessEnv; if (!this.isInServerlessEnv) { /** * remove the files from the temp file-system. * for users using this lib in a serverless execution environment, * if the users updates/changes the core version they are using, * handshake info and api version that are stored in the temp files * might also be required to be updated. User can enforce this by stetting * this boolean to false, which would remove the files from the temporary * directory. The user can then again set it to true which would store the * updated handshake info and apiversion in the temp files */ utils_1.removeServerlessCache(); } this.recipeModules = config.recipeList.map((func) => { return func(this.appInfo, this.isInServerlessEnv); }); // check if duplicate APIs are exposed by any recipe by mistake for (let i = 0; i < this.recipeModules.length; i++) { let recipe = this.recipeModules[i]; let apisHandled = recipe.getAPIsHandled(); let stringifiedApisHandled = apisHandled .map((api) => { if (api.disabled) { return ""; } return api.method + ";" + api.pathWithoutApiBasePath.getAsStringDangerous(); }) .filter((i) => i !== ""); let findDuplicates = (arr) => arr.filter((item, index) => arr.indexOf(item) != index); if (findDuplicates(stringifiedApisHandled).length !== 0) { throw new error_1.default({ recipe, type: error_1.default.GENERAL_ERROR, payload: new Error("Duplicate APIs exposed from recipe. Please combine them into one API"), }); } } let telemetry = config.telemetry === undefined ? process.env.TEST_MODE !== "testing" : config.telemetry; if (telemetry) { this.sendTelemetry(); } } static init(config) { if (SuperTokens.instance === undefined) { SuperTokens.instance = new SuperTokens(config); } } static reset() { if (process.env.TEST_MODE !== "testing") { throw new error_1.default({ type: error_1.default.GENERAL_ERROR, recipe: undefined, payload: new Error("calling testing function in non testing env"), }); } querier_1.Querier.reset(); SuperTokens.instance = undefined; } static getInstanceOrThrowError() { if (SuperTokens.instance !== undefined) { return SuperTokens.instance; } throw new error_1.default({ type: error_1.default.GENERAL_ERROR, recipe: undefined, payload: new Error("Initialisation not done. Did you forget to call the SuperTokens.init function?"), }); } } exports.default = SuperTokens; //# sourceMappingURL=supertokens.js.map