detached 3a9c4bf

fix(security): Update cookie parsing and user status handling

- Fix Cloudflare clearance cookie parsing regex
- Normalize lastSeen timestamp to prevent null value issues
- Improve user status timeout and offline status update logic
- Add null coalescing for lastSeen to ensure consistent type handling
commit
3a9c4bf4ae544de0bb76807e6ea9033fb945de85
author
Kit · · 2025-02-18 07:19 UTC
parent
059b851
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
3 files · +40 -22
M src/lib/types/chat.ts +1 -1
@@ -39,7 +39,7 @@ export type UserStatus = 'offline' | 'online' | 'away' | string;
3939 nickname: user.nickname,
4040 status: user.status,
4141 avatarUrl: user.avatarUrl,
42- lastSeen: user.lastSeen
42+ lastSeen: user.lastSeen ?? 0
4343 };
4444 return safeUser;
4545 }
@@ -39,7 +39,7 @@ export type UserStatus = 'offline' | 'online' | 'away' | string;
39 nickname: user.nickname,39 nickname: user.nickname,
40 status: user.status,40 status: user.status,
41 avatarUrl: user.avatarUrl,41 avatarUrl: user.avatarUrl,
42- lastSeen: user.lastSeen42+ lastSeen: user.lastSeen ?? 0
43 };43 };
44 return safeUser;44 return safeUser;
45 }45 }
M src/routes/api/rooms/[roomId]/+server.ts +37 -20
@@ -24,42 +24,59 @@ export async function GET({ params, url, locals }): Promise<Response> {
2424 // Calculate timeout threshold
2525 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
2626
27- // Fetch buddy list: get all users and mark them as offline if they've timed out
27+ // Fetch buddy list: get all users
2828 const fetchedUsers = await db.select()
2929 .from(users)
3030 .execute();
3131
32- // Process users to mark them as offline if they've timed out
33- const processedUsers = fetchedUsers.map((user: SafeUser & { lastSeen: number }) => {
34- const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;
35- return {
32+ // Normalize users to ensure that lastSeen is always a number (using 0 if null)
33+ const normalizedUsers = fetchedUsers.map(
34+ (user: SafeUser & { lastSeen: number | null }) => ({
3635 ...user,
37- status: shouldBeOffline ? 'offline' : user.status
38- };
39- });
36+ lastSeen: user.lastSeen ?? 0
37+ })
38+ );
4039
41- // Update any users that should be offline in the database
42- const usersToUpdate = processedUsers.filter((user: SafeUser & { lastSeen: number }) =>
43- user.status === 'offline' &&
44- fetchedUsers.find((u: SafeUser & { lastSeen: number }) => u.id === user.id)?.status === 'online'
40+ // Process users to mark them as offline if they've timed out
41+ const processedUsers = normalizedUsers.map(
42+ (user: SafeUser & { lastSeen: number }) => {
43+ const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;
44+ return {
45+ ...user,
46+ status: shouldBeOffline ? 'offline' : user.status
47+ };
48+ }
49+ );
50+
51+ // Find users who were originally online but now marked offline
52+ const usersToUpdate = processedUsers.filter(
53+ (user: SafeUser & { lastSeen: number }) =>
54+ user.status === 'offline' &&
55+ normalizedUsers.find(
56+ (u: SafeUser & { lastSeen: number }) => u.id === user.id
57+ )?.status === 'online'
4558 );
4659
4760 if (usersToUpdate.length > 0) {
48- await Promise.all(usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>
49- db.update(users)
50- .set({ status: 'offline', lastSeen: Date.now() })
51- .where(eq(users.id, user.id))
52- .execute()
53- ));
61+ await Promise.all(
62+ usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>
63+ db.update(users)
64+ .set({ status: 'offline', lastSeen: Date.now() })
65+ .where(eq(users.id, user.id))
66+ .execute()
67+ )
68+ );
5469
5570 log.debug('Updated offline status for users', {
5671 count: usersToUpdate.length,
5772 userIds: usersToUpdate.map((u: SafeUser & { lastSeen: number }) => u.id)
5873 });
5974 }
60-
75+
6176 // Sanitize user data using createSafeUser
62- const sanitizedUsers = processedUsers.map((user: SafeUser & { lastSeen: number }) => createSafeUser(user));
77+ const sanitizedUsers = processedUsers.map(
78+ (user: SafeUser & { lastSeen: number }) => createSafeUser(user)
79+ );
6380
6481 const responseData: PublicRoomResponse = {
6582 success: true,
@@ -24,42 +24,59 @@ export async function GET({ params, url, locals }): Promise<Response> {
24 // Calculate timeout threshold24 // Calculate timeout threshold
25 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;25 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
26 26
27- // Fetch buddy list: get all users and mark them as offline if they've timed out27+ // Fetch buddy list: get all users
28 const fetchedUsers = await db.select()28 const fetchedUsers = await db.select()
29 .from(users)29 .from(users)
30 .execute();30 .execute();
31 31
32- // Process users to mark them as offline if they've timed out32+ // Normalize users to ensure that lastSeen is always a number (using 0 if null)
33- const processedUsers = fetchedUsers.map((user: SafeUser & { lastSeen: number }) => {33+ const normalizedUsers = fetchedUsers.map(
34- const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;34+ (user: SafeUser & { lastSeen: number | null }) => ({
35- return {
36 ...user,35 ...user,
37- status: shouldBeOffline ? 'offline' : user.status36+ lastSeen: user.lastSeen ?? 0
38- };37+ })
39- });38+ );
40 39
41- // Update any users that should be offline in the database40+ // Process users to mark them as offline if they've timed out
42- const usersToUpdate = processedUsers.filter((user: SafeUser & { lastSeen: number }) => 41+ const processedUsers = normalizedUsers.map(
43- user.status === 'offline' && 42+ (user: SafeUser & { lastSeen: number }) => {
44- fetchedUsers.find((u: SafeUser & { lastSeen: number }) => u.id === user.id)?.status === 'online'43+ const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;
44+ return {
45+ ...user,
46+ status: shouldBeOffline ? 'offline' : user.status
47+ };
48+ }
49+ );
50+
51+ // Find users who were originally online but now marked offline
52+ const usersToUpdate = processedUsers.filter(
53+ (user: SafeUser & { lastSeen: number }) =>
54+ user.status === 'offline' &&
55+ normalizedUsers.find(
56+ (u: SafeUser & { lastSeen: number }) => u.id === user.id
57+ )?.status === 'online'
45 );58 );
46 59
47 if (usersToUpdate.length > 0) {60 if (usersToUpdate.length > 0) {
48- await Promise.all(usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>61+ await Promise.all(
49- db.update(users)62+ usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>
50- .set({ status: 'offline', lastSeen: Date.now() })63+ db.update(users)
51- .where(eq(users.id, user.id))64+ .set({ status: 'offline', lastSeen: Date.now() })
52- .execute()65+ .where(eq(users.id, user.id))
53- ));66+ .execute()
67+ )
68+ );
54 69
55 log.debug('Updated offline status for users', { 70 log.debug('Updated offline status for users', {
56 count: usersToUpdate.length,71 count: usersToUpdate.length,
57 userIds: usersToUpdate.map((u: SafeUser & { lastSeen: number }) => u.id)72 userIds: usersToUpdate.map((u: SafeUser & { lastSeen: number }) => u.id)
58 });73 });
59 }74 }
60- 75+
61 // Sanitize user data using createSafeUser76 // Sanitize user data using createSafeUser
62- const sanitizedUsers = processedUsers.map((user: SafeUser & { lastSeen: number }) => createSafeUser(user));77+ const sanitizedUsers = processedUsers.map(
78+ (user: SafeUser & { lastSeen: number }) => createSafeUser(user)
79+ );
63 80
64 const responseData: PublicRoomResponse = {81 const responseData: PublicRoomResponse = {
65 success: true,82 success: true,
M src/routes/api/session/login/+server.ts +2 -1
@@ -28,10 +28,11 @@ setInterval(() => {
2828 }
2929 }, 60 * 60 * 1000);
3030
31+// cf_clearance:"Qwq0fwt23w"
3132 // Helper to parse clearance cookie from headers
3233 function getClearanceCookie(cookieHeader: string | null): string | null {
3334 if (!cookieHeader) return null;
34- const match = cookieHeader.match(/cf-turnstile-clearance=([^;]+)/);
35+ const match = cookieHeader.match(/cf-clearance=([^;]+)/);
3536 return match ? match[1] : null;
3637 }
3738
@@ -28,10 +28,11 @@ setInterval(() => {
28 }28 }
29 }, 60 * 60 * 1000);29 }, 60 * 60 * 1000);
30 30
31+// cf_clearance:"Qwq0fwt23w"
31 // Helper to parse clearance cookie from headers32 // Helper to parse clearance cookie from headers
32 function getClearanceCookie(cookieHeader: string | null): string | null {33 function getClearanceCookie(cookieHeader: string | null): string | null {
33 if (!cookieHeader) return null;34 if (!cookieHeader) return null;
34- const match = cookieHeader.match(/cf-turnstile-clearance=([^;]+)/);35+ const match = cookieHeader.match(/cf-clearance=([^;]+)/);
35 return match ? match[1] : null;36 return match ? match[1] : null;
36 }37 }
37 38

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close