Skip to content

Commit c6f9e70

Browse files
committed
fix(security): harden API errors and test isolation
Keep pentest fixes from 05de047 separate from this follow-up. - Replace raw 500 error details with generic client responses and server-side logging - Stop legacy image redirects and pin Next.js to Sharp 0.35.3 - Return 502 for DNS resolution failures while retaining 400 for SSRF blocks - Isolate WhatsApp test fixtures and cover malformed held-order data - Remove duplicate tax preview error logging Verified with full lint, build, and test suites.
1 parent 7d6075d commit c6f9e70

29 files changed

Lines changed: 324 additions & 817 deletions

frontend/package-lock.json

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

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"typescript": "5.9.3"
4444
},
4545
"overrides": {
46-
"postcss": "8.5.20"
46+
"postcss": "8.5.20",
47+
"sharp": "$sharp"
4748
}
4849
}

main/kds-server.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ export function startKdsServer(): Promise<void> {
148148
kds_default_view: s.kds_default_view === 'kanban' ? 'kanban' : 'tabs',
149149
});
150150
} catch (error: any) {
151-
res.status(500).json({ error: error.message });
151+
console.error("[API] Internal error:", error);
152+
res.status(500).json({ error: "Internal server error" });
152153
}
153154
});
154155

@@ -190,7 +191,8 @@ export function startKdsServer(): Promise<void> {
190191
},
191192
});
192193
} catch (error: any) {
193-
res.status(500).json({ error: error.message });
194+
console.error("[API] Internal error:", error);
195+
res.status(500).json({ error: "Internal server error" });
194196
}
195197
});
196198

@@ -236,7 +238,8 @@ export function startKdsServer(): Promise<void> {
236238

237239
res.json({ orders: ordersWithItems });
238240
} catch (error: any) {
239-
res.status(500).json({ error: error.message });
241+
console.error("[API] Internal error:", error);
242+
res.status(500).json({ error: "Internal server error" });
240243
}
241244
});
242245

@@ -274,7 +277,8 @@ export function startKdsServer(): Promise<void> {
274277
res.json({ success: true });
275278
} catch (error: any) {
276279
console.error('[KDS Server] PATCH item status error:', error);
277-
res.status(500).json({ error: error.message });
280+
console.error("[API] Internal error:", error);
281+
res.status(500).json({ error: "Internal server error" });
278282
}
279283
});
280284

@@ -285,7 +289,8 @@ export function startKdsServer(): Promise<void> {
285289
const categories = db.prepare('SELECT * FROM categories WHERE is_active = 1 ORDER BY sort_order').all();
286290
res.json({ categories });
287291
} catch (error: any) {
288-
res.status(500).json({ error: error.message });
292+
console.error("[API] Internal error:", error);
293+
res.status(500).json({ error: "Internal server error" });
289294
}
290295
});
291296

main/routes/addon-groups.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ router.get('/', (req: Request, res: Response) => {
4242

4343
res.json({ addon_groups: groupsWithAddons });
4444
} catch (error: any) {
45-
res.status(500).json({ error: error.message });
45+
console.error("[API] Internal error:", error);
46+
res.status(500).json({ error: "Internal server error" });
4647
}
4748
});
4849

@@ -57,7 +58,8 @@ router.get('/:id', (req: Request, res: Response) => {
5758
const addons = db.prepare('SELECT * FROM addons WHERE addon_group_id = ? ORDER BY sort_order, name').all(req.params.id);
5859
res.json({ addon_group: { ...group, addons } });
5960
} catch (error: any) {
60-
res.status(500).json({ error: error.message });
61+
console.error("[API] Internal error:", error);
62+
res.status(500).json({ error: "Internal server error" });
6163
}
6264
});
6365

@@ -102,7 +104,8 @@ router.post('/', requireRole('owner', 'manager'), (req: Request, res: Response)
102104

103105
res.status(201).json({ addon_group: Object.assign({}, group, { addons: groupAddons }) });
104106
} catch (error: any) {
105-
res.status(500).json({ error: error.message });
107+
console.error("[API] Internal error:", error);
108+
res.status(500).json({ error: "Internal server error" });
106109
}
107110
});
108111

@@ -151,7 +154,8 @@ router.put('/:id', requireRole('owner', 'manager'), (req: Request, res: Response
151154

152155
res.json({ addon_group: Object.assign({}, updated, { addons: updatedAddons }) });
153156
} catch (error: any) {
154-
res.status(500).json({ error: error.message });
157+
console.error("[API] Internal error:", error);
158+
res.status(500).json({ error: "Internal server error" });
155159
}
156160
});
157161

@@ -169,7 +173,8 @@ router.delete('/:id', requireRole('owner', 'manager'), (req: Request, res: Respo
169173
db.prepare('UPDATE addon_groups SET is_active = 0, updated_at = ? WHERE id = ?').run(now(), req.params.id);
170174
res.json({ message: 'Addon group deleted' });
171175
} catch (error: any) {
172-
res.status(500).json({ error: error.message });
176+
console.error("[API] Internal error:", error);
177+
res.status(500).json({ error: "Internal server error" });
173178
}
174179
});
175180

@@ -195,7 +200,8 @@ router.post('/:groupId/addons', requireRole('owner', 'manager'), (req: Request,
195200
const addon = db.prepare('SELECT * FROM addons WHERE id = ?').get(addonId);
196201
res.status(201).json({ addon });
197202
} catch (error: any) {
198-
res.status(500).json({ error: error.message });
203+
console.error("[API] Internal error:", error);
204+
res.status(500).json({ error: "Internal server error" });
199205
}
200206
});
201207

@@ -225,7 +231,8 @@ router.put('/:groupId/addons/:addonId', requireRole('owner', 'manager'), (req: R
225231
const updated = db.prepare('SELECT * FROM addons WHERE id = ?').get(req.params.addonId);
226232
res.json({ addon: updated });
227233
} catch (error: any) {
228-
res.status(500).json({ error: error.message });
234+
console.error("[API] Internal error:", error);
235+
res.status(500).json({ error: "Internal server error" });
229236
}
230237
});
231238

@@ -247,7 +254,8 @@ router.delete('/:groupId/addons/:addonId', requireRole('owner', 'manager'), (req
247254
db.prepare('UPDATE addons SET is_active = 0, updated_at = ? WHERE id = ?').run(now(), req.params.addonId);
248255
res.json({ message: 'Addon deleted' });
249256
} catch (error: any) {
250-
res.status(500).json({ error: error.message });
257+
console.error("[API] Internal error:", error);
258+
res.status(500).json({ error: "Internal server error" });
251259
}
252260
});
253261

main/routes/auth.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ router.post('/login', authRateLimit(), (req: Request, res: Response) => {
347347
});
348348
} catch (error: any) {
349349
console.error('[Auth] Login error:', error);
350-
res.status(500).json({ error: error.message });
350+
console.error("[API] Internal error:", error);
351+
res.status(500).json({ error: "Internal server error" });
351352
}
352353
});
353354

@@ -481,7 +482,8 @@ router.post('/password/change', (req: Request, res: Response) => {
481482

482483
res.json({ message: 'Password changed successfully' });
483484
} catch (error: any) {
484-
res.status(500).json({ error: error.message });
485+
console.error("[API] Internal error:", error);
486+
res.status(500).json({ error: "Internal server error" });
485487
}
486488
});
487489

@@ -554,7 +556,8 @@ router.post('/recover-password', authRateLimit(), (req: Request, res: Response)
554556
res.json({ message: 'Password reset successfully. You can now log in with your new password.' });
555557
} catch (error: any) {
556558
console.error('[Auth] Password recovery error:', error);
557-
res.status(500).json({ error: error.message });
559+
console.error("[API] Internal error:", error);
560+
res.status(500).json({ error: "Internal server error" });
558561
}
559562
});
560563

@@ -574,7 +577,8 @@ router.get('/setup/status', (_req: Request, res: Response) => {
574577
masterPinAvailable: isMasterPinAvailable(),
575578
});
576579
} catch (error: any) {
577-
res.status(500).json({ error: error.message });
580+
console.error("[API] Internal error:", error);
581+
res.status(500).json({ error: "Internal server error" });
578582
}
579583
});
580584

@@ -758,7 +762,7 @@ router.post('/setup/initialize', (req: Request, res: Response) => {
758762
const status = message.includes('already complete') ? 403
759763
: message.includes('already exists') ? 400
760764
: 500;
761-
res.status(status).json({ error: message });
765+
res.status(status).json({ error: status === 500 ? 'Setup failed' : message });
762766
}
763767
});
764768

main/routes/bills.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ router.get('/', requireRole('owner', 'manager', 'cashier'), (req: Request, res:
5858
const bills = db.prepare(query).all(...params).map(parseRowJson);
5959
res.json({ bills });
6060
} catch (error: any) {
61-
res.status(500).json({ error: error.message });
61+
console.error("[API] Internal error:", error);
62+
res.status(500).json({ error: "Internal server error" });
6263
}
6364
});
6465

@@ -75,7 +76,8 @@ router.get('/:id', requireRole('owner', 'manager', 'cashier'), (req: Request, re
7576

7677
res.json({ bill: { ...bill, order, customer } });
7778
} catch (error: any) {
78-
res.status(500).json({ error: error.message });
79+
console.error("[API] Internal error:", error);
80+
res.status(500).json({ error: "Internal server error" });
7981
}
8082
});
8183

@@ -93,7 +95,8 @@ router.get('/order/:orderId', requireRole('owner', 'manager', 'cashier'), (req:
9395

9496
res.json({ bill: { ...bill, order, customer } });
9597
} catch (error: any) {
96-
res.status(500).json({ error: error.message });
98+
console.error("[API] Internal error:", error);
99+
res.status(500).json({ error: "Internal server error" });
97100
}
98101
});
99102

@@ -191,7 +194,8 @@ router.post('/generate', requireRole('owner', 'manager', 'cashier'), (req: Reque
191194
notifyOrderUpdated();
192195
res.status(201).json({ bill });
193196
} catch (error: any) {
194-
res.status(500).json({ error: error.message });
197+
console.error("[API] Internal error:", error);
198+
res.status(500).json({ error: "Internal server error" });
195199
}
196200
});
197201

@@ -398,7 +402,8 @@ router.post('/:id/payment', requireRole('owner', 'manager', 'cashier'), (req: Re
398402
res.json(result);
399403
} catch (error: any) {
400404
const statusCode = error.statusCode || 500;
401-
res.status(statusCode).json({ error: error.message });
405+
console.error('[API] Bill payment failed:', error);
406+
res.status(statusCode).json({ error: statusCode >= 500 ? 'Bill payment failed' : error.message });
402407
}
403408
});
404409

@@ -506,7 +511,8 @@ router.post('/:id/applyDiscount', requireRole('owner', 'manager'), (req: Request
506511
notifyOrderUpdated();
507512
res.json({ bill: updatedBill });
508513
} catch (error: any) {
509-
res.status(500).json({ error: error.message });
514+
console.error("[API] Internal error:", error);
515+
res.status(500).json({ error: "Internal server error" });
510516
}
511517
});
512518

@@ -524,7 +530,8 @@ router.post('/:id/markPrinted', requireRole('owner', 'manager'), (req: Request,
524530
const updatedBill = parseRowJson(db.prepare('SELECT * FROM bills WHERE id = ?').get(req.params.id));
525531
res.json({ bill: updatedBill });
526532
} catch (error: any) {
527-
res.status(500).json({ error: error.message });
533+
console.error("[API] Internal error:", error);
534+
res.status(500).json({ error: "Internal server error" });
528535
}
529536
});
530537

@@ -545,7 +552,8 @@ router.post('/:id/print', requireRole('owner', 'manager', 'cashier'), async (req
545552
} catch (error: any) {
546553
// Return 404 for "Bill not found", 500 for other errors
547554
const statusCode = error.message?.includes('Bill not found') ? 404 : 500;
548-
res.status(statusCode).json({ error: error.message });
555+
console.error('[API] Receipt printing failed:', error);
556+
res.status(statusCode).json({ error: statusCode >= 500 ? 'Receipt printing failed' : 'Bill not found' });
549557
}
550558
});
551559

@@ -563,7 +571,8 @@ router.get('/:id/print-history', requireRole('owner', 'manager', 'cashier'), (re
563571

564572
res.json({ prints });
565573
} catch (error: any) {
566-
res.status(500).json({ error: error.message });
574+
console.error("[API] Internal error:", error);
575+
res.status(500).json({ error: "Internal server error" });
567576
}
568577
});
569578

main/routes/categories.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ router.get('/', (req: Request, res: Response) => {
3333

3434
res.json({ categories: categoriesWithChildren });
3535
} catch (error: any) {
36-
res.status(500).json({ error: error.message });
36+
console.error("[API] Internal error:", error);
37+
res.status(500).json({ error: "Internal server error" });
3738
}
3839
});
3940

@@ -50,7 +51,8 @@ router.get('/:id', (req: Request, res: Response) => {
5051

5152
res.json({ category: { ...category, children, products } });
5253
} catch (error: any) {
53-
res.status(500).json({ error: error.message });
54+
console.error("[API] Internal error:", error);
55+
res.status(500).json({ error: "Internal server error" });
5456
}
5557
});
5658

@@ -74,7 +76,8 @@ router.post('/', requireRole('owner', 'manager'), (req: Request, res: Response)
7476
const category = db.prepare('SELECT * FROM categories WHERE id = ?').get(id);
7577
res.status(201).json({ category });
7678
} catch (error: any) {
77-
res.status(500).json({ error: error.message });
79+
console.error("[API] Internal error:", error);
80+
res.status(500).json({ error: "Internal server error" });
7881
}
7982
});
8083

@@ -103,7 +106,8 @@ router.put('/:id', requireRole('owner', 'manager'), (req: Request, res: Response
103106
const updated = db.prepare('SELECT * FROM categories WHERE id = ?').get(req.params.id);
104107
res.json({ category: updated });
105108
} catch (error: any) {
106-
res.status(500).json({ error: error.message });
109+
console.error("[API] Internal error:", error);
110+
res.status(500).json({ error: "Internal server error" });
107111
}
108112
});
109113

@@ -149,7 +153,8 @@ router.delete('/:id', requireRole('owner', 'manager'), (req: Request, res: Respo
149153
db.prepare('UPDATE categories SET deleted_at = ? WHERE id = ?').run(now(), req.params.id);
150154
res.json({ message: 'Category deleted' });
151155
} catch (error: any) {
152-
res.status(500).json({ error: error.message });
156+
console.error("[API] Internal error:", error);
157+
res.status(500).json({ error: "Internal server error" });
153158
}
154159
});
155160

0 commit comments

Comments
 (0)