supertokens-node
Version:
NodeJS driver for SuperTokens core
254 lines (253 loc) • 13.8 kB
JavaScript
"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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const recipeModule_1 = __importDefault(require("../../recipeModule"));
const error_1 = __importDefault(require("./error"));
const utils_1 = require("./utils");
const refresh_1 = __importDefault(require("./api/refresh"));
const signout_1 = __importDefault(require("./api/signout"));
const constants_1 = require("./constants");
const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath"));
const cookieAndHeaders_1 = require("./cookieAndHeaders");
const recipeImplementation_1 = __importDefault(require("./recipeImplementation"));
const implementation_1 = __importDefault(require("./api/implementation"));
const supertokens_js_override_1 = __importDefault(require("supertokens-js-override"));
const logger_1 = require("../../logger");
const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet");
const utils_2 = require("../../utils");
const plugins_1 = require("../../plugins");
const sessionRequestFunctions_1 = require("./sessionRequestFunctions");
// For Express
class SessionRecipe extends recipeModule_1.default {
constructor(stInstance, recipeId, appInfo, isInServerlessEnv, config) {
super(stInstance, recipeId, appInfo);
this.claimsAddedByOtherRecipes = [];
this.claimValidatorsAddedByOtherRecipes = [];
this.addClaimFromOtherRecipe = (claim) => {
// We are throwing here (and not in addClaimValidatorFromOtherRecipe) because if multiple
// claims are added with the same key they will overwrite each other. Validators will all run
// and work as expected even if they are added multiple times.
if (this.claimsAddedByOtherRecipes.some((c) => c.key === claim.key)) {
throw new Error("Claim added by multiple recipes");
}
this.claimsAddedByOtherRecipes.push(claim);
};
this.getClaimsAddedByOtherRecipes = () => {
return this.claimsAddedByOtherRecipes;
};
this.addClaimValidatorFromOtherRecipe = (builder) => {
this.claimValidatorsAddedByOtherRecipes.push(builder);
};
this.getClaimValidatorsAddedByOtherRecipes = () => {
return this.claimValidatorsAddedByOtherRecipes;
};
// abstract instance functions below...............
this.getAPIsHandled = () => {
let apisHandled = [
{
method: "post",
pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.REFRESH_API_PATH),
id: constants_1.REFRESH_API_PATH,
disabled: this.apiImpl.refreshPOST === undefined,
},
{
method: "post",
pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGNOUT_API_PATH),
id: constants_1.SIGNOUT_API_PATH,
disabled: this.apiImpl.signOutPOST === undefined,
},
];
return apisHandled;
};
this.handleAPIRequest = async (id, _tenantId, req, res, _path, _method, userContext) => {
let options = {
config: this.config,
recipeId: this.getRecipeId(),
isInServerlessEnv: this.isInServerlessEnv,
recipeImplementation: this.recipeInterfaceImpl,
req,
res,
};
if (id === constants_1.REFRESH_API_PATH) {
return await (0, refresh_1.default)(this.apiImpl, options, userContext);
}
else if (id === constants_1.SIGNOUT_API_PATH) {
return await (0, signout_1.default)(this.stInstance, this.apiImpl, options, userContext);
}
else {
return false;
}
};
this.handleError = async (err, request, response, userContext) => {
if (err.fromRecipe === SessionRecipe.RECIPE_ID) {
if (err.type === error_1.default.UNAUTHORISED) {
(0, logger_1.logDebugMessage)("errorHandler: returning UNAUTHORISED");
if (err.payload === undefined ||
err.payload.clearTokens === undefined ||
err.payload.clearTokens === true) {
(0, logger_1.logDebugMessage)("errorHandler: Clearing tokens because of UNAUTHORISED response");
(0, cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods)(this.config, response, request, userContext);
}
return await this.config.errorHandlers.onUnauthorised(err.message, request, response, userContext);
}
else if (err.type === error_1.default.TRY_REFRESH_TOKEN) {
(0, logger_1.logDebugMessage)("errorHandler: returning TRY_REFRESH_TOKEN");
return await this.config.errorHandlers.onTryRefreshToken(err.message, request, response, userContext);
}
else if (err.type === error_1.default.TOKEN_THEFT_DETECTED) {
(0, logger_1.logDebugMessage)("errorHandler: returning TOKEN_THEFT_DETECTED");
(0, logger_1.logDebugMessage)("errorHandler: Clearing tokens because of TOKEN_THEFT_DETECTED response");
(0, cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods)(this.config, response, request, userContext);
return await this.config.errorHandlers.onTokenTheftDetected(err.payload.sessionHandle, err.payload.userId, err.payload.recipeUserId, request, response, userContext);
}
else if (err.type === error_1.default.INVALID_CLAIMS) {
return await this.config.errorHandlers.onInvalidClaim(err.payload, request, response, userContext);
}
else if (err.type === error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES) {
(0, logger_1.logDebugMessage)("errorHandler: returning CLEAR_DUPLICATE_SESSION_COOKIES");
// This error occurs in the `refreshPOST` API when multiple session
// cookies are found in the request and the user has set `olderCookieDomain`.
// We remove session cookies from the olderCookieDomain. The response must return `200 OK`
// to avoid logging out the user, allowing the session to continue with the valid cookie.
return await this.config.errorHandlers.onClearDuplicateSessionCookies(err.message, request, response, userContext);
}
else {
throw err;
}
}
else {
throw err;
}
};
this.getAllCORSHeaders = () => {
let corsHeaders = [...(0, cookieAndHeaders_1.getCORSAllowedHeaders)()];
return corsHeaders;
};
this.isErrorFromThisRecipe = (err) => {
return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === SessionRecipe.RECIPE_ID;
};
this.verifySession = async (options, request, response, userContext) => {
return await this.apiImpl.verifySession({
verifySessionOptions: options,
options: {
config: this.config,
req: request,
res: response,
recipeId: this.getRecipeId(),
isInServerlessEnv: this.isInServerlessEnv,
recipeImplementation: this.recipeInterfaceImpl,
},
userContext,
});
};
this.getRequiredClaimValidators = async (session, overrideGlobalClaimValidators, userContext) => {
const claimValidatorsAddedByOtherRecipes = this.getClaimValidatorsAddedByOtherRecipes();
const globalClaimValidators = await this.recipeInterfaceImpl.getGlobalClaimValidators({
userId: session.getUserId(userContext),
recipeUserId: session.getRecipeUserId(userContext),
tenantId: session.getTenantId(userContext),
claimValidatorsAddedByOtherRecipes,
userContext,
});
return overrideGlobalClaimValidators !== undefined
? await overrideGlobalClaimValidators(globalClaimValidators, session, userContext)
: globalClaimValidators;
};
this.createNewSession = async (input) => {
let user = await this.stInstance
.getRecipeInstanceOrThrow("accountlinking")
.recipeInterfaceImpl.getUser({ userId: input.recipeUserId.getAsString(), userContext: input.userContext });
let userId = input.recipeUserId.getAsString();
if (user !== undefined) {
userId = user.id;
}
return await (0, sessionRequestFunctions_1.createNewSessionInRequest)({
req: input.req,
res: input.res,
userContext: input.userContext,
recipeInstance: this,
stInstance: this.stInstance,
accessTokenPayload: input.accessTokenPayload,
userId,
recipeUserId: input.recipeUserId,
config: this.config,
appInfo: this.stInstance.appInfo,
sessionDataInDatabase: input.sessionDataInDatabase,
tenantId: input.tenantId,
});
};
this.getSession = async (input) => {
return (0, sessionRequestFunctions_1.getSessionFromRequest)({
req: input.req,
res: input.res,
recipeInterfaceImpl: this.recipeInterfaceImpl,
recipeInstance: this,
stInstance: this.stInstance,
config: this.config,
options: input.options,
userContext: input.userContext, // userContext is normalized inside the function
});
};
this.config = (0, utils_1.validateAndNormaliseUserInput)(this, appInfo, config);
const antiCsrfToLog = typeof this.config.antiCsrfFunctionOrString === "string"
? this.config.antiCsrfFunctionOrString
: "function";
(0, logger_1.logDebugMessage)("session init: antiCsrf: " + antiCsrfToLog);
(0, logger_1.logDebugMessage)("session init: cookieDomain: " + this.config.cookieDomain);
const sameSiteToPrint = config !== undefined && config.cookieSameSite !== undefined ? config.cookieSameSite : "default function";
(0, logger_1.logDebugMessage)("session init: cookieSameSite: " + sameSiteToPrint);
(0, logger_1.logDebugMessage)("session init: cookieSecure: " + this.config.cookieSecure);
(0, logger_1.logDebugMessage)("session init: refreshTokenPath: " + this.config.refreshTokenPath.getAsStringDangerous());
(0, logger_1.logDebugMessage)("session init: sessionExpiredStatusCode: " + this.config.sessionExpiredStatusCode);
this.isInServerlessEnv = isInServerlessEnv;
let builder = new supertokens_js_override_1.default((0, recipeImplementation_1.default)(this.querier, this.config, this.getAppInfo(), () => this.recipeInterfaceImpl));
this.recipeInterfaceImpl = builder.override(this.config.override.functions).build();
{
let builder = new supertokens_js_override_1.default((0, implementation_1.default)(this.stInstance, this));
this.apiImpl = builder.override(this.config.override.apis).build();
}
}
static getInstanceOrThrowError() {
if (SessionRecipe.instance !== undefined) {
return SessionRecipe.instance;
}
throw new Error("Initialisation not done. Did you forget to call the SuperTokens.init or Session.init function?");
}
static init(config) {
return (stInstance, appInfo, isInServerlessEnv, plugins) => {
if (SessionRecipe.instance === undefined) {
SessionRecipe.instance = new SessionRecipe(stInstance, SessionRecipe.RECIPE_ID, appInfo, isInServerlessEnv, (0, plugins_1.applyPlugins)(SessionRecipe.RECIPE_ID, config, plugins !== null && plugins !== void 0 ? plugins : []));
return SessionRecipe.instance;
}
else {
throw new Error("Session recipe has already been initialised. Please check your code for bugs.");
}
};
}
static reset() {
if (!(0, utils_2.isTestEnv)()) {
throw new Error("calling testing function in non testing env");
}
SessionRecipe.instance = undefined;
(0, combinedRemoteJWKSet_1.resetCombinedJWKS)();
}
}
SessionRecipe.instance = undefined;
SessionRecipe.RECIPE_ID = "session";
exports.default = SessionRecipe;