detached 28660c8

refactor: Improve logging privacy and readability across API routes

commit
28660c8937ed82043e7101e7bee0adde36a83c47
author
Kit · · 2025-02-14 13:41 UTC
parent
3c14cb2
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
4 files · +38 -21
M src/routes/api/register/+server.ts +11 -10
@@ -9,7 +9,7 @@ const captchaAttempts = new Map<string, { count: number, lastAttempt: number }>(
99 const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
1010
1111 export const POST: RequestHandler = async ({ request }) => {
12- console.debug("POST /api/register called");
12+ console.log("[Register] New registration attempt received");
1313
1414 // Only allow POST
1515 if (request.method !== 'POST') {
@@ -19,8 +19,8 @@ export const POST: RequestHandler = async ({ request }) => {
1919 let body: unknown;
2020 try {
2121 body = await request.json();
22- } catch (err) {
23- console.debug("Invalid JSON", err);
22+ } catch {
23+ console.log("[Register] Invalid JSON payload received");
2424 return new Response(JSON.stringify({ error: 'Invalid JSON' } as RegisterResponseError), { status: 400 });
2525 }
2626
@@ -29,7 +29,7 @@ export const POST: RequestHandler = async ({ request }) => {
2929 // Destructure and validate required fields from the payload.
3030 if (typeof suUsername !== 'string' || typeof suPassword !== 'string' ||
3131 typeof suConfirmPassword !== 'string' || typeof captchaAnswer !== 'string') {
32- console.debug("Missing or invalid input fields", { suUsername, suPassword, suConfirmPassword, captchaAnswer });
32+ console.log("[Register] Missing or invalid input fields");
3333 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as RegisterResponseError), { status: 400 });
3434 }
3535
@@ -66,19 +66,20 @@ export const POST: RequestHandler = async ({ request }) => {
6666 const expectedCaptcha = "point de rencontre";
6767 // Get the IP address for rate limiting. In production, a more reliable method should be used.
6868 const ip = request.headers.get('x-forwarded-for') || 'unknown';
69+ const maskedIp = ip.split('.').map((octet, idx) => idx < 3 ? 'xxx' : octet).join('.');
6970 const now = Date.now();
7071 const attemptData = captchaAttempts.get(ip) || { count: 0, lastAttempt: 0 };
7172 // If the user has failed three or more times, apply aggressive exponential backoff.
7273 if (attemptData.count >= 3) {
7374 const delay = Math.pow(2, attemptData.count - 3 + 1) * 1000; // delay in milliseconds
74- console.debug(`Applying exponential backoff delay of ${delay}ms for IP ${ip}`);
75+ console.log(`[Register] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);
7576 await sleep(delay);
7677 }
7778 if (captcha !== expectedCaptcha) {
7879 attemptData.count++;
7980 attemptData.lastAttempt = now;
8081 captchaAttempts.set(ip, attemptData);
81- console.debug(`Captcha failed for IP ${ip}. Attempt count: ${attemptData.count}`);
82+ console.log(`[Register] Captcha failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
8283 const remaining = Math.max(0, 3 - attemptData.count);
8384 return new Response(JSON.stringify({ error: `Incorrect captcha answer. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` } as RegisterResponseError), { status: 400 });
8485 }
@@ -91,8 +92,8 @@ export const POST: RequestHandler = async ({ request }) => {
9192 let hashedPassword: string;
9293 try {
9394 hashedPassword = await hashPassword(password);
94- } catch (err) {
95- console.debug("Error hashing password", err);
95+ } catch {
96+ console.log("[Register] Error hashing password");
9697 return new Response(JSON.stringify({ error: 'Internal Server Error' } as RegisterResponseError), { status: 500 });
9798 }
9899 // Insert the new user into the database.
@@ -102,9 +103,9 @@ export const POST: RequestHandler = async ({ request }) => {
102103 nickname: username,
103104 createdAt: Date.now()
104105 });
105- console.debug(`User ${username} registered successfully`);
106+ console.log(`[Register] User ${username} registered successfully`);
106107 } catch (err) {
107- console.debug("Database insertion error", err);
108+ console.log("Database insertion error", err);
108109 // Assume a duplicate user error if the email (or derived unique field) already exists.
109110 return new Response(JSON.stringify({ error: 'User registration failed. Possibly user already exists.' } as RegisterResponseError), { status: 409 });
110111 }
@@ -9,7 +9,7 @@ const captchaAttempts = new Map<string, { count: number, lastAttempt: number }>(
9 const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));9 const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
10 10
11 export const POST: RequestHandler = async ({ request }) => {11 export const POST: RequestHandler = async ({ request }) => {
12- console.debug("POST /api/register called");12+ console.log("[Register] New registration attempt received");
13 13
14 // Only allow POST14 // Only allow POST
15 if (request.method !== 'POST') {15 if (request.method !== 'POST') {
@@ -19,8 +19,8 @@ export const POST: RequestHandler = async ({ request }) => {
19 let body: unknown;19 let body: unknown;
20 try {20 try {
21 body = await request.json();21 body = await request.json();
22- } catch (err) {22+ } catch {
23- console.debug("Invalid JSON", err);23+ console.log("[Register] Invalid JSON payload received");
24 return new Response(JSON.stringify({ error: 'Invalid JSON' } as RegisterResponseError), { status: 400 });24 return new Response(JSON.stringify({ error: 'Invalid JSON' } as RegisterResponseError), { status: 400 });
25 }25 }
26 26
@@ -29,7 +29,7 @@ export const POST: RequestHandler = async ({ request }) => {
29 // Destructure and validate required fields from the payload.29 // Destructure and validate required fields from the payload.
30 if (typeof suUsername !== 'string' || typeof suPassword !== 'string' ||30 if (typeof suUsername !== 'string' || typeof suPassword !== 'string' ||
31 typeof suConfirmPassword !== 'string' || typeof captchaAnswer !== 'string') {31 typeof suConfirmPassword !== 'string' || typeof captchaAnswer !== 'string') {
32- console.debug("Missing or invalid input fields", { suUsername, suPassword, suConfirmPassword, captchaAnswer });32+ console.log("[Register] Missing or invalid input fields");
33 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as RegisterResponseError), { status: 400 });33 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as RegisterResponseError), { status: 400 });
34 }34 }
35 35
@@ -66,19 +66,20 @@ export const POST: RequestHandler = async ({ request }) => {
66 const expectedCaptcha = "point de rencontre";66 const expectedCaptcha = "point de rencontre";
67 // Get the IP address for rate limiting. In production, a more reliable method should be used.67 // Get the IP address for rate limiting. In production, a more reliable method should be used.
68 const ip = request.headers.get('x-forwarded-for') || 'unknown';68 const ip = request.headers.get('x-forwarded-for') || 'unknown';
69+ const maskedIp = ip.split('.').map((octet, idx) => idx < 3 ? 'xxx' : octet).join('.');
69 const now = Date.now();70 const now = Date.now();
70 const attemptData = captchaAttempts.get(ip) || { count: 0, lastAttempt: 0 };71 const attemptData = captchaAttempts.get(ip) || { count: 0, lastAttempt: 0 };
71 // If the user has failed three or more times, apply aggressive exponential backoff.72 // If the user has failed three or more times, apply aggressive exponential backoff.
72 if (attemptData.count >= 3) {73 if (attemptData.count >= 3) {
73 const delay = Math.pow(2, attemptData.count - 3 + 1) * 1000; // delay in milliseconds74 const delay = Math.pow(2, attemptData.count - 3 + 1) * 1000; // delay in milliseconds
74- console.debug(`Applying exponential backoff delay of ${delay}ms for IP ${ip}`);75+ console.log(`[Register] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);
75 await sleep(delay);76 await sleep(delay);
76 }77 }
77 if (captcha !== expectedCaptcha) {78 if (captcha !== expectedCaptcha) {
78 attemptData.count++;79 attemptData.count++;
79 attemptData.lastAttempt = now;80 attemptData.lastAttempt = now;
80 captchaAttempts.set(ip, attemptData);81 captchaAttempts.set(ip, attemptData);
81- console.debug(`Captcha failed for IP ${ip}. Attempt count: ${attemptData.count}`);82+ console.log(`[Register] Captcha failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
82 const remaining = Math.max(0, 3 - attemptData.count);83 const remaining = Math.max(0, 3 - attemptData.count);
83 return new Response(JSON.stringify({ error: `Incorrect captcha answer. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` } as RegisterResponseError), { status: 400 });84 return new Response(JSON.stringify({ error: `Incorrect captcha answer. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` } as RegisterResponseError), { status: 400 });
84 }85 }
@@ -91,8 +92,8 @@ export const POST: RequestHandler = async ({ request }) => {
91 let hashedPassword: string;92 let hashedPassword: string;
92 try {93 try {
93 hashedPassword = await hashPassword(password);94 hashedPassword = await hashPassword(password);
94- } catch (err) {95+ } catch {
95- console.debug("Error hashing password", err);96+ console.log("[Register] Error hashing password");
96 return new Response(JSON.stringify({ error: 'Internal Server Error' } as RegisterResponseError), { status: 500 });97 return new Response(JSON.stringify({ error: 'Internal Server Error' } as RegisterResponseError), { status: 500 });
97 }98 }
98 // Insert the new user into the database.99 // Insert the new user into the database.
@@ -102,9 +103,9 @@ export const POST: RequestHandler = async ({ request }) => {
102 nickname: username,103 nickname: username,
103 createdAt: Date.now()104 createdAt: Date.now()
104 });105 });
105- console.debug(`User ${username} registered successfully`);106+ console.log(`[Register] User ${username} registered successfully`);
106 } catch (err) {107 } catch (err) {
107- console.debug("Database insertion error", err);108+ console.log("Database insertion error", err);
108 // Assume a duplicate user error if the email (or derived unique field) already exists.109 // Assume a duplicate user error if the email (or derived unique field) already exists.
109 return new Response(JSON.stringify({ error: 'User registration failed. Possibly user already exists.' } as RegisterResponseError), { status: 409 });110 return new Response(JSON.stringify({ error: 'User registration failed. Possibly user already exists.' } as RegisterResponseError), { status: 409 });
110 }111 }
M src/routes/api/rooms/[roomId]/+server.ts +13 -3
@@ -14,7 +14,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
1414 }
1515
1616 try {
17- console.debug(`Fetching ${isPublic ? 'public' : 'private'} room data for room: ${roomId}`);
17+ console.log('[Rooms] Fetching room data:', {
18+ roomId,
19+ isPublic,
20+ requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
21+ });
1822
1923 // Fetch messages for the room (most recent then reversed for chronological order)
2024 const messageQuery = db.select()
@@ -42,12 +46,18 @@ export async function GET({ params, url, locals }): Promise<Response> {
4246 buddyList: sanitizedUsers
4347 };
4448
49+ console.log('[Rooms] Successfully fetched room data:', {
50+ roomId,
51+ userCount: sanitizedUsers.length,
52+ isPublic
53+ });
54+
4555 return new Response(JSON.stringify(responseData), {
4656 status: 200,
4757 headers: { 'Content-Type': 'application/json' }
4858 });
49- } catch (err) {
50- console.debug('Error fetching room data:', err);
59+ } catch {
60+ console.log('[Rooms] Error fetching room data:', { roomId });
5161 const errorResponse: PublicRoomResponse = {
5262 success: false,
5363 error: 'Failed to fetch room data'
@@ -14,7 +14,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
14 }14 }
15 15
16 try {16 try {
17- console.debug(`Fetching ${isPublic ? 'public' : 'private'} room data for room: ${roomId}`);17+ console.log('[Rooms] Fetching room data:', {
18+ roomId,
19+ isPublic,
20+ requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
21+ });
18 22
19 // Fetch messages for the room (most recent then reversed for chronological order)23 // Fetch messages for the room (most recent then reversed for chronological order)
20 const messageQuery = db.select()24 const messageQuery = db.select()
@@ -42,12 +46,18 @@ export async function GET({ params, url, locals }): Promise<Response> {
42 buddyList: sanitizedUsers46 buddyList: sanitizedUsers
43 };47 };
44 48
49+ console.log('[Rooms] Successfully fetched room data:', {
50+ roomId,
51+ userCount: sanitizedUsers.length,
52+ isPublic
53+ });
54+
45 return new Response(JSON.stringify(responseData), {55 return new Response(JSON.stringify(responseData), {
46 status: 200,56 status: 200,
47 headers: { 'Content-Type': 'application/json' }57 headers: { 'Content-Type': 'application/json' }
48 });58 });
49- } catch (err) {59+ } catch {
50- console.debug('Error fetching room data:', err);60+ console.log('[Rooms] Error fetching room data:', { roomId });
51 const errorResponse: PublicRoomResponse = {61 const errorResponse: PublicRoomResponse = {
52 success: false,62 success: false,
53 error: 'Failed to fetch room data'63 error: 'Failed to fetch room data'
M src/routes/api/status/+server.ts +4 -3
@@ -18,13 +18,14 @@ export async function POST({ request, cookies, locals }) {
1818 }
1919
2020 const userId = locals.user.id;
21- console.debug('Updating status for user', userId, 'to', status);
21+ const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22+ console.log('[Status] Updating user status:', { userId: maskedUserId, status });
2223
2324 // Update the user status in the database
2425 await db.update(users)
2526 .set({ status, lastSeen: status === 'offline' ? Date.now() : null })
2627 .where(eq(users.id, userId));
27- console.debug('Status updated in database');
28+ console.log('[Status] Database updated successfully');
2829
2930 // Renew session if status is online and session is near expiry
3031 if (status === 'online') {
@@ -39,7 +40,7 @@ export async function POST({ request, cookies, locals }) {
3940 const newToken = generateSessionToken();
4041 const newSession = await createSession(newToken, userId);
4142 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
42- console.debug('Session renewed for user', userId);
43+ console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });
4344 }
4445 }
4546 }
@@ -18,13 +18,14 @@ export async function POST({ request, cookies, locals }) {
18 }18 }
19 19
20 const userId = locals.user.id;20 const userId = locals.user.id;
21- console.debug('Updating status for user', userId, 'to', status);21+ const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22+ console.log('[Status] Updating user status:', { userId: maskedUserId, status });
22 23
23 // Update the user status in the database24 // Update the user status in the database
24 await db.update(users)25 await db.update(users)
25 .set({ status, lastSeen: status === 'offline' ? Date.now() : null })26 .set({ status, lastSeen: status === 'offline' ? Date.now() : null })
26 .where(eq(users.id, userId));27 .where(eq(users.id, userId));
27- console.debug('Status updated in database');28+ console.log('[Status] Database updated successfully');
28 29
29 // Renew session if status is online and session is near expiry30 // Renew session if status is online and session is near expiry
30 if (status === 'online') {31 if (status === 'online') {
@@ -39,7 +40,7 @@ export async function POST({ request, cookies, locals }) {
39 const newToken = generateSessionToken();40 const newToken = generateSessionToken();
40 const newSession = await createSession(newToken, userId);41 const newSession = await createSession(newToken, userId);
41 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);42 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
42- console.debug('Session renewed for user', userId);43+ console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });
43 }44 }
44 }45 }
45 }46 }
M src/routes/api/users/[userId]/+server.ts +10 -5
@@ -5,7 +5,8 @@ import { eq } from 'drizzle-orm';
55 import type { RequestHandler } from './$types';
66
77 export const GET: RequestHandler = async ({ params }) => {
8- console.debug('GET /api/users/[userId] called with params:', params);
8+ const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
9+ console.log('[Users] Fetching user data:', { userId: maskedUserId });
910
1011 try {
1112 const user = await db.select({
@@ -20,17 +21,21 @@ export const GET: RequestHandler = async ({ params }) => {
2021 .get();
2122
2223 if (!user) {
23- console.debug('User not found:', params.userId);
24+ console.log('[Users] User not found:', { userId: maskedUserId });
2425 throw error(404, 'User not found');
2526 }
2627
27- console.debug('User found:', user);
28+ console.log('[Users] User data retrieved:', {
29+ userId: maskedUserId,
30+ status: user.status,
31+ lastSeen: user.lastSeen ? new Date(user.lastSeen).toISOString() : null
32+ });
2833 return json({
2934 success: true,
3035 user
3136 });
32- } catch (err) {
33- console.debug('Error fetching user:', err);
37+ } catch {
38+ console.log('[Users] Error fetching user data:', { userId: maskedUserId });
3439 throw error(500, 'Failed to fetch user information');
3540 }
3641 };⏎̸
@@ -5,7 +5,8 @@ import { eq } from 'drizzle-orm';
5 import type { RequestHandler } from './$types';5 import type { RequestHandler } from './$types';
6 6
7 export const GET: RequestHandler = async ({ params }) => {7 export const GET: RequestHandler = async ({ params }) => {
8- console.debug('GET /api/users/[userId] called with params:', params);8+ const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
9+ console.log('[Users] Fetching user data:', { userId: maskedUserId });
9 10
10 try {11 try {
11 const user = await db.select({12 const user = await db.select({
@@ -20,17 +21,21 @@ export const GET: RequestHandler = async ({ params }) => {
20 .get();21 .get();
21 22
22 if (!user) {23 if (!user) {
23- console.debug('User not found:', params.userId);24+ console.log('[Users] User not found:', { userId: maskedUserId });
24 throw error(404, 'User not found');25 throw error(404, 'User not found');
25 }26 }
26 27
27- console.debug('User found:', user);28+ console.log('[Users] User data retrieved:', {
29+ userId: maskedUserId,
30+ status: user.status,
31+ lastSeen: user.lastSeen ? new Date(user.lastSeen).toISOString() : null
32+ });
28 return json({33 return json({
29 success: true,34 success: true,
30 user35 user
31 });36 });
32- } catch (err) {37+ } catch {
33- console.debug('Error fetching user:', err);38+ console.log('[Users] Error fetching user data:', { userId: maskedUserId });
34 throw error(500, 'Failed to fetch user information');39 throw error(500, 'Failed to fetch user information');
35 }40 }
36 };⏎̸41 };⏎̸

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