From c363d6833fab1b36004bee0cfc0a2b31bd3d6ee1 Mon Sep 17 00:00:00 2001 From: dtoxvanilla1991 Date: Sun, 21 Dec 2025 20:42:19 -0500 Subject: [PATCH 1/2] feat: integrate @kinde/js-utils ExpressStore for session management - Replace deprecated Kinde TS SDK session handling with js-utils ExpressStore - Add @kinde/js-utils dependency (v0.29.0) - Add sessionManager tests - Update type definitions for express-session - Configure pnpm workspace --- package.json | 1 + pnpm-lock.yaml | 21 ++--- pnpm-workspace.yaml | 2 + src/index.ts | 29 ++++--- src/mocks.ts | 3 +- src/setup/index.ts | 30 ++----- src/setup/kindeSetupTypes.ts | 10 +-- src/setup/sessionManager.test.ts | 132 +++++++++++++++++++++++++++++++ src/setup/sessionManager.ts | 38 ++++----- src/types/express-session.d.ts | 19 +++-- vite.config.ts | 1 + 11 files changed, 202 insertions(+), 84 deletions(-) create mode 100644 pnpm-workspace.yaml create mode 100644 src/setup/sessionManager.test.ts diff --git a/package.json b/package.json index 8e225c0b..fc0033da 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "private": false, "dependencies": { "@kinde-oss/kinde-typescript-sdk": "^2.13.0", + "@kinde/js-utils": "^0.29.0", "@kinde/jwt-validator": "^0.4.0", "aws-jwt-verify": "^5.0.0", "eslint-config-prettier": "^10.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59b4d5e6..9e7fa1ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@kinde/js-utils': link:../js-utils + importers: .: @@ -11,6 +14,9 @@ importers: '@kinde-oss/kinde-typescript-sdk': specifier: ^2.13.0 version: 2.13.0(eslint@9.26.0)(typescript@5.8.3) + '@kinde/js-utils': + specifier: link:../js-utils + version: link:../js-utils '@kinde/jwt-validator': specifier: ^0.4.0 version: 0.4.0 @@ -359,14 +365,6 @@ packages: '@kinde-oss/kinde-typescript-sdk@2.13.0': resolution: {integrity: sha512-cBH9a6dKQRyk1Bfh0gfAPGt9CbofCbtw3Q27hWJ5QzcYTG6SlJ6O2T0ctQ0yImgvXnSGTajwXRACjXCceW454A==} - '@kinde/js-utils@0.23.0': - resolution: {integrity: sha512-FNg2GVH9vE3Nzhu4uBChVIvp1mJta86nc7itViEr+Scp6C0DPBNNUcTQBgZJTqp4fRUz4+MuKh0tNGvggA2ZzQ==} - peerDependencies: - expo-secure-store: '>=11.0.0' - peerDependenciesMeta: - expo-secure-store: - optional: true - '@kinde/jwt-decoder@0.2.0': resolution: {integrity: sha512-dqtwCmAvywOVLkkUfp4UbqdvVLsK0cvHsJhU3gDY9rgjAdZhGw0vCreBW6j3MFLxbi6cZm7pMU7/O5SJgvN5Rw==} @@ -2437,21 +2435,16 @@ snapshots: '@kinde-oss/kinde-typescript-sdk@2.13.0(eslint@9.26.0)(typescript@5.8.3)': dependencies: - '@kinde/js-utils': 0.23.0 + '@kinde/js-utils': link:../js-utils '@kinde/jwt-decoder': 0.2.0 '@kinde/jwt-validator': 0.4.0 '@typescript-eslint/parser': 8.31.1(eslint@9.26.0)(typescript@5.8.3) uncrypto: 0.1.3 transitivePeerDependencies: - eslint - - expo-secure-store - supports-color - typescript - '@kinde/js-utils@0.23.0': - dependencies: - '@kinde/jwt-decoder': 0.2.0 - '@kinde/jwt-decoder@0.2.0': {} '@kinde/jwt-validator@0.4.0': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..a42d214a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +overrides: + "@kinde/js-utils": link:../js-utils diff --git a/src/index.ts b/src/index.ts index 4b7b2cfd..74ebad3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,34 +1,33 @@ +import type { Express } from "express"; import { type SetupConfig, getInternalClient, setupInternalClient, } from "./setup/index.js"; import { setupAuthRouter } from "./auth/index.js"; -import type { Express } from "express"; import { jwtVerify } from "./helpers/kindeMiddlewareHelpers.js"; - -import { - managementApi, - GrantType, - Configuration, -} from "@kinde-oss/kinde-typescript-sdk"; - +import { setupKindeSession } from "./setup/sessionManager.js"; +import type { GrantType } from "@kinde-oss/kinde-typescript-sdk"; export * from "./helpers/index.js"; -export { managementApi, GrantType, Configuration, jwtVerify }; +export { jwtVerify }; /** - * Encapsulates Kinde setup by completing creating internal TypeScript SDK - * client, setting up its session manager interface and attaching auth router - * to provided express instance. + * Encapsulates Kinde setup for an Express application by setting up sessions, + * creating the client, and attaching authentication routes. * - * @param {Express} app - * @param {SetupConfig} config + * @param {SetupConfig} config The Kinde configuration object. + * @param {Express} app The Express application instance. + * @returns The initialized Kinde client. */ export const setupKinde = ( config: SetupConfig, app: Express, ) => { - setupInternalClient(app, config); + // setting up expressSession layer first for Kinde + setupKindeSession(app); + // initializing the Kinde SDK client + setupInternalClient(config); + // setting up the authentication routes setupAuthRouter(app, "/"); return getInternalClient(); }; diff --git a/src/mocks.ts b/src/mocks.ts index 4bc612c1..b6186ac0 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -1,4 +1,5 @@ -import { GrantType, setupKinde } from "./index.js"; +import { GrantType } from "@kinde-oss/kinde-typescript-sdk"; +import { setupKinde } from "./index.js"; import express from "express"; export const mockClientConfig = { diff --git a/src/setup/index.ts b/src/setup/index.ts index 1a9a216d..b96beb2c 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -1,24 +1,10 @@ -import { setupKindeSession } from "./sessionManager.js"; -import { setupInternalClient as setupKindeClient } from "./kindeClient.js"; -import type { GrantType } from "@kinde-oss/kinde-typescript-sdk"; -import type { SetupConfig } from "./kindeSetupTypes.js"; -import type { Express } from "express"; +// this file only re-exports the necessary functions and types from the other files +// in the setup directory +export { + getInternalClient, + getInitialConfig, + setupInternalClient, +} from "./kindeClient.js"; -export { getInternalClient, getInitialConfig } from "./kindeClient.js"; +export { getSessionManager, setupKindeSession } from "./sessionManager.js"; export * from "./kindeSetupTypes.js"; - -/** - * Encapsulates Kinde setup of creatint creating internal TypeScript SDK - * client, setting up its session manager interface and attaching session - * manager to provided express instance. - * - * @param {Express} app - * @param {SetupConfig} config - */ -export const setupInternalClient = ( - app: Express, - config: SetupConfig, -): void => { - setupKindeSession(app); - setupKindeClient(config); -}; diff --git a/src/setup/kindeSetupTypes.ts b/src/setup/kindeSetupTypes.ts index bff06f54..77020eeb 100644 --- a/src/setup/kindeSetupTypes.ts +++ b/src/setup/kindeSetupTypes.ts @@ -6,7 +6,7 @@ import type { CCClientOptions, } from "@kinde-oss/kinde-typescript-sdk"; -interface CommonSetupConfigParams { +export interface CommonSetupConfigParams { grantType: G; issuerBaseUrl: string; postLogoutRedirectUrl: string; @@ -16,20 +16,20 @@ interface CommonSetupConfigParams { scope?: string; } -interface ACSetupConfigParams +export interface ACSetupConfigParams extends CommonSetupConfigParams { secret: string; unAuthorisedUrl: string; redirectUrl: string; } -interface PKCESetupConfigParams +export interface PKCESetupConfigParams extends CommonSetupConfigParams { unAuthorisedUrl: string; redirectUrl: string; } -interface CCSetupConfigParams +export interface CCSetupConfigParams extends CommonSetupConfigParams { secret: string; } @@ -38,7 +38,7 @@ export type ClientType = ReturnType< typeof createKindeServerClient >; -export type SetupConfig = G extends GrantType.PKCE +export type SetupConfig = G extends GrantType.PKCE ? PKCESetupConfigParams : G extends GrantType.AUTHORIZATION_CODE ? ACSetupConfigParams diff --git a/src/setup/sessionManager.test.ts b/src/setup/sessionManager.test.ts new file mode 100644 index 00000000..d94b8ecc --- /dev/null +++ b/src/setup/sessionManager.test.ts @@ -0,0 +1,132 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import express, { type Express } from "express"; +import request from "supertest"; +import session from "express-session"; +import { setupKindeSession } from "./sessionManager.js"; + +describe("sessionManager", () => { + let app: Express; + + beforeEach(() => { + app = express(); + app.use(express.json()); + }); + + describe("setupKindeSession()", () => { + it("should add session middleware if it has not been added", () => { + setupKindeSession(app); + const sessionMiddleware = app._router.stack.find( + (layer) => layer.name === "session", + ); + expect(sessionMiddleware).toBeDefined(); + }); + + it("should not add session middleware if it has already been added", () => { + app.use( + session({ + secret: + process.env.TEST_SESSION_SECRET || "test-secret-" + Date.now(), + resave: false, + saveUninitialized: true, + }), + ); + const sessionMiddlewareCount = app._router.stack.filter( + (layer) => layer.name === "session", + ).length; + expect(sessionMiddlewareCount).toBe(1); + setupKindeSession(app); + const sessionMiddlewareCountAfter = app._router.stack.filter( + (layer) => layer.name === "session", + ).length; + expect(sessionMiddlewareCountAfter).toBe(1); + }); + + it("should add getSessionManager middleware", () => { + const initialStackLength = app._router.stack.length; + setupKindeSession(app); + const finalStackLength = app._router.stack.length; + expect(finalStackLength).toBeGreaterThan(initialStackLength); + }); + }); + + describe("ExpressSessionManager functionality", () => { + beforeEach(() => { + setupKindeSession(app); + app.get("/session-functions", (req, res) => { + res.json({ + setSessionItem: typeof req.setSessionItem === "function", + getSessionItem: typeof req.getSessionItem === "function", + removeSessionItem: typeof req.removeSessionItem === "function", + destroySession: typeof req.destroySession === "function", + }); + }); + + app.post("/set-item", async (req, res) => { + const { key, value } = req.body; + await req.setSessionItem(key, value); + res.sendStatus(200); + }); + + app.get("/get-item", async (req, res) => { + const { key } = req.query; + const value = await req.getSessionItem(key as string); + res.json({ value }); + }); + + app.post("/remove-item", async (req, res) => { + const { key } = req.body; + await req.removeSessionItem(key); + res.sendStatus(200); + }); + + app.post("/destroy", async (req, res) => { + try { + await req.destroySession(); + res.sendStatus(200); + } catch { + res.status(500).send("Error destroying session"); + } + }); + }); + + it("adds ExpressSessionManager methods to the request object", async () => { + const res = await request(app).get("/session-functions"); + expect(res.status).toBe(200); + expect(res.body).toEqual({ + setSessionItem: true, + getSessionItem: true, + removeSessionItem: true, + destroySession: true, + }); + }); + + it("sets and gets a session item via ExpressSessionManager", async () => { + const agent = request.agent(app); + await agent + .post("/set-item") + .send({ key: "testKey", value: "testValue" }); + const res = await agent.get("/get-item").query({ key: "testKey" }); + expect(res.body.value).toBe("testValue"); + }); + + it("removes a session item via ExpressSessionManager", async () => { + const agent = request.agent(app); + await agent + .post("/set-item") + .send({ key: "testKey", value: "testValue" }); + await agent.post("/remove-item").send({ key: "testKey" }); + const res = await agent.get("/get-item").query({ key: "testKey" }); + expect(res.body.value).toBe(null); + }); + + it("destroys the session via ExpressSessionManager", async () => { + const agent = request.agent(app); + await agent + .post("/set-item") + .send({ key: "testKey", value: "testValue" }); + await agent.post("/destroy"); + const res = await agent.get("/get-item").query({ key: "testKey" }); + expect(res.body.value).toBe(null); + }); + }); +}); diff --git a/src/setup/sessionManager.ts b/src/setup/sessionManager.ts index 6aaa838b..ccfa7168 100644 --- a/src/setup/sessionManager.ts +++ b/src/setup/sessionManager.ts @@ -1,6 +1,7 @@ import { ExpressMiddleware, randomString } from "../utils.js"; import type { Express, Request, Response, NextFunction } from "express"; import session, { type SessionOptions } from "express-session"; +import { ExpressSessionManager } from "@kinde/js-utils"; const SESSION_MAX_AGE: number = 1000 * 60 * 60 * 24; @@ -18,27 +19,27 @@ const sessionConfig: SessionOptions = { }; /** - * Sets up @type{import('@kinde-oss/kinde-typescript-sdk').SessionManager} as an - * as an express middleware, with the assumption that `express-session` package - * middleware has already been configured. + * Sets up the session manager by creating an instance of the + * `ExpressSessionManager` class from @kinde/js-utils for each request. * * @returns {ExpressMiddleware} */ -const getSessionManager = (): ExpressMiddleware => { +export const getSessionManager = (): ExpressMiddleware => { return (req: Request, _: Response, next: NextFunction) => { - req.setSessionItem = async (itemKey, itemValue) => { - req.session[itemKey] = itemValue; - }; - req.getSessionItem = async (itemKey) => { - return req.session[itemKey] ?? null; - }; - req.removeSessionItem = async (itemKey) => { - delete req.session[itemKey]; - }; - req.destroySession = async () => { - req.session.destroy((e) => console.log(e)); - }; - next(); + try { + // Ensuring the session is initialized + const manager = new ExpressSessionManager(req); + + // Now bridging the methods from @kinde/js-utils to req object + req.setSessionItem = manager.setSessionItem.bind(manager); + req.getSessionItem = manager.getSessionItem.bind(manager); + req.removeSessionItem = manager.removeSessionItem.bind(manager); + req.destroySession = manager.destroySession.bind(manager); + + next(); + } catch (error) { + next(error); + } }; }; @@ -51,8 +52,7 @@ const hasSessionMiddleware = (app: Express) => { }; /** - * Attaches the `express-session` middleware and the `SessionManager` for internal - * typescript SDK (in middleware form). + * Attaches the `express-session` middleware and the Kinde ExpressSessionManager. * @param {Express} app */ export const setupKindeSession = (app: Express): void => { diff --git a/src/types/express-session.d.ts b/src/types/express-session.d.ts index 419d3b70..cb9f2bda 100644 --- a/src/types/express-session.d.ts +++ b/src/types/express-session.d.ts @@ -1,18 +1,21 @@ -import type { SessionData } from "express-session"; -import type { SessionManager, UserType } from "@kinde-oss/kinde-typescript-sdk"; - declare module "express-session" { interface SessionData { [key: string]: unknown; } } - declare global { namespace Express { - export interface Request extends SessionManager { - user?: UserType; - session: Session & Partial; - sessionID: string; + interface Request { + session?: { + [key: string]: unknown; + destroy: (callback: (err?: Error | null) => void) => void; + }; + setSessionItem?: (itemKey: string, itemValue: unknown) => Promise; + getSessionItem?: (itemKey: string) => Promise; + removeSessionItem?: (itemKey: string) => Promise; + destroySession?: () => void; } } } + +export {}; diff --git a/vite.config.ts b/vite.config.ts index debeeeb0..25251dc3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ const external = [ "express", "express-session", "@kinde-oss/kinde-typescript-sdk", + "@kinde/js-utils", "@kinde/jwt-validator", "aws-jwt-verify", "crypto", From b949be778351a32b1efbedd13472432cff0b5117 Mon Sep 17 00:00:00 2001 From: dtoxvanilla1991 Date: Tue, 7 Apr 2026 23:45:03 +0100 Subject: [PATCH 2/2] feat: update @kinde/js-utils dependency version and remove workspace overrides --- pnpm-lock.yaml | 34 ++++++++++++++++++++++++++++------ pnpm-workspace.yaml | 2 -- 2 files changed, 28 insertions(+), 8 deletions(-) delete mode 100644 pnpm-workspace.yaml diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e7fa1ec..d21ed247 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - '@kinde/js-utils': link:../js-utils - importers: .: @@ -15,8 +12,8 @@ importers: specifier: ^2.13.0 version: 2.13.0(eslint@9.26.0)(typescript@5.8.3) '@kinde/js-utils': - specifier: link:../js-utils - version: link:../js-utils + specifier: ^0.29.0 + version: 0.29.0 '@kinde/jwt-validator': specifier: ^0.4.0 version: 0.4.0 @@ -365,6 +362,22 @@ packages: '@kinde-oss/kinde-typescript-sdk@2.13.0': resolution: {integrity: sha512-cBH9a6dKQRyk1Bfh0gfAPGt9CbofCbtw3Q27hWJ5QzcYTG6SlJ6O2T0ctQ0yImgvXnSGTajwXRACjXCceW454A==} + '@kinde/js-utils@0.23.0': + resolution: {integrity: sha512-FNg2GVH9vE3Nzhu4uBChVIvp1mJta86nc7itViEr+Scp6C0DPBNNUcTQBgZJTqp4fRUz4+MuKh0tNGvggA2ZzQ==} + peerDependencies: + expo-secure-store: '>=11.0.0' + peerDependenciesMeta: + expo-secure-store: + optional: true + + '@kinde/js-utils@0.29.0': + resolution: {integrity: sha512-YtoUGxlvHKaj3wlPVGG9sSklB/sz8Re++/OlIridPICz+prMHOOGPk9CantKGrymsdxJjTSq9/0zPac+C6oqQg==} + peerDependencies: + expo-secure-store: '>=11.0.0' + peerDependenciesMeta: + expo-secure-store: + optional: true + '@kinde/jwt-decoder@0.2.0': resolution: {integrity: sha512-dqtwCmAvywOVLkkUfp4UbqdvVLsK0cvHsJhU3gDY9rgjAdZhGw0vCreBW6j3MFLxbi6cZm7pMU7/O5SJgvN5Rw==} @@ -2435,16 +2448,25 @@ snapshots: '@kinde-oss/kinde-typescript-sdk@2.13.0(eslint@9.26.0)(typescript@5.8.3)': dependencies: - '@kinde/js-utils': link:../js-utils + '@kinde/js-utils': 0.23.0 '@kinde/jwt-decoder': 0.2.0 '@kinde/jwt-validator': 0.4.0 '@typescript-eslint/parser': 8.31.1(eslint@9.26.0)(typescript@5.8.3) uncrypto: 0.1.3 transitivePeerDependencies: - eslint + - expo-secure-store - supports-color - typescript + '@kinde/js-utils@0.23.0': + dependencies: + '@kinde/jwt-decoder': 0.2.0 + + '@kinde/js-utils@0.29.0': + dependencies: + '@kinde/jwt-decoder': 0.2.0 + '@kinde/jwt-decoder@0.2.0': {} '@kinde/jwt-validator@0.4.0': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index a42d214a..00000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -overrides: - "@kinde/js-utils": link:../js-utils