Skip to content

Commit c363d68

Browse files
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
1 parent 70e0287 commit c363d68

11 files changed

Lines changed: 202 additions & 84 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"private": false,
8282
"dependencies": {
8383
"@kinde-oss/kinde-typescript-sdk": "^2.13.0",
84+
"@kinde/js-utils": "^0.29.0",
8485
"@kinde/jwt-validator": "^0.4.0",
8586
"aws-jwt-verify": "^5.0.0",
8687
"eslint-config-prettier": "^10.1.2",

pnpm-lock.yaml

Lines changed: 7 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
overrides:
2+
"@kinde/js-utils": link:../js-utils

src/index.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1+
import type { Express } from "express";
12
import {
23
type SetupConfig,
34
getInternalClient,
45
setupInternalClient,
56
} from "./setup/index.js";
67
import { setupAuthRouter } from "./auth/index.js";
7-
import type { Express } from "express";
88
import { jwtVerify } from "./helpers/kindeMiddlewareHelpers.js";
9-
10-
import {
11-
managementApi,
12-
GrantType,
13-
Configuration,
14-
} from "@kinde-oss/kinde-typescript-sdk";
15-
9+
import { setupKindeSession } from "./setup/sessionManager.js";
10+
import type { GrantType } from "@kinde-oss/kinde-typescript-sdk";
1611
export * from "./helpers/index.js";
17-
export { managementApi, GrantType, Configuration, jwtVerify };
12+
export { jwtVerify };
1813

1914
/**
20-
* Encapsulates Kinde setup by completing creating internal TypeScript SDK
21-
* client, setting up its session manager interface and attaching auth router
22-
* to provided express instance.
15+
* Encapsulates Kinde setup for an Express application by setting up sessions,
16+
* creating the client, and attaching authentication routes.
2317
*
24-
* @param {Express} app
25-
* @param {SetupConfig} config
18+
* @param {SetupConfig} config The Kinde configuration object.
19+
* @param {Express} app The Express application instance.
20+
* @returns The initialized Kinde client.
2621
*/
2722
export const setupKinde = <G extends GrantType>(
2823
config: SetupConfig<G>,
2924
app: Express,
3025
) => {
31-
setupInternalClient(app, config);
26+
// setting up expressSession layer first for Kinde
27+
setupKindeSession(app);
28+
// initializing the Kinde SDK client
29+
setupInternalClient(config);
30+
// setting up the authentication routes
3231
setupAuthRouter(app, "/");
3332
return getInternalClient();
3433
};

src/mocks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { GrantType, setupKinde } from "./index.js";
1+
import { GrantType } from "@kinde-oss/kinde-typescript-sdk";
2+
import { setupKinde } from "./index.js";
23
import express from "express";
34

45
export const mockClientConfig = {

src/setup/index.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
1-
import { setupKindeSession } from "./sessionManager.js";
2-
import { setupInternalClient as setupKindeClient } from "./kindeClient.js";
3-
import type { GrantType } from "@kinde-oss/kinde-typescript-sdk";
4-
import type { SetupConfig } from "./kindeSetupTypes.js";
5-
import type { Express } from "express";
1+
// this file only re-exports the necessary functions and types from the other files
2+
// in the setup directory
3+
export {
4+
getInternalClient,
5+
getInitialConfig,
6+
setupInternalClient,
7+
} from "./kindeClient.js";
68

7-
export { getInternalClient, getInitialConfig } from "./kindeClient.js";
9+
export { getSessionManager, setupKindeSession } from "./sessionManager.js";
810
export * from "./kindeSetupTypes.js";
9-
10-
/**
11-
* Encapsulates Kinde setup of creatint creating internal TypeScript SDK
12-
* client, setting up its session manager interface and attaching session
13-
* manager to provided express instance.
14-
*
15-
* @param {Express} app
16-
* @param {SetupConfig} config
17-
*/
18-
export const setupInternalClient = <G extends GrantType>(
19-
app: Express,
20-
config: SetupConfig<G>,
21-
): void => {
22-
setupKindeSession(app);
23-
setupKindeClient(config);
24-
};

src/setup/kindeSetupTypes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
CCClientOptions,
77
} from "@kinde-oss/kinde-typescript-sdk";
88

9-
interface CommonSetupConfigParams<G extends GrantType> {
9+
export interface CommonSetupConfigParams<G extends GrantType> {
1010
grantType: G;
1111
issuerBaseUrl: string;
1212
postLogoutRedirectUrl: string;
@@ -16,20 +16,20 @@ interface CommonSetupConfigParams<G extends GrantType> {
1616
scope?: string;
1717
}
1818

19-
interface ACSetupConfigParams
19+
export interface ACSetupConfigParams
2020
extends CommonSetupConfigParams<GrantType.AUTHORIZATION_CODE> {
2121
secret: string;
2222
unAuthorisedUrl: string;
2323
redirectUrl: string;
2424
}
2525

26-
interface PKCESetupConfigParams
26+
export interface PKCESetupConfigParams
2727
extends CommonSetupConfigParams<GrantType.PKCE> {
2828
unAuthorisedUrl: string;
2929
redirectUrl: string;
3030
}
3131

32-
interface CCSetupConfigParams
32+
export interface CCSetupConfigParams
3333
extends CommonSetupConfigParams<GrantType.CLIENT_CREDENTIALS> {
3434
secret: string;
3535
}
@@ -38,7 +38,7 @@ export type ClientType<G extends GrantType> = ReturnType<
3838
typeof createKindeServerClient<G>
3939
>;
4040

41-
export type SetupConfig<G> = G extends GrantType.PKCE
41+
export type SetupConfig<G extends GrantType> = G extends GrantType.PKCE
4242
? PKCESetupConfigParams
4343
: G extends GrantType.AUTHORIZATION_CODE
4444
? ACSetupConfigParams

src/setup/sessionManager.test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import express, { type Express } from "express";
3+
import request from "supertest";
4+
import session from "express-session";
5+
import { setupKindeSession } from "./sessionManager.js";
6+
7+
describe("sessionManager", () => {
8+
let app: Express;
9+
10+
beforeEach(() => {
11+
app = express();
12+
app.use(express.json());
13+
});
14+
15+
describe("setupKindeSession()", () => {
16+
it("should add session middleware if it has not been added", () => {
17+
setupKindeSession(app);
18+
const sessionMiddleware = app._router.stack.find(
19+
(layer) => layer.name === "session",
20+
);
21+
expect(sessionMiddleware).toBeDefined();
22+
});
23+
24+
it("should not add session middleware if it has already been added", () => {
25+
app.use(
26+
session({
27+
secret:
28+
process.env.TEST_SESSION_SECRET || "test-secret-" + Date.now(),
29+
resave: false,
30+
saveUninitialized: true,
31+
}),
32+
);
33+
const sessionMiddlewareCount = app._router.stack.filter(
34+
(layer) => layer.name === "session",
35+
).length;
36+
expect(sessionMiddlewareCount).toBe(1);
37+
setupKindeSession(app);
38+
const sessionMiddlewareCountAfter = app._router.stack.filter(
39+
(layer) => layer.name === "session",
40+
).length;
41+
expect(sessionMiddlewareCountAfter).toBe(1);
42+
});
43+
44+
it("should add getSessionManager middleware", () => {
45+
const initialStackLength = app._router.stack.length;
46+
setupKindeSession(app);
47+
const finalStackLength = app._router.stack.length;
48+
expect(finalStackLength).toBeGreaterThan(initialStackLength);
49+
});
50+
});
51+
52+
describe("ExpressSessionManager functionality", () => {
53+
beforeEach(() => {
54+
setupKindeSession(app);
55+
app.get("/session-functions", (req, res) => {
56+
res.json({
57+
setSessionItem: typeof req.setSessionItem === "function",
58+
getSessionItem: typeof req.getSessionItem === "function",
59+
removeSessionItem: typeof req.removeSessionItem === "function",
60+
destroySession: typeof req.destroySession === "function",
61+
});
62+
});
63+
64+
app.post("/set-item", async (req, res) => {
65+
const { key, value } = req.body;
66+
await req.setSessionItem(key, value);
67+
res.sendStatus(200);
68+
});
69+
70+
app.get("/get-item", async (req, res) => {
71+
const { key } = req.query;
72+
const value = await req.getSessionItem(key as string);
73+
res.json({ value });
74+
});
75+
76+
app.post("/remove-item", async (req, res) => {
77+
const { key } = req.body;
78+
await req.removeSessionItem(key);
79+
res.sendStatus(200);
80+
});
81+
82+
app.post("/destroy", async (req, res) => {
83+
try {
84+
await req.destroySession();
85+
res.sendStatus(200);
86+
} catch {
87+
res.status(500).send("Error destroying session");
88+
}
89+
});
90+
});
91+
92+
it("adds ExpressSessionManager methods to the request object", async () => {
93+
const res = await request(app).get("/session-functions");
94+
expect(res.status).toBe(200);
95+
expect(res.body).toEqual({
96+
setSessionItem: true,
97+
getSessionItem: true,
98+
removeSessionItem: true,
99+
destroySession: true,
100+
});
101+
});
102+
103+
it("sets and gets a session item via ExpressSessionManager", async () => {
104+
const agent = request.agent(app);
105+
await agent
106+
.post("/set-item")
107+
.send({ key: "testKey", value: "testValue" });
108+
const res = await agent.get("/get-item").query({ key: "testKey" });
109+
expect(res.body.value).toBe("testValue");
110+
});
111+
112+
it("removes a session item via ExpressSessionManager", async () => {
113+
const agent = request.agent(app);
114+
await agent
115+
.post("/set-item")
116+
.send({ key: "testKey", value: "testValue" });
117+
await agent.post("/remove-item").send({ key: "testKey" });
118+
const res = await agent.get("/get-item").query({ key: "testKey" });
119+
expect(res.body.value).toBe(null);
120+
});
121+
122+
it("destroys the session via ExpressSessionManager", async () => {
123+
const agent = request.agent(app);
124+
await agent
125+
.post("/set-item")
126+
.send({ key: "testKey", value: "testValue" });
127+
await agent.post("/destroy");
128+
const res = await agent.get("/get-item").query({ key: "testKey" });
129+
expect(res.body.value).toBe(null);
130+
});
131+
});
132+
});

src/setup/sessionManager.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ExpressMiddleware, randomString } from "../utils.js";
22
import type { Express, Request, Response, NextFunction } from "express";
33
import session, { type SessionOptions } from "express-session";
4+
import { ExpressSessionManager } from "@kinde/js-utils";
45

56
const SESSION_MAX_AGE: number = 1000 * 60 * 60 * 24;
67

@@ -18,27 +19,27 @@ const sessionConfig: SessionOptions = {
1819
};
1920

2021
/**
21-
* Sets up @type{import('@kinde-oss/kinde-typescript-sdk').SessionManager} as an
22-
* as an express middleware, with the assumption that `express-session` package
23-
* middleware has already been configured.
22+
* Sets up the session manager by creating an instance of the
23+
* `ExpressSessionManager` class from @kinde/js-utils for each request.
2424
*
2525
* @returns {ExpressMiddleware}
2626
*/
27-
const getSessionManager = (): ExpressMiddleware<void> => {
27+
export const getSessionManager = (): ExpressMiddleware<void> => {
2828
return (req: Request, _: Response, next: NextFunction) => {
29-
req.setSessionItem = async (itemKey, itemValue) => {
30-
req.session[itemKey] = itemValue;
31-
};
32-
req.getSessionItem = async (itemKey) => {
33-
return req.session[itemKey] ?? null;
34-
};
35-
req.removeSessionItem = async (itemKey) => {
36-
delete req.session[itemKey];
37-
};
38-
req.destroySession = async () => {
39-
req.session.destroy((e) => console.log(e));
40-
};
41-
next();
29+
try {
30+
// Ensuring the session is initialized
31+
const manager = new ExpressSessionManager(req);
32+
33+
// Now bridging the methods from @kinde/js-utils to req object
34+
req.setSessionItem = manager.setSessionItem.bind(manager);
35+
req.getSessionItem = manager.getSessionItem.bind(manager);
36+
req.removeSessionItem = manager.removeSessionItem.bind(manager);
37+
req.destroySession = manager.destroySession.bind(manager);
38+
39+
next();
40+
} catch (error) {
41+
next(error);
42+
}
4243
};
4344
};
4445

@@ -51,8 +52,7 @@ const hasSessionMiddleware = (app: Express) => {
5152
};
5253

5354
/**
54-
* Attaches the `express-session` middleware and the `SessionManager` for internal
55-
* typescript SDK (in middleware form).
55+
* Attaches the `express-session` middleware and the Kinde ExpressSessionManager.
5656
* @param {Express} app
5757
*/
5858
export const setupKindeSession = (app: Express): void => {

0 commit comments

Comments
 (0)