From f3fcaf8bd1e2d0f5d6a10cf0fe41fcc4607174d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mih=C3=A1ly=20Lengyel?= Date: Mon, 11 Sep 2023 08:14:32 +0200 Subject: [PATCH] feat: ignore protected props in createNewSession* (#690) --- CHANGELOG.md | 1 + lib/build/recipe/session/constants.d.ts | 3 +++ lib/build/recipe/session/constants.js | 15 ++++++++++++- lib/build/recipe/session/index.js | 4 ++++ .../recipe/session/recipeImplementation.d.ts | 2 -- .../recipe/session/recipeImplementation.js | 21 ++++--------------- lib/build/recipe/session/sessionClass.js | 6 +++--- lib/build/recipe/session/sessionFunctions.js | 4 ++-- .../recipe/session/sessionRequestFunctions.js | 3 +++ lib/ts/recipe/session/constants.ts | 15 +++++++++++++ lib/ts/recipe/session/index.ts | 5 +++++ lib/ts/recipe/session/recipeImplementation.ts | 16 +------------- lib/ts/recipe/session/sessionClass.ts | 3 ++- lib/ts/recipe/session/sessionFunctions.ts | 3 ++- .../recipe/session/sessionRequestFunctions.ts | 6 +++++- test/accountlinking/session.test.js | 2 +- test/session/accessTokenVersions.test.js | 13 +++++++----- 17 files changed, 73 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 260b65ba6..ddfdcee0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Now supporting FDI 1.18 - removed the recipe specific `User` type, now all functions are using the new generic `User` type - The `fetchValue` callback of claims now take a new `recipeUserId` param +- Now ignoring protected props in the payload in `createNewSession` and `createNewSessionWithoutRequestResponse` - EmailPassword: - removed `getUserById`, `getUserByEmail` diff --git a/lib/build/recipe/session/constants.d.ts b/lib/build/recipe/session/constants.d.ts index 77315f542..bff82f168 100644 --- a/lib/build/recipe/session/constants.d.ts +++ b/lib/build/recipe/session/constants.d.ts @@ -4,3 +4,6 @@ export declare const REFRESH_API_PATH = "/session/refresh"; export declare const SIGNOUT_API_PATH = "/signout"; export declare const availableTokenTransferMethods: TokenTransferMethod[]; export declare const hundredYearsInMs = 3153600000000; +export declare const JWKCacheCooldownInMs = 500; +export declare const JWKCacheMaxAgeInMs = 60000; +export declare const protectedProps: string[]; diff --git a/lib/build/recipe/session/constants.js b/lib/build/recipe/session/constants.js index 06ebc6c0a..e653436ba 100644 --- a/lib/build/recipe/session/constants.js +++ b/lib/build/recipe/session/constants.js @@ -14,8 +14,21 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.hundredYearsInMs = exports.availableTokenTransferMethods = exports.SIGNOUT_API_PATH = exports.REFRESH_API_PATH = void 0; +exports.protectedProps = exports.JWKCacheMaxAgeInMs = exports.JWKCacheCooldownInMs = exports.hundredYearsInMs = 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"]; exports.hundredYearsInMs = 3153600000000; +exports.JWKCacheCooldownInMs = 500; +exports.JWKCacheMaxAgeInMs = 60000; +exports.protectedProps = [ + "sub", + "iat", + "exp", + "sessionHandle", + "parentRefreshTokenHash1", + "refreshTokenHash1", + "antiCsrfToken", + "rsub", + "tId", +]; diff --git a/lib/build/recipe/session/index.js b/lib/build/recipe/session/index.js index a11a3e9d5..82b519987 100644 --- a/lib/build/recipe/session/index.js +++ b/lib/build/recipe/session/index.js @@ -26,6 +26,7 @@ const utils_1 = require("./utils"); const sessionRequestFunctions_1 = require("./sessionRequestFunctions"); const __1 = require("../.."); const constants_1 = require("../multitenancy/constants"); +const constants_2 = require("./constants"); class SessionWrapper { static async createNewSession( req, @@ -71,6 +72,9 @@ class SessionWrapper { const appInfo = recipeInstance.getAppInfo(); const issuer = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); let finalAccessTokenPayload = Object.assign(Object.assign({}, accessTokenPayload), { iss: issuer }); + for (const prop of constants_2.protectedProps) { + delete finalAccessTokenPayload[prop]; + } let user = await __1.getUser(recipeUserId.getAsString(), userContext); let userId = recipeUserId.getAsString(); if (user !== undefined) { diff --git a/lib/build/recipe/session/recipeImplementation.d.ts b/lib/build/recipe/session/recipeImplementation.d.ts index 5af8072a1..9c1969e84 100644 --- a/lib/build/recipe/session/recipeImplementation.d.ts +++ b/lib/build/recipe/session/recipeImplementation.d.ts @@ -10,8 +10,6 @@ export declare type Helpers = { appInfo: NormalisedAppinfo; getRecipeImpl: () => RecipeInterface; }; -export declare const JWKCacheMaxAgeInMs = 60000; -export declare const protectedProps: string[]; export default function getRecipeInterface( querier: Querier, config: TypeNormalisedInput, diff --git a/lib/build/recipe/session/recipeImplementation.js b/lib/build/recipe/session/recipeImplementation.js index 7e2bb82be..99edb7219 100644 --- a/lib/build/recipe/session/recipeImplementation.js +++ b/lib/build/recipe/session/recipeImplementation.js @@ -41,7 +41,6 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.protectedProps = exports.JWKCacheMaxAgeInMs = void 0; const jose_1 = require("jose"); const SessionFunctions = __importStar(require("./sessionFunctions")); const cookieAndHeaders_1 = require("./cookieAndHeaders"); @@ -54,24 +53,12 @@ const accessToken_1 = require("./accessToken"); const error_1 = __importDefault(require("./error")); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const constants_1 = require("../multitenancy/constants"); -const JWKCacheCooldownInMs = 500; -exports.JWKCacheMaxAgeInMs = 60000; -exports.protectedProps = [ - "sub", - "iat", - "exp", - "sessionHandle", - "parentRefreshTokenHash1", - "refreshTokenHash1", - "antiCsrfToken", - "rsub", - "tId", -]; +const constants_2 = require("./constants"); function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverrides) { const JWKS = querier.getAllCoreUrlsForPath("/.well-known/jwks.json").map((url) => jose_1.createRemoteJWKSet(new URL(url), { - cooldownDuration: JWKCacheCooldownInMs, - cacheMaxAge: exports.JWKCacheMaxAgeInMs, + cooldownDuration: constants_2.JWKCacheCooldownInMs, + cacheMaxAge: constants_2.JWKCacheMaxAgeInMs, }) ); /** @@ -362,7 +349,7 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride return false; } let newAccessTokenPayload = Object.assign({}, sessionInfo.customClaimsInAccessTokenPayload); - for (const key of exports.protectedProps) { + for (const key of constants_2.protectedProps) { delete newAccessTokenPayload[key]; } newAccessTokenPayload = Object.assign(Object.assign({}, newAccessTokenPayload), accessTokenPayloadUpdate); diff --git a/lib/build/recipe/session/sessionClass.js b/lib/build/recipe/session/sessionClass.js index dfc2ed91d..ec1a2ddd6 100644 --- a/lib/build/recipe/session/sessionClass.js +++ b/lib/build/recipe/session/sessionClass.js @@ -21,10 +21,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); */ const cookieAndHeaders_1 = require("./cookieAndHeaders"); const error_1 = __importDefault(require("./error")); -const recipeImplementation_1 = require("./recipeImplementation"); const utils_1 = require("./utils"); const jwt_1 = require("./jwt"); const logger_1 = require("../../logger"); +const constants_1 = require("./constants"); class Session { constructor( helpers, @@ -132,7 +132,7 @@ class Session { // Any update to this function should also be reflected in the respective JWT version async mergeIntoAccessTokenPayload(accessTokenPayloadUpdate, userContext) { let newAccessTokenPayload = Object.assign({}, this.getAccessTokenPayload(userContext)); - for (const key of recipeImplementation_1.protectedProps) { + for (const key of constants_1.protectedProps) { delete newAccessTokenPayload[key]; } newAccessTokenPayload = Object.assign(Object.assign({}, newAccessTokenPayload), accessTokenPayloadUpdate); @@ -220,7 +220,7 @@ class Session { userContext, }); if (validateClaimResponse.accessTokenPayloadUpdate !== undefined) { - for (const key of recipeImplementation_1.protectedProps) { + for (const key of constants_1.protectedProps) { delete validateClaimResponse.accessTokenPayloadUpdate[key]; } await this.mergeIntoAccessTokenPayload(validateClaimResponse.accessTokenPayloadUpdate, userContext); diff --git a/lib/build/recipe/session/sessionFunctions.js b/lib/build/recipe/session/sessionFunctions.js index 9bc026a23..dcb3b07b1 100644 --- a/lib/build/recipe/session/sessionFunctions.js +++ b/lib/build/recipe/session/sessionFunctions.js @@ -24,11 +24,11 @@ const accessToken_1 = require("./accessToken"); const error_1 = __importDefault(require("./error")); const processState_1 = require("../../processState"); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); -const recipeImplementation_1 = require("./recipeImplementation"); const utils_1 = require("../../utils"); const logger_1 = require("../../logger"); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const constants_1 = require("../multitenancy/constants"); +const constants_2 = require("./constants"); /** * @description call this to "login" a user. */ @@ -129,7 +129,7 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC } // We check if the token was created since the last time we refreshed the keys from the core // Since we do not know the exact timing of the last refresh, we check against the max age - if (timeCreated <= Date.now() - recipeImplementation_1.JWKCacheMaxAgeInMs) { + if (timeCreated <= Date.now() - constants_2.JWKCacheMaxAgeInMs) { throw err; } } else { diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index 1b7f2a417..27a22477a 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -295,6 +295,9 @@ async function createNewSessionInRequest({ const claimsAddedByOtherRecipes = recipeInstance.getClaimsAddedByOtherRecipes(); const issuer = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); let finalAccessTokenPayload = Object.assign(Object.assign({}, accessTokenPayload), { iss: issuer }); + for (const prop of constants_1.protectedProps) { + delete finalAccessTokenPayload[prop]; + } for (const claim of claimsAddedByOtherRecipes) { const update = await claim.build(userId, recipeUserId, tenantId, userContext); finalAccessTokenPayload = Object.assign(Object.assign({}, finalAccessTokenPayload), update); diff --git a/lib/ts/recipe/session/constants.ts b/lib/ts/recipe/session/constants.ts index d6e8ac58a..a5c903b81 100644 --- a/lib/ts/recipe/session/constants.ts +++ b/lib/ts/recipe/session/constants.ts @@ -21,3 +21,18 @@ export const SIGNOUT_API_PATH = "/signout"; export const availableTokenTransferMethods: TokenTransferMethod[] = ["cookie", "header"]; export const hundredYearsInMs = 3153600000000; + +export const JWKCacheCooldownInMs = 500; +export const JWKCacheMaxAgeInMs = 60000; + +export const protectedProps = [ + "sub", + "iat", + "exp", + "sessionHandle", + "parentRefreshTokenHash1", + "refreshTokenHash1", + "antiCsrfToken", + "rsub", + "tId", +]; diff --git a/lib/ts/recipe/session/index.ts b/lib/ts/recipe/session/index.ts index 34e0dc099..d207cc97f 100644 --- a/lib/ts/recipe/session/index.ts +++ b/lib/ts/recipe/session/index.ts @@ -32,6 +32,7 @@ import { createNewSessionInRequest, getSessionFromRequest, refreshSessionInReque import RecipeUserId from "../../recipeUserId"; import { getUser } from "../.."; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; +import { protectedProps } from "./constants"; export default class SessionWrapper { static init = Recipe.init; @@ -90,6 +91,10 @@ export default class SessionWrapper { iss: issuer, }; + for (const prop of protectedProps) { + delete finalAccessTokenPayload[prop]; + } + let user = await getUser(recipeUserId.getAsString(), userContext); let userId = recipeUserId.getAsString(); if (user !== undefined) { diff --git a/lib/ts/recipe/session/recipeImplementation.ts b/lib/ts/recipe/session/recipeImplementation.ts index bd016b2d7..94c4e8fba 100644 --- a/lib/ts/recipe/session/recipeImplementation.ts +++ b/lib/ts/recipe/session/recipeImplementation.ts @@ -22,6 +22,7 @@ import { validateAccessTokenStructure } from "./accessToken"; import SessionError from "./error"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; +import { JWKCacheCooldownInMs, JWKCacheMaxAgeInMs, protectedProps } from "./constants"; export type Helpers = { querier: Querier; @@ -31,21 +32,6 @@ export type Helpers = { getRecipeImpl: () => RecipeInterface; }; -const JWKCacheCooldownInMs = 500; -export const JWKCacheMaxAgeInMs = 60000; - -export const protectedProps = [ - "sub", - "iat", - "exp", - "sessionHandle", - "parentRefreshTokenHash1", - "refreshTokenHash1", - "antiCsrfToken", - "rsub", - "tId", -]; - export default function getRecipeInterface( querier: Querier, config: TypeNormalisedInput, diff --git a/lib/ts/recipe/session/sessionClass.ts b/lib/ts/recipe/session/sessionClass.ts index e12a49b14..ba0df0349 100644 --- a/lib/ts/recipe/session/sessionClass.ts +++ b/lib/ts/recipe/session/sessionClass.ts @@ -15,11 +15,12 @@ import { buildFrontToken, clearSession, setAntiCsrfTokenInHeaders, setToken } from "./cookieAndHeaders"; import STError from "./error"; import { SessionClaim, SessionClaimValidator, SessionContainerInterface, ReqResInfo, TokenInfo } from "./types"; -import { Helpers, protectedProps } from "./recipeImplementation"; +import { Helpers } from "./recipeImplementation"; import { setAccessTokenInResponse } from "./utils"; import { parseJWTWithoutSignatureVerification } from "./jwt"; import { logDebugMessage } from "../../logger"; import RecipeUserId from "../../recipeUserId"; +import { protectedProps } from "./constants"; export default class Session implements SessionContainerInterface { constructor( diff --git a/lib/ts/recipe/session/sessionFunctions.ts b/lib/ts/recipe/session/sessionFunctions.ts index cccc09864..d2c1ab71c 100644 --- a/lib/ts/recipe/session/sessionFunctions.ts +++ b/lib/ts/recipe/session/sessionFunctions.ts @@ -18,11 +18,12 @@ import STError from "./error"; import { PROCESS_STATE, ProcessState } from "../../processState"; import { CreateOrRefreshAPIResponse, SessionInformation } from "./types"; import NormalisedURLPath from "../../normalisedURLPath"; -import { Helpers, JWKCacheMaxAgeInMs } from "./recipeImplementation"; +import { Helpers } from "./recipeImplementation"; import { maxVersion } from "../../utils"; import { logDebugMessage } from "../../logger"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; +import { JWKCacheMaxAgeInMs } from "./constants"; /** * @description call this to "login" a user. diff --git a/lib/ts/recipe/session/sessionRequestFunctions.ts b/lib/ts/recipe/session/sessionRequestFunctions.ts index c61d5ef1d..7ee13b775 100644 --- a/lib/ts/recipe/session/sessionRequestFunctions.ts +++ b/lib/ts/recipe/session/sessionRequestFunctions.ts @@ -11,7 +11,7 @@ import SuperTokens from "../../supertokens"; import { getRequiredClaimValidators } from "./utils"; import { getRidFromHeader, isAnIpAddress, normaliseHttpMethod, setRequestInUserContextIfNotDefined } from "../../utils"; import { logDebugMessage } from "../../logger"; -import { availableTokenTransferMethods } from "./constants"; +import { availableTokenTransferMethods, protectedProps } from "./constants"; import { clearSession, getAntiCsrfTokenFromHeaders, getToken, setCookie } from "./cookieAndHeaders"; import { ParsedJWTInfo, parseJWTWithoutSignatureVerification } from "./jwt"; import { validateAccessTokenStructure } from "./accessToken"; @@ -359,6 +359,10 @@ export async function createNewSessionInRequest({ iss: issuer, }; + for (const prop of protectedProps) { + delete finalAccessTokenPayload[prop]; + } + for (const claim of claimsAddedByOtherRecipes) { const update = await claim.build(userId, recipeUserId, tenantId, userContext); finalAccessTokenPayload = { diff --git a/test/accountlinking/session.test.js b/test/accountlinking/session.test.js index 535959f8a..1c93cff19 100644 --- a/test/accountlinking/session.test.js +++ b/test/accountlinking/session.test.js @@ -33,7 +33,7 @@ let EmailVerification = require("../../recipe/emailverification"); const express = require("express"); const request = require("supertest"); let { middleware, errorHandler } = require("../../framework/express"); -let { protectedProps } = require("../../lib/build/recipe/session/recipeImplementation"); +let { protectedProps } = require("../../lib/build/recipe/session/constants"); let { PrimitiveClaim } = require("../../lib/build/recipe/session/claimBaseClasses/primitiveClaim"); describe(`sessionTests: ${printPath("[test/accountlinking/session.test.js]")}`, function () { diff --git a/test/session/accessTokenVersions.test.js b/test/session/accessTokenVersions.test.js index 11ac5b25c..9faa000d9 100644 --- a/test/session/accessTokenVersions.test.js +++ b/test/session/accessTokenVersions.test.js @@ -129,7 +129,7 @@ describe(`AccessToken versions: ${printPath("[test/session/accessTokenVersions.t assert(parsedHeader.kid.startsWith("s-")); }); - it("should throw an error when adding protected props", async function () { + it("should ignore protected props", async function () { const connectionURI = await startST(); SuperTokens.init({ supertokens: { @@ -156,7 +156,7 @@ describe(`AccessToken versions: ${printPath("[test/session/accessTokenVersions.t }, }) ) - .expect(400) + .expect(200) .end((err, resp) => { if (err) { rej(err); @@ -167,9 +167,12 @@ describe(`AccessToken versions: ${printPath("[test/session/accessTokenVersions.t ); let cookies = extractInfoFromResponse(res); - assert.strictEqual(cookies.accessTokenFromAny, undefined); - assert.strictEqual(cookies.refreshTokenFromAny, undefined); - assert.strictEqual(cookies.frontToken, undefined); + assert.notEqual(cookies.accessTokenFromAny, undefined); + assert.notEqual(cookies.refreshTokenFromAny, undefined); + assert.notEqual(cookies.frontToken, undefined); + + const parsedToken = parseJWTWithoutSignatureVerification(cookies.accessTokenFromAny); + assert.notEqual(parsedToken.payload.sub, "asdf"); }); it("should make sign in/up return a 500 when adding protected props", async function () {