Skip to content

Commit 05a3712

Browse files
authored
Add COUPLE_SESSION_WITH_COOKIE_NAME env var (#1881)
* Add COUPLE_SESSION_WITH_COOKIE_NAME env var This means the user's session will be reset if the value of the coupled cookie name changes. Useful if the app is deployed on a subpath of another site, and you want to reset the session when the auth for the user changes * fix test
1 parent 2f93956 commit 05a3712

File tree

5 files changed

+59
-8
lines changed

5 files changed

+59
-8
lines changed

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ ALTERNATIVE_REDIRECT_URLS=`[]`
8080
### Cookies
8181
# name of the cookie used to store the session
8282
COOKIE_NAME=hf-chat
83+
# If the value of this cookie changes, the session is destroyed. Useful if chat-ui is deployed on a subpath
84+
# of your domain, and you want chat ui sessions to reset if the user's auth changes
85+
COUPLE_SESSION_WITH_COOKIE_NAME=
8386
# specify secure behaviour for cookies
8487
COOKIE_SAMESITE=# can be "lax", "strict", "none" or left empty
8588
COOKIE_SECURE=# set to true to only allow cookies over https

src/lib/server/auth.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { logger } from "$lib/server/logger";
1717
import { ObjectId } from "mongodb";
1818
import type { Cookie } from "elysia";
1919
import { adminTokenManager } from "./adminToken";
20+
import type { User } from "$lib/types/User";
2021

2122
export interface OIDCSettings {
2223
redirectURI: string;
@@ -72,14 +73,27 @@ export function refreshSessionCookie(cookies: Cookies, sessionId: string) {
7273
});
7374
}
7475

75-
export async function findUser(sessionId: string) {
76+
export async function findUser(
77+
sessionId: string,
78+
coupledCookieHash?: string
79+
): Promise<{
80+
user: User | null;
81+
invalidateSession: boolean;
82+
}> {
7683
const session = await collections.sessions.findOne({ sessionId });
7784

7885
if (!session) {
79-
return null;
86+
return { user: null, invalidateSession: false };
87+
}
88+
89+
if (coupledCookieHash && session.coupledCookieHash !== coupledCookieHash) {
90+
return { user: null, invalidateSession: true };
8091
}
8192

82-
return await collections.users.findOne({ _id: session.userId });
93+
return {
94+
user: await collections.users.findOne({ _id: session.userId }),
95+
invalidateSession: false,
96+
};
8397
}
8498
export const authCondition = (locals: App.Locals) => {
8599
if (!locals.user && !locals.sessionId) {
@@ -191,6 +205,23 @@ type HeaderRecord =
191205
| { type: "elysia"; value: Record<string, string | undefined> }
192206
| { type: "svelte"; value: Headers };
193207

208+
export async function getCoupledCookieHash(cookie: CookieRecord): Promise<string | undefined> {
209+
if (!config.COUPLE_SESSION_WITH_COOKIE_NAME) {
210+
return undefined;
211+
}
212+
213+
const cookieValue =
214+
cookie.type === "elysia"
215+
? cookie.value[config.COUPLE_SESSION_WITH_COOKIE_NAME]?.value
216+
: cookie.value.get(config.COUPLE_SESSION_WITH_COOKIE_NAME);
217+
218+
if (!cookieValue) {
219+
return "no-cookie";
220+
}
221+
222+
return await sha256(cookieValue);
223+
}
224+
194225
export async function authenticateRequest(
195226
headers: HeaderRecord,
196227
cookie: CookieRecord,
@@ -238,12 +269,23 @@ export async function authenticateRequest(
238269
if (token) {
239270
secretSessionId = token;
240271
sessionId = await sha256(token);
241-
const user = await findUser(sessionId);
272+
273+
const result = await findUser(sessionId, await getCoupledCookieHash(cookie));
274+
275+
if (result.invalidateSession) {
276+
secretSessionId = crypto.randomUUID();
277+
sessionId = await sha256(secretSessionId);
278+
279+
if (await collections.sessions.findOne({ sessionId })) {
280+
throw new Error("Session ID collision");
281+
}
282+
}
283+
242284
return {
243-
user: user ?? undefined,
285+
user: result.user ?? undefined,
244286
sessionId,
245287
secretSessionId,
246-
isAdmin: user?.isAdmin || adminTokenManager.isAdmin(sessionId),
288+
isAdmin: result.user?.isAdmin || adminTokenManager.isAdmin(sessionId),
247289
};
248290
}
249291

src/lib/types/Session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export interface Session extends Timestamps {
1010
ip?: string;
1111
expiresAt: Date;
1212
admin?: boolean;
13+
coupledCookieHash?: string;
1314
}

src/routes/login/callback/updateUser.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ describe("login", () => {
9090
it("should create default settings for new user", async () => {
9191
await updateUser({ userData, locals, cookies: cookiesMock });
9292

93-
const user = await findUser(locals.sessionId);
93+
const user = (await findUser(locals.sessionId)).user;
9494

9595
assert.exists(user);
9696

src/routes/login/callback/updateUser.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { refreshSessionCookie } from "$lib/server/auth";
1+
import { getCoupledCookieHash, refreshSessionCookie } from "$lib/server/auth";
22
import { collections } from "$lib/server/database";
33
import { ObjectId } from "mongodb";
44
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
@@ -119,6 +119,9 @@ export async function updateUser(params: {
119119

120120
locals.sessionId = sessionId;
121121

122+
// Get cookie hash if coupling is enabled
123+
const coupledCookieHash = await getCoupledCookieHash({ type: "svelte", value: cookies });
124+
122125
if (existingUser) {
123126
// update existing user if any
124127
await collections.users.updateOne(
@@ -137,6 +140,7 @@ export async function updateUser(params: {
137140
userAgent,
138141
ip,
139142
expiresAt: addWeeks(new Date(), 2),
143+
...(coupledCookieHash ? { coupledCookieHash } : {}),
140144
});
141145
} else {
142146
// user doesn't exist yet, create a new one
@@ -164,6 +168,7 @@ export async function updateUser(params: {
164168
userAgent,
165169
ip,
166170
expiresAt: addWeeks(new Date(), 2),
171+
...(coupledCookieHash ? { coupledCookieHash } : {}),
167172
});
168173

169174
// move pre-existing settings to new user

0 commit comments

Comments
 (0)