Skip to content

Commit 5b16418

Browse files
exchange request.session to request.encryptedSession
1 parent d4eb8da commit 5b16418

File tree

11 files changed

+162
-93
lines changed

11 files changed

+162
-93
lines changed

.env.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ API_BACKEND_URL=
1616
# Replace this value with a strong, randomly generated string (at least 32 characters).
1717
# Example for generation in Node.js: require('crypto').randomBytes(32).toString('hex')
1818
COOKIE_SECRET=
19+
SESSION_SECRET=

package-lock.json

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

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@fastify/env": "^5.0.2",
2626
"@fastify/http-proxy": "^11.1.2",
2727
"@fastify/sensible": "^6.0.3",
28+
"@fastify/secure-session": "^8.2.0",
2829
"@fastify/session": "^11.1.0",
2930
"@fastify/static": "^8.1.1",
3031
"@fastify/vite": "^8.1.3",
@@ -83,4 +84,4 @@
8384
"vite": "^6.3.4",
8485
"vitest": "^3.1.4"
8586
}
86-
}
87+
}

server/app.js

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,19 @@ const __dirname = dirname(__filename);
1111

1212
export default async function (fastify, opts) {
1313
await fastify.register(envPlugin);
14-
await fastify.register(testRoute)
15-
16-
// await fastify.register(AutoLoad, {
17-
// dir: join(__dirname, "plugins"),
18-
// options: { ...opts },
19-
// });
20-
21-
// await fastify.register(AutoLoad, {
22-
// dir: join(__dirname, "routes"),
23-
// options: { ...opts },
24-
// });
25-
}
26-
27-
function testRoute(fastify, opts) {
2814
fastify.register(encryptedSession, {
2915
...opts,
3016
});
3117

32-
// this route basically stores the query parameter test in the encrypted session store and reads it on subequent requests
33-
fastify.get("/test", async (request, reply) => {
34-
// read query param
35-
const { query } = request;
36-
37-
// we use the encrypted session api with get/set like the normal session api
38-
const previousValue = request.encryptedSession.get("testFromClient");
39-
40-
console.log("value stored before request is processed:", request.encryptedSession.stringify());
18+
await fastify.register(AutoLoad, {
19+
dir: join(__dirname, "plugins"),
20+
options: { ...opts },
21+
});
4122

42-
if (query.test) {
43-
request.encryptedSession.set("testFromClient", query.test);
44-
}
23+
await fastify.register(AutoLoad, {
24+
dir: join(__dirname, "routes"),
25+
options: { ...opts },
26+
});
4527

46-
request.encryptedSession.set("testKey", "testValue");
4728

48-
return { message: "Test route works!", previousValue: previousValue || "not set", currentValue: request.encryptedSession.get("testFromClient") || "not set" };
49-
});
5029
}

server/config/env.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const schema = {
1111
'OIDC_SCOPES',
1212
'POST_LOGIN_REDIRECT',
1313
'COOKIE_SECRET',
14+
'SESSION_SECRET',
1415
'API_BACKEND_URL',
1516
],
1617
properties: {
@@ -22,6 +23,7 @@ const schema = {
2223
OIDC_SCOPES: { type: 'string' },
2324
POST_LOGIN_REDIRECT: { type: 'string' },
2425
COOKIE_SECRET: { type: 'string' },
26+
SESSION_SECRET: { type: 'string' },
2527
API_BACKEND_URL: { type: 'string' },
2628

2729
// System variables

server/encrypted-session.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const SECURE_COOKIE_KEY_ENCRYPTION_KEY = "encryptionKey";
1818
export const REQUEST_DECORATOR = "encryptedSession";
1919

2020
async function encryptedSession(fastify) {
21-
const { COOKIE_SECRET, NODE_ENV } = fastify.config;
21+
const { COOKIE_SECRET, SESSION_SECRET, NODE_ENV } = fastify.config;
2222

2323
await fastify.register(fastifyCookie);
2424

@@ -37,7 +37,7 @@ async function encryptedSession(fastify) {
3737

3838

3939
fastify.register(fastifySession, {
40-
secret: "test-secret-32-char-or-longerasdasdasdasdasdasdasdasdasdasd",
40+
secret: SESSION_SECRET,
4141
cookieName: COOKIE_NAME_SESSION,
4242
// sessionName: UNDERLYING_SESSION_NAME, //NOT POSSIBLE to change the name it is decorated on the request object
4343
cookie: {
@@ -89,11 +89,6 @@ async function encryptedSession(fastify) {
8989
// onSend is called before the response is send. Here we take encrypt the Session object and store it in the fastify-session.
9090
// Then we also want to make sure the unencrypted object is removed from memory
9191
fastify.addHook('onSend', (request, reply, _payload, next) => {
92-
console.log("onSend hook called", request[REQUEST_DECORATOR].stringify());
93-
94-
//on send we will encrypt the store and set it in the backend-side session store
95-
console.log("Encrypted store that will be set in session:", request[REQUEST_DECORATOR].stringify());
96-
9792
const encyrptionKey = Buffer.from(request[SECURE_SESSION_NAME].get(SECURE_COOKIE_KEY_ENCRYPTION_KEY), "base64");
9893
if (!encyrptionKey) {
9994
// if no encryption key is found in the secure session, we cannot encrypt the store. This should not happen since an encrption key is generated when the request arrived
@@ -114,7 +109,6 @@ async function encryptedSession(fastify) {
114109
iv,
115110
tag,
116111
});
117-
console.log("Encrypted store set in session:", request.session.encryptedStore);
118112
request.log.info("store encrypted and set into request.session.encryptedStore");
119113
next()
120114
})

server/plugins/auth-utils.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,20 @@ async function authUtilsPlugin(fastify) {
7777
request.log.info("Preparing OIDC login redirect.");
7878

7979
const { redirectTo } = request.query;
80-
request.session.set("postLoginRedirectRoute", redirectTo);
80+
request.encryptedSession.set("postLoginRedirectRoute", redirectTo);
8181

8282
const { clientId, redirectUri, scopes } = oidcConfig;
8383

8484
const state = crypto.randomBytes(16).toString("hex");
8585
const codeVerifier = crypto.randomBytes(32).toString("base64url");
8686
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
8787

88-
request.session.set("oauthState", state);
89-
request.session.set("codeVerifier", codeVerifier);
88+
request.encryptedSession.set("oauthState", state);
89+
request.encryptedSession.set("codeVerifier", codeVerifier);
9090
request.log.info({
9191
stateSet: Boolean(state),
9292
verifierSet: Boolean(codeVerifier),
93-
}, "OAuth state and code verifier set in session.");
93+
}, "OAuth state and code verifier set in encryptedSession.");
9494

9595
const url = new URL(authorizationEndpoint);
9696
url.searchParams.set("response_type", "code");
@@ -116,7 +116,7 @@ async function authUtilsPlugin(fastify) {
116116
request.log.error("Missing authorization code in callback.");
117117
throw new AuthenticationError("Missing code in callback.");
118118
}
119-
if (state !== request.session.get("oauthState")) {
119+
if (state !== request.encryptedSession.get("oauthState")) {
120120
request.log.error("Invalid state in callback.");
121121
throw new AuthenticationError("Invalid state in callback.");
122122
}
@@ -126,7 +126,7 @@ async function authUtilsPlugin(fastify) {
126126
code,
127127
redirect_uri: redirectUri,
128128
client_id: clientId,
129-
code_verifier: request.session.get("codeVerifier"),
129+
code_verifier: request.encryptedSession.get("codeVerifier"),
130130
});
131131

132132
const response = await fetch(tokenEndpoint, {
@@ -146,7 +146,7 @@ async function authUtilsPlugin(fastify) {
146146
refreshToken: tokens.refresh_token,
147147
expiresAt: null,
148148
userInfo: extractUserInfoFromIdToken(request, tokens.id_token),
149-
postLoginRedirectRoute: request.session.get("postLoginRedirectRoute") || "",
149+
postLoginRedirectRoute: request.encryptedSession.get("postLoginRedirectRoute") || "",
150150
};
151151

152152
if (tokens.expires_in && typeof tokens.expires_in === "number") {

server/plugins/http-proxy.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ function proxyPlugin(fastify) {
1919
const keyRefreshToken = useCrate ? "onboarding_refreshToken" : "mcp_refreshToken";
2020

2121
// Check if there is an access token
22-
const accessToken = request.session.get(keyAccessToken);
22+
const accessToken = request.encryptedSession.get(keyAccessToken);
2323
if (!accessToken) {
2424
request.log.error("Missing access token.");
2525
return reply.unauthorized("Missing access token.");
2626
}
2727

2828
// Check if the access token is expired or about to expire
29-
const expiresAt = request.session.get(keyTokenExpiresAt);
29+
const expiresAt = request.encryptedSession.get(keyTokenExpiresAt);
3030
const now = Date.now();
3131
const REFRESH_BUFFER_SECONDS = 20; // to allow for network latency
3232
if (!expiresAt || now < expiresAt - REFRESH_BUFFER_SECONDS) {
@@ -37,10 +37,10 @@ function proxyPlugin(fastify) {
3737
request.log.info({ expiresAt: new Date(expiresAt).toISOString() }, "Access token is expired or about to expire; attempting refresh.");
3838

3939
// Check if there is a refresh token
40-
const refreshToken = request.session.get(keyRefreshToken);
40+
const refreshToken = request.encryptedSession.get(keyRefreshToken);
4141
if (!refreshToken) {
42-
request.log.error("Missing refresh token; deleting session.");
43-
request.session.destroy();
42+
request.log.error("Missing refresh token; deleting encryptedSession.");
43+
request.encryptedSession.clear();//TODO: also clear user encrpytion key?
4444
return reply.unauthorized("Session expired without token refresh capability.");
4545
}
4646

@@ -54,23 +54,23 @@ function proxyPlugin(fastify) {
5454
}, issuerConfiguration.tokenEndpoint);
5555
if (!refreshedTokenData || !refreshedTokenData.accessToken) {
5656
request.log.error("Token refresh failed (no access token); deleting session.");
57-
request.session.destroy();
57+
request.encryptedSession.clear();//TODO: also clear user encrpytion key?
5858
return reply.unauthorized("Session expired and token refresh failed.");
5959
}
6060

6161
request.log.info("Token refresh successful; updating the session.");
6262

63-
request.session.set(keyAccessToken, refreshedTokenData.accessToken);
63+
request.encryptedSession.set(keyAccessToken, refreshedTokenData.accessToken);
6464
if (refreshedTokenData.refreshToken) {
65-
request.session.set(keyRefreshToken, refreshedTokenData.refreshToken);
65+
request.encryptedSession.set(keyRefreshToken, refreshedTokenData.refreshToken);
6666
} else {
67-
request.session.delete(keyRefreshToken);
67+
request.encryptedSession.delete(keyRefreshToken);
6868
}
6969
if (refreshedTokenData.expiresIn) {
7070
const newExpiresAt = Date.now() + (refreshedTokenData.expiresIn * 1000);
71-
request.session.set(keyTokenExpiresAt, newExpiresAt);
71+
request.encryptedSession.set(keyTokenExpiresAt, newExpiresAt);
7272
} else {
73-
request.session.delete(keyTokenExpiresAt);
73+
request.encryptedSession.delete(keyTokenExpiresAt);
7474
}
7575

7676
request.log.info("Token refresh successful and session updated; continuing with the HTTP request.");
@@ -86,7 +86,7 @@ function proxyPlugin(fastify) {
8686
replyOptions: {
8787
rewriteRequestHeaders: (req, headers) => {
8888
const useCrate = req.headers["x-use-crate"];
89-
const accessToken = useCrate ? req.session.get("onboarding_accessToken") : `${req.session.get("onboarding_accessToken")},${req.session.get("mcp_accessToken")}`;
89+
const accessToken = useCrate ? req.encryptedSession.get("onboarding_accessToken") : `${req.encryptedSession.get("onboarding_accessToken")},${req.encryptedSession.get("mcp_accessToken")}`;
9090

9191
return {
9292
...headers,

0 commit comments

Comments
 (0)