From 45f0ac691c6316dd86dca8a15fad8a7f86b66756 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Thu, 31 Oct 2024 13:03:58 +0100 Subject: [PATCH] feat: make cookie and header names configurable for access and refresh tokens --- lib/build/recipe/session/constants.d.ts | 8 ++ lib/build/recipe/session/constants.js | 10 ++- .../recipe/session/cookieAndHeaders.d.ts | 19 +++-- lib/build/recipe/session/cookieAndHeaders.js | 75 +++++++------------ .../session/sessionRequestFunctions.d.ts | 4 +- .../recipe/session/sessionRequestFunctions.js | 30 ++++++-- lib/build/recipe/session/types.d.ts | 4 + lib/build/recipe/session/utils.d.ts | 11 ++- lib/build/recipe/session/utils.js | 42 +++++++++-- lib/ts/recipe/session/constants.ts | 11 +++ lib/ts/recipe/session/cookieAndHeaders.ts | 73 +++++++----------- .../recipe/session/sessionRequestFunctions.ts | 27 +++++-- lib/ts/recipe/session/types.ts | 4 + lib/ts/recipe/session/utils.ts | 37 ++++++++- 14 files changed, 230 insertions(+), 125 deletions(-) diff --git a/lib/build/recipe/session/constants.d.ts b/lib/build/recipe/session/constants.d.ts index 78a3ec979..008545203 100644 --- a/lib/build/recipe/session/constants.d.ts +++ b/lib/build/recipe/session/constants.d.ts @@ -6,3 +6,11 @@ export declare const availableTokenTransferMethods: TokenTransferMethod[]; export declare const oneYearInMs = 31536000000; export declare const JWKCacheCooldownInMs = 500; export declare const protectedProps: string[]; +export declare const authorizationHeaderKey = "authorization"; +export declare const accessTokenHeaderKey = "st-access-token"; +export declare const accessTokenCookieKey = "sAccessToken"; +export declare const refreshTokenCookieKey = "sRefreshToken"; +export declare const refreshTokenHeaderKey = "st-refresh-token"; +export declare const antiCsrfHeaderKey = "anti-csrf"; +export declare const frontTokenHeaderKey = "front-token"; +export declare const authModeHeaderKey = "st-auth-mode"; diff --git a/lib/build/recipe/session/constants.js b/lib/build/recipe/session/constants.js index 84d5b0225..e196684e5 100644 --- a/lib/build/recipe/session/constants.js +++ b/lib/build/recipe/session/constants.js @@ -14,7 +14,7 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.protectedProps = exports.JWKCacheCooldownInMs = exports.oneYearInMs = exports.availableTokenTransferMethods = exports.SIGNOUT_API_PATH = exports.REFRESH_API_PATH = void 0; +exports.authModeHeaderKey = exports.frontTokenHeaderKey = exports.antiCsrfHeaderKey = exports.refreshTokenHeaderKey = exports.refreshTokenCookieKey = exports.accessTokenCookieKey = exports.accessTokenHeaderKey = exports.authorizationHeaderKey = exports.protectedProps = exports.JWKCacheCooldownInMs = exports.oneYearInMs = exports.availableTokenTransferMethods = exports.SIGNOUT_API_PATH = exports.REFRESH_API_PATH = void 0; exports.REFRESH_API_PATH = "/session/refresh"; exports.SIGNOUT_API_PATH = "/signout"; exports.availableTokenTransferMethods = ["cookie", "header"]; @@ -32,3 +32,11 @@ exports.protectedProps = [ "tId", "stt", ]; +exports.authorizationHeaderKey = "authorization"; +exports.accessTokenHeaderKey = "st-access-token"; +exports.accessTokenCookieKey = "sAccessToken"; +exports.refreshTokenCookieKey = "sRefreshToken"; +exports.refreshTokenHeaderKey = "st-refresh-token"; +exports.antiCsrfHeaderKey = "anti-csrf"; +exports.frontTokenHeaderKey = "front-token"; +exports.authModeHeaderKey = "st-auth-mode"; diff --git a/lib/build/recipe/session/cookieAndHeaders.d.ts b/lib/build/recipe/session/cookieAndHeaders.d.ts index 804655add..dd036e7d3 100644 --- a/lib/build/recipe/session/cookieAndHeaders.d.ts +++ b/lib/build/recipe/session/cookieAndHeaders.d.ts @@ -5,14 +5,14 @@ import { TokenTransferMethod, TokenType, TypeNormalisedInput } from "./types"; export declare function clearSessionFromAllTokenTransferMethods( config: TypeNormalisedInput, res: BaseResponse, - request: BaseRequest | undefined, + request: BaseRequest, userContext: UserContext ): void; export declare function clearSession( config: TypeNormalisedInput, res: BaseResponse, transferMethod: TokenTransferMethod, - request: BaseRequest | undefined, + request: BaseRequest, userContext: UserContext ): void; export declare function getAntiCsrfTokenFromHeaders(req: BaseRequest): string | undefined; @@ -20,12 +20,12 @@ export declare function setAntiCsrfTokenInHeaders(res: BaseResponse, antiCsrfTok export declare function buildFrontToken(userId: string, atExpiry: number, accessTokenPayload: any): string; export declare function setFrontTokenInHeaders(res: BaseResponse, frontToken: string): void; export declare function getCORSAllowedHeaders(): string[]; -export declare function getCookieNameFromTokenType(tokenType: TokenType): "sAccessToken" | "sRefreshToken"; -export declare function getResponseHeaderNameForTokenType(tokenType: TokenType): "st-access-token" | "st-refresh-token"; export declare function getToken( + config: TypeNormalisedInput, req: BaseRequest, tokenType: TokenType, - transferMethod: TokenTransferMethod + transferMethod: TokenTransferMethod, + userContext: UserContext ): string | undefined; export declare function setToken( config: TypeNormalisedInput, @@ -34,7 +34,7 @@ export declare function setToken( value: string, expires: number, transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, + req: BaseRequest, userContext: UserContext ): void; export declare function setHeader(res: BaseResponse, name: string, value: string): void; @@ -83,4 +83,9 @@ export declare function clearSessionCookiesFromOlderCookieDomain({ config: TypeNormalisedInput; userContext: UserContext; }): void; -export declare function hasMultipleCookiesForTokenType(req: BaseRequest, tokenType: TokenType): boolean; +export declare function hasMultipleCookiesForTokenType( + config: TypeNormalisedInput, + req: BaseRequest, + tokenType: TokenType, + userContext: UserContext +): boolean; diff --git a/lib/build/recipe/session/cookieAndHeaders.js b/lib/build/recipe/session/cookieAndHeaders.js index 26c1d3475..fd45f8f6f 100644 --- a/lib/build/recipe/session/cookieAndHeaders.js +++ b/lib/build/recipe/session/cookieAndHeaders.js @@ -5,7 +5,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getResponseHeaderNameForTokenType = exports.getCookieNameFromTokenType = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0; +exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the @@ -25,14 +25,6 @@ const logger_1 = require("../../logger"); const utils_1 = require("../../utils"); const constants_2 = require("./constants"); const error_1 = __importDefault(require("./error")); -const authorizationHeaderKey = "authorization"; -const accessTokenCookieKey = "sAccessToken"; -const accessTokenHeaderKey = "st-access-token"; -const refreshTokenCookieKey = "sRefreshToken"; -const refreshTokenHeaderKey = "st-refresh-token"; -const antiCsrfHeaderKey = "anti-csrf"; -const frontTokenHeaderKey = "front-token"; -const authModeHeaderKey = "st-auth-mode"; function clearSessionFromAllTokenTransferMethods(config, res, request, userContext) { // We are clearing the session in all transfermethods to be sure to override cookies in case they have been already added to the response. // This is done to handle the following use-case: @@ -51,19 +43,19 @@ function clearSession(config, res, transferMethod, request, userContext) { for (const token of tokenTypes) { setToken(config, res, token, "", 0, transferMethod, request, userContext); } - res.removeHeader(antiCsrfHeaderKey); + res.removeHeader(constants_2.antiCsrfHeaderKey); // This can be added multiple times in some cases, but that should be OK - res.setHeader(frontTokenHeaderKey, "remove", false); - res.setHeader("Access-Control-Expose-Headers", frontTokenHeaderKey, true); + res.setHeader(constants_2.frontTokenHeaderKey, "remove", false); + res.setHeader("Access-Control-Expose-Headers", constants_2.frontTokenHeaderKey, true); } exports.clearSession = clearSession; function getAntiCsrfTokenFromHeaders(req) { - return req.getHeaderValue(antiCsrfHeaderKey); + return req.getHeaderValue(constants_2.antiCsrfHeaderKey); } exports.getAntiCsrfTokenFromHeaders = getAntiCsrfTokenFromHeaders; function setAntiCsrfTokenInHeaders(res, antiCsrfToken) { - res.setHeader(antiCsrfHeaderKey, antiCsrfToken, false); - res.setHeader("Access-Control-Expose-Headers", antiCsrfHeaderKey, true); + res.setHeader(constants_2.antiCsrfHeaderKey, antiCsrfToken, false); + res.setHeader("Access-Control-Expose-Headers", constants_2.antiCsrfHeaderKey, true); } exports.setAntiCsrfTokenInHeaders = setAntiCsrfTokenInHeaders; function buildFrontToken(userId, atExpiry, accessTokenPayload) { @@ -76,41 +68,24 @@ function buildFrontToken(userId, atExpiry, accessTokenPayload) { } exports.buildFrontToken = buildFrontToken; function setFrontTokenInHeaders(res, frontToken) { - res.setHeader(frontTokenHeaderKey, frontToken, false); - res.setHeader("Access-Control-Expose-Headers", frontTokenHeaderKey, true); + res.setHeader(constants_2.frontTokenHeaderKey, frontToken, false); + res.setHeader("Access-Control-Expose-Headers", constants_2.frontTokenHeaderKey, true); } exports.setFrontTokenInHeaders = setFrontTokenInHeaders; function getCORSAllowedHeaders() { - return [antiCsrfHeaderKey, constants_1.HEADER_RID, authorizationHeaderKey, authModeHeaderKey]; + return [ + constants_2.antiCsrfHeaderKey, + constants_1.HEADER_RID, + constants_2.authorizationHeaderKey, + constants_2.authModeHeaderKey, + ]; } exports.getCORSAllowedHeaders = getCORSAllowedHeaders; -function getCookieNameFromTokenType(tokenType) { - switch (tokenType) { - case "access": - return accessTokenCookieKey; - case "refresh": - return refreshTokenCookieKey; - default: - throw new Error("Unknown token type, should never happen."); - } -} -exports.getCookieNameFromTokenType = getCookieNameFromTokenType; -function getResponseHeaderNameForTokenType(tokenType) { - switch (tokenType) { - case "access": - return accessTokenHeaderKey; - case "refresh": - return refreshTokenHeaderKey; - default: - throw new Error("Unknown token type, should never happen."); - } -} -exports.getResponseHeaderNameForTokenType = getResponseHeaderNameForTokenType; -function getToken(req, tokenType, transferMethod) { +function getToken(config, req, tokenType, transferMethod, userContext) { if (transferMethod === "cookie") { - return req.getCookieValue(getCookieNameFromTokenType(tokenType)); + return req.getCookieValue(config.getCookieNameFromTokenType(req, tokenType, userContext)); } else if (transferMethod === "header") { - const value = req.getHeaderValue(authorizationHeaderKey); + const value = req.getHeaderValue(constants_2.authorizationHeaderKey); if (value === undefined || !value.startsWith("Bearer ")) { return undefined; } @@ -126,7 +101,7 @@ function setToken(config, res, tokenType, value, expires, transferMethod, req, u setCookie( config, res, - getCookieNameFromTokenType(tokenType), + config.getCookieNameFromTokenType(req, tokenType, userContext), value, expires, tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", @@ -134,7 +109,7 @@ function setToken(config, res, tokenType, value, expires, transferMethod, req, u userContext ); } else if (transferMethod === "header") { - setHeader(res, getResponseHeaderNameForTokenType(tokenType), value); + setHeader(res, config.getResponseHeaderNameForTokenType(req, tokenType, userContext), value); } } exports.setToken = setToken; @@ -174,7 +149,9 @@ function setCookie(config, res, name, value, expires, pathType, req, userContext exports.setCookie = setCookie; function getAuthModeFromHeader(req) { var _a; - return (_a = req.getHeaderValue(authModeHeaderKey)) === null || _a === void 0 ? void 0 : _a.toLowerCase(); + return (_a = req.getHeaderValue(constants_2.authModeHeaderKey)) === null || _a === void 0 + ? void 0 + : _a.toLowerCase(); } exports.getAuthModeFromHeader = getAuthModeFromHeader; /** @@ -202,7 +179,7 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex let didClearCookies = false; const tokenTypes = ["access", "refresh"]; for (const token of tokenTypes) { - if (hasMultipleCookiesForTokenType(req, token)) { + if (hasMultipleCookiesForTokenType(config, req, token, userContext)) { // If a request has multiple session cookies and 'olderCookieDomain' is // unset, we can't identify the correct cookie for refreshing the session. // Using the wrong cookie can cause an infinite refresh loop. To avoid this, @@ -237,13 +214,13 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex } } exports.clearSessionCookiesFromOlderCookieDomain = clearSessionCookiesFromOlderCookieDomain; -function hasMultipleCookiesForTokenType(req, tokenType) { +function hasMultipleCookiesForTokenType(config, req, tokenType, userContext) { const cookieString = req.getHeaderValue("cookie"); if (cookieString === undefined) { return false; } const cookies = parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString); - const cookieName = getCookieNameFromTokenType(tokenType); + const cookieName = config.getCookieNameFromTokenType(req, tokenType, userContext); return cookies[cookieName] !== undefined && cookies[cookieName].length > 1; } exports.hasMultipleCookiesForTokenType = hasMultipleCookiesForTokenType; diff --git a/lib/build/recipe/session/sessionRequestFunctions.d.ts b/lib/build/recipe/session/sessionRequestFunctions.d.ts index 3002f3bd2..46796682e 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.d.ts +++ b/lib/build/recipe/session/sessionRequestFunctions.d.ts @@ -26,8 +26,10 @@ export declare function getSessionFromRequest({ userContext: UserContext; }): Promise; export declare function getAccessTokenFromRequest( + config: TypeNormalisedInput, req: any, - allowedTransferMethod: TokenTransferMethod | "any" + allowedTransferMethod: TokenTransferMethod | "any", + userContext: UserContext ): { requestTransferMethod: TokenTransferMethod | undefined; accessToken: ParsedJWTInfo | undefined; diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index df0a56f0e..d3a49abbe 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -50,7 +50,12 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op forCreateNewSession: false, userContext, }); - const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(req, allowedTransferMethod); + const { requestTransferMethod, accessToken } = getAccessTokenFromRequest( + config, + req, + allowedTransferMethod, + userContext + ); let antiCsrfToken = cookieAndHeaders_1.getAntiCsrfTokenFromHeaders(req); let doAntiCsrfCheck = options !== undefined ? options.antiCsrfCheck : undefined; if (doAntiCsrfCheck === undefined) { @@ -123,11 +128,11 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op return session; } exports.getSessionFromRequest = getSessionFromRequest; -function getAccessTokenFromRequest(req, allowedTransferMethod) { +function getAccessTokenFromRequest(config, req, allowedTransferMethod, userContext) { const accessTokens = {}; // We check all token transfer methods for available access tokens for (const transferMethod of constants_1.availableTokenTransferMethods) { - const tokenString = cookieAndHeaders_1.getToken(req, "access", transferMethod); + const tokenString = cookieAndHeaders_1.getToken(config, req, "access", transferMethod, userContext); if (tokenString !== undefined) { try { const info = jwt_1.parseJWTWithoutSignatureVerification(tokenString); @@ -158,7 +163,12 @@ function getAccessTokenFromRequest(req, allowedTransferMethod) { // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). // ensuring outdated token payload isn't used. - const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access"); + const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType( + config, + req, + "access", + userContext + ); if (hasMultipleAccessTokenCookies) { logger_1.logDebugMessage( "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" @@ -196,7 +206,13 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn // We check all token transfer methods for available refresh tokens // We do this so that we can later clear all we are not overwriting for (const transferMethod of constants_1.availableTokenTransferMethods) { - refreshTokens[transferMethod] = cookieAndHeaders_1.getToken(req, "refresh", transferMethod); + refreshTokens[transferMethod] = cookieAndHeaders_1.getToken( + config, + req, + "refresh", + transferMethod, + userContext + ); if (refreshTokens[transferMethod] !== undefined) { logger_1.logDebugMessage("refreshSession: got refresh token from " + transferMethod); } @@ -244,7 +260,7 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn // See: https://github.com/supertokens/supertokens-node/issues/790 if ( (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined + cookieAndHeaders_1.getToken(config, req, "access", "cookie", userContext) !== undefined ) { logger_1.logDebugMessage( "refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined" @@ -446,7 +462,7 @@ async function createNewSessionInRequest({ for (const transferMethod of constants_1.availableTokenTransferMethods) { if ( transferMethod !== outputTransferMethod && - cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined + cookieAndHeaders_1.getToken(config, req, "access", transferMethod, userContext) !== undefined ) { cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext); } diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index 14ef7e785..186fab33d 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -51,6 +51,8 @@ export declare type TypeInput = { forCreateNewSession: boolean; userContext: UserContext; }) => TokenTransferMethod | "any"; + getCookieNameFromTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; + getResponseHeaderNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; errorHandlers?: ErrorHandlers; antiCsrf?: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE"; exposeAccessTokenToFrontendInCookieBasedAuth?: boolean; @@ -74,6 +76,8 @@ export declare type TypeNormalisedInput = { userContext: UserContext; }) => "strict" | "lax" | "none"; cookieSecure: boolean; + getCookieNameFromTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; + getResponseHeaderNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; sessionExpiredStatusCode: number; errorHandlers: NormalisedErrorHandlers; antiCsrfFunctionOrString: diff --git a/lib/build/recipe/session/utils.d.ts b/lib/build/recipe/session/utils.d.ts index 24ce87cb5..ad33595c7 100644 --- a/lib/build/recipe/session/utils.d.ts +++ b/lib/build/recipe/session/utils.d.ts @@ -7,6 +7,7 @@ import { SessionContainerInterface, VerifySessionOptions, TokenTransferMethod, + TokenType, } from "./types"; import SessionRecipe from "./recipe"; import { NormalisedAppinfo, UserContext } from "../../types"; @@ -56,7 +57,7 @@ export declare function setAccessTokenInResponse( frontToken: string, config: TypeNormalisedInput, transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, + req: BaseRequest, userContext: UserContext ): void; export declare function getRequiredClaimValidators( @@ -74,3 +75,11 @@ export declare function validateClaimsInPayload( reason: import("../../types").JSONValue; }[] >; +export declare function getCookieNameFromTokenType( + _req: BaseRequest, + tokenType: TokenType +): "sAccessToken" | "sRefreshToken"; +export declare function getResponseHeaderNameForTokenType( + _req: BaseRequest, + tokenType: TokenType +): "st-access-token" | "st-refresh-token"; diff --git a/lib/build/recipe/session/utils.js b/lib/build/recipe/session/utils.js index d39bbaf15..de84f8407 100644 --- a/lib/build/recipe/session/utils.js +++ b/lib/build/recipe/session/utils.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.validateClaimsInPayload = exports.getRequiredClaimValidators = exports.setAccessTokenInResponse = exports.normaliseSameSiteOrThrowError = exports.validateAndNormaliseUserInput = exports.getURLProtocol = exports.normaliseSessionScopeOrThrowError = exports.sendTokenTheftDetectedResponse = exports.sendInvalidClaimResponse = exports.sendUnauthorisedResponse = exports.sendTryRefreshTokenResponse = void 0; +exports.getResponseHeaderNameForTokenType = exports.getCookieNameFromTokenType = exports.validateClaimsInPayload = exports.getRequiredClaimValidators = exports.setAccessTokenInResponse = exports.normaliseSameSiteOrThrowError = exports.validateAndNormaliseUserInput = exports.getURLProtocol = exports.normaliseSessionScopeOrThrowError = exports.sendTokenTheftDetectedResponse = exports.sendInvalidClaimResponse = exports.sendUnauthorisedResponse = exports.sendTryRefreshTokenResponse = void 0; const cookieAndHeaders_1 = require("./cookieAndHeaders"); const recipe_1 = __importDefault(require("./recipe")); const constants_1 = require("./constants"); @@ -89,7 +89,7 @@ function getURLProtocol(url) { } exports.getURLProtocol = getURLProtocol; function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { - var _a, _b, _c, _d; + var _a, _b, _c, _d, _e, _f; let cookieDomain = config === undefined || config.cookieDomain === undefined ? undefined @@ -221,6 +221,16 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { (config === null || config === void 0 ? void 0 : config.getTokenTransferMethod) === undefined ? defaultGetTokenTransferMethod : config.getTokenTransferMethod, + getCookieNameFromTokenType: + (_d = config === null || config === void 0 ? void 0 : config.getCookieNameFromTokenType) !== null && + _d !== void 0 + ? _d + : getCookieNameFromTokenType, + getResponseHeaderNameForTokenType: + (_e = config === null || config === void 0 ? void 0 : config.getResponseHeaderNameForTokenType) !== null && + _e !== void 0 + ? _e + : getResponseHeaderNameForTokenType, cookieDomain, olderCookieDomain, getCookieSameSite: cookieSameSite, @@ -231,9 +241,9 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { override, invalidClaimStatusCode, jwksRefreshIntervalSec: - (_d = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && - _d !== void 0 - ? _d + (_f = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && + _f !== void 0 + ? _f : 3600 * 4, }; } @@ -331,3 +341,25 @@ function defaultGetTokenTransferMethod({ req, forCreateNewSession }) { return "any"; } } +function getCookieNameFromTokenType(_req, tokenType) { + switch (tokenType) { + case "access": + return constants_1.accessTokenCookieKey; + case "refresh": + return constants_1.refreshTokenCookieKey; + default: + throw new Error("Unknown token type, should never happen."); + } +} +exports.getCookieNameFromTokenType = getCookieNameFromTokenType; +function getResponseHeaderNameForTokenType(_req, tokenType) { + switch (tokenType) { + case "access": + return constants_1.accessTokenHeaderKey; + case "refresh": + return constants_1.refreshTokenHeaderKey; + default: + throw new Error("Unknown token type, should never happen."); + } +} +exports.getResponseHeaderNameForTokenType = getResponseHeaderNameForTokenType; diff --git a/lib/ts/recipe/session/constants.ts b/lib/ts/recipe/session/constants.ts index e7a5c296a..7ed524d3c 100644 --- a/lib/ts/recipe/session/constants.ts +++ b/lib/ts/recipe/session/constants.ts @@ -36,3 +36,14 @@ export const protectedProps = [ "tId", "stt", ]; +export const authorizationHeaderKey = "authorization"; +export const accessTokenHeaderKey = "st-access-token"; +export const accessTokenCookieKey = "sAccessToken"; +export const refreshTokenCookieKey = "sRefreshToken"; +export const refreshTokenHeaderKey = "st-refresh-token"; + +export const antiCsrfHeaderKey = "anti-csrf"; + +export const frontTokenHeaderKey = "front-token"; + +export const authModeHeaderKey = "st-auth-mode"; diff --git a/lib/ts/recipe/session/cookieAndHeaders.ts b/lib/ts/recipe/session/cookieAndHeaders.ts index 6c3180f40..c80f604ba 100644 --- a/lib/ts/recipe/session/cookieAndHeaders.ts +++ b/lib/ts/recipe/session/cookieAndHeaders.ts @@ -17,26 +17,20 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import { logDebugMessage } from "../../logger"; import { UserContext } from "../../types"; import { encodeBase64 } from "../../utils"; -import { availableTokenTransferMethods } from "./constants"; +import { + availableTokenTransferMethods, + authorizationHeaderKey, + antiCsrfHeaderKey, + frontTokenHeaderKey, + authModeHeaderKey, +} from "./constants"; import SessionError from "./error"; import { TokenTransferMethod, TokenType, TypeNormalisedInput } from "./types"; -const authorizationHeaderKey = "authorization"; -const accessTokenCookieKey = "sAccessToken"; -const accessTokenHeaderKey = "st-access-token"; -const refreshTokenCookieKey = "sRefreshToken"; -const refreshTokenHeaderKey = "st-refresh-token"; - -const antiCsrfHeaderKey = "anti-csrf"; - -const frontTokenHeaderKey = "front-token"; - -const authModeHeaderKey = "st-auth-mode"; - export function clearSessionFromAllTokenTransferMethods( config: TypeNormalisedInput, res: BaseResponse, - request: BaseRequest | undefined, + request: BaseRequest, userContext: UserContext ) { // We are clearing the session in all transfermethods to be sure to override cookies in case they have been already added to the response. @@ -54,7 +48,7 @@ export function clearSession( config: TypeNormalisedInput, res: BaseResponse, transferMethod: TokenTransferMethod, - request: BaseRequest | undefined, + request: BaseRequest, userContext: UserContext ) { // If we can be specific about which transferMethod we want to clear, there is no reason to clear the other ones @@ -96,31 +90,15 @@ export function getCORSAllowedHeaders(): string[] { return [antiCsrfHeaderKey, HEADER_RID, authorizationHeaderKey, authModeHeaderKey]; } -export function getCookieNameFromTokenType(tokenType: TokenType) { - switch (tokenType) { - case "access": - return accessTokenCookieKey; - case "refresh": - return refreshTokenCookieKey; - default: - throw new Error("Unknown token type, should never happen."); - } -} - -export function getResponseHeaderNameForTokenType(tokenType: TokenType) { - switch (tokenType) { - case "access": - return accessTokenHeaderKey; - case "refresh": - return refreshTokenHeaderKey; - default: - throw new Error("Unknown token type, should never happen."); - } -} - -export function getToken(req: BaseRequest, tokenType: TokenType, transferMethod: TokenTransferMethod) { +export function getToken( + config: TypeNormalisedInput, + req: BaseRequest, + tokenType: TokenType, + transferMethod: TokenTransferMethod, + userContext: UserContext +) { if (transferMethod === "cookie") { - return req.getCookieValue(getCookieNameFromTokenType(tokenType)); + return req.getCookieValue(config.getCookieNameFromTokenType(req, tokenType, userContext)); } else if (transferMethod === "header") { const value = req.getHeaderValue(authorizationHeaderKey); if (value === undefined || !value.startsWith("Bearer ")) { @@ -140,7 +118,7 @@ export function setToken( value: string, expires: number, transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, + req: BaseRequest, userContext: UserContext ) { logDebugMessage(`setToken: Setting ${tokenType} token as ${transferMethod}`); @@ -148,7 +126,7 @@ export function setToken( setCookie( config, res, - getCookieNameFromTokenType(tokenType), + config.getCookieNameFromTokenType(req, tokenType, userContext), value, expires, tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", @@ -156,7 +134,7 @@ export function setToken( userContext ); } else if (transferMethod === "header") { - setHeader(res, getResponseHeaderNameForTokenType(tokenType), value); + setHeader(res, config.getResponseHeaderNameForTokenType(req, tokenType, userContext), value); } } @@ -246,7 +224,7 @@ export function clearSessionCookiesFromOlderCookieDomain({ const tokenTypes: TokenType[] = ["access", "refresh"]; for (const token of tokenTypes) { - if (hasMultipleCookiesForTokenType(req, token)) { + if (hasMultipleCookiesForTokenType(config, req, token, userContext)) { // If a request has multiple session cookies and 'olderCookieDomain' is // unset, we can't identify the correct cookie for refreshing the session. // Using the wrong cookie can cause an infinite refresh loop. To avoid this, @@ -283,7 +261,12 @@ export function clearSessionCookiesFromOlderCookieDomain({ } } -export function hasMultipleCookiesForTokenType(req: BaseRequest, tokenType: TokenType): boolean { +export function hasMultipleCookiesForTokenType( + config: TypeNormalisedInput, + req: BaseRequest, + tokenType: TokenType, + userContext: UserContext +): boolean { const cookieString = req.getHeaderValue("cookie"); if (cookieString === undefined) { @@ -291,7 +274,7 @@ export function hasMultipleCookiesForTokenType(req: BaseRequest, tokenType: Toke } const cookies = parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString); - const cookieName = getCookieNameFromTokenType(tokenType); + const cookieName = config.getCookieNameFromTokenType(req, tokenType, userContext); return cookies[cookieName] !== undefined && cookies[cookieName].length > 1; } diff --git a/lib/ts/recipe/session/sessionRequestFunctions.ts b/lib/ts/recipe/session/sessionRequestFunctions.ts index cde8c6744..1c5ba176b 100644 --- a/lib/ts/recipe/session/sessionRequestFunctions.ts +++ b/lib/ts/recipe/session/sessionRequestFunctions.ts @@ -80,7 +80,12 @@ export async function getSessionFromRequest({ userContext, }); - const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(req, allowedTransferMethod); + const { requestTransferMethod, accessToken } = getAccessTokenFromRequest( + config, + req, + allowedTransferMethod, + userContext + ); let antiCsrfToken = getAntiCsrfTokenFromHeaders(req); let doAntiCsrfCheck = options !== undefined ? options.antiCsrfCheck : undefined; @@ -161,14 +166,19 @@ export async function getSessionFromRequest({ return session; } -export function getAccessTokenFromRequest(req: any, allowedTransferMethod: TokenTransferMethod | "any") { +export function getAccessTokenFromRequest( + config: TypeNormalisedInput, + req: any, + allowedTransferMethod: TokenTransferMethod | "any", + userContext: UserContext +) { const accessTokens: { [key in TokenTransferMethod]?: ParsedJWTInfo; } = {}; // We check all token transfer methods for available access tokens for (const transferMethod of availableTokenTransferMethods) { - const tokenString = getToken(req, "access", transferMethod); + const tokenString = getToken(config, req, "access", transferMethod, userContext); if (tokenString !== undefined) { try { const info = parseJWTWithoutSignatureVerification(tokenString); @@ -202,7 +212,7 @@ export function getAccessTokenFromRequest(req: any, allowedTransferMethod: Token // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). // ensuring outdated token payload isn't used. - const hasMultipleAccessTokenCookies = hasMultipleCookiesForTokenType(req, "access"); + const hasMultipleAccessTokenCookies = hasMultipleCookiesForTokenType(config, req, "access", userContext); if (hasMultipleAccessTokenCookies) { logDebugMessage( "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" @@ -259,7 +269,7 @@ export async function refreshSessionInRequest({ // We check all token transfer methods for available refresh tokens // We do this so that we can later clear all we are not overwriting for (const transferMethod of availableTokenTransferMethods) { - refreshTokens[transferMethod] = getToken(req, "refresh", transferMethod); + refreshTokens[transferMethod] = getToken(config, req, "refresh", transferMethod, userContext); if (refreshTokens[transferMethod] !== undefined) { logDebugMessage("refreshSession: got refresh token from " + transferMethod); } @@ -300,7 +310,7 @@ export async function refreshSessionInRequest({ // See: https://github.com/supertokens/supertokens-node/issues/790 if ( (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - getToken(req, "access", "cookie") !== undefined + getToken(config, req, "access", "cookie", userContext) !== undefined ) { logDebugMessage( "refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined" @@ -518,7 +528,10 @@ export async function createNewSessionInRequest({ logDebugMessage("createNewSession: Session created in core built"); for (const transferMethod of availableTokenTransferMethods) { - if (transferMethod !== outputTransferMethod && getToken(req, "access", transferMethod) !== undefined) { + if ( + transferMethod !== outputTransferMethod && + getToken(config, req, "access", transferMethod, userContext) !== undefined + ) { clearSession(config, res, transferMethod, req, userContext); } } diff --git a/lib/ts/recipe/session/types.ts b/lib/ts/recipe/session/types.ts index 371cdc34e..5d63faff2 100644 --- a/lib/ts/recipe/session/types.ts +++ b/lib/ts/recipe/session/types.ts @@ -74,6 +74,8 @@ export type TypeInput = { forCreateNewSession: boolean; userContext: UserContext; }) => TokenTransferMethod | "any"; + getCookieNameFromTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; + getResponseHeaderNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; errorHandlers?: ErrorHandlers; antiCsrf?: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE"; @@ -99,6 +101,8 @@ export type TypeNormalisedInput = { userContext: UserContext; }) => "strict" | "lax" | "none"; cookieSecure: boolean; + getCookieNameFromTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; + getResponseHeaderNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; sessionExpiredStatusCode: number; errorHandlers: NormalisedErrorHandlers; diff --git a/lib/ts/recipe/session/utils.ts b/lib/ts/recipe/session/utils.ts index 44f972dc0..3238a4689 100644 --- a/lib/ts/recipe/session/utils.ts +++ b/lib/ts/recipe/session/utils.ts @@ -22,10 +22,18 @@ import { SessionContainerInterface, VerifySessionOptions, TokenTransferMethod, + TokenType, } from "./types"; import { setFrontTokenInHeaders, setToken, getAuthModeFromHeader } from "./cookieAndHeaders"; import SessionRecipe from "./recipe"; -import { REFRESH_API_PATH, oneYearInMs } from "./constants"; +import { + REFRESH_API_PATH, + accessTokenCookieKey, + accessTokenHeaderKey, + oneYearInMs, + refreshTokenCookieKey, + refreshTokenHeaderKey, +} from "./constants"; import NormalisedURLPath from "../../normalisedURLPath"; import { NormalisedAppinfo, UserContext } from "../../types"; import { isAnIpAddress, send200Response } from "../../utils"; @@ -294,6 +302,9 @@ export function validateAndNormaliseUserInput( config?.getTokenTransferMethod === undefined ? defaultGetTokenTransferMethod : config.getTokenTransferMethod, + getCookieNameFromTokenType: config?.getCookieNameFromTokenType ?? getCookieNameFromTokenType, + getResponseHeaderNameForTokenType: + config?.getResponseHeaderNameForTokenType ?? getResponseHeaderNameForTokenType, cookieDomain, olderCookieDomain, getCookieSameSite: cookieSameSite, @@ -322,7 +333,7 @@ export function setAccessTokenInResponse( frontToken: string, config: TypeNormalisedInput, transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, + req: BaseRequest, userContext: UserContext ) { setFrontTokenInHeaders(res, frontToken); @@ -423,3 +434,25 @@ function defaultGetTokenTransferMethod({ return "any"; } } + +export function getCookieNameFromTokenType(_req: BaseRequest, tokenType: TokenType) { + switch (tokenType) { + case "access": + return accessTokenCookieKey; + case "refresh": + return refreshTokenCookieKey; + default: + throw new Error("Unknown token type, should never happen."); + } +} + +export function getResponseHeaderNameForTokenType(_req: BaseRequest, tokenType: TokenType) { + switch (tokenType) { + case "access": + return accessTokenHeaderKey; + case "refresh": + return refreshTokenHeaderKey; + default: + throw new Error("Unknown token type, should never happen."); + } +}