Harden auth, payments, and frontend against review findings.

Close exploitable gaps in booking/payment flows, enforce token versioning and account checks, gate sensitive payment data, and add middleware plus input validation across admin routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Michilis
2026-06-24 19:59:02 +00:00
co-authored by Cursor
parent fc4af38e8a
commit a6840ea953
37 changed files with 1432 additions and 528 deletions
+18
View File
@@ -162,6 +162,13 @@ legalPagesRouter.get('/', async (c) => {
legalPagesRouter.get('/:slug', async (c) => {
const { slug } = c.req.param();
const locale = c.req.query('locale') || 'en';
// Reject anything that isn't a simple slug. The filesystem fallback below builds a
// path from this value, so an unconstrained slug (e.g. "../../etc/passwd") would
// allow path traversal / arbitrary file reads.
if (!/^[a-z0-9-]+$/.test(slug)) {
return c.json({ error: 'Legal page not found' }, 404);
}
// First try to get from database
const page = await dbGet<any>(
@@ -275,6 +282,17 @@ legalPagesRouter.put('/admin/:slug', requireAuth(['admin']), async (c) => {
if (!enContent && !esContent) {
return c.json({ error: 'At least one language content is required' }, 400);
}
// Bound the sizes of admin-supplied content to avoid unbounded payloads.
const MAX_CONTENT_LEN = 200000; // ~200 KB of markdown per language
const MAX_TITLE_LEN = 255;
const tooLong = (v: any, max: number) => typeof v === 'string' && v.length > max;
if (tooLong(enContent, MAX_CONTENT_LEN) || tooLong(esContent, MAX_CONTENT_LEN)) {
return c.json({ error: `Content must be at most ${MAX_CONTENT_LEN} characters` }, 400);
}
if (tooLong(title, MAX_TITLE_LEN) || tooLong(titleEs, MAX_TITLE_LEN)) {
return c.json({ error: `Title must be at most ${MAX_TITLE_LEN} characters` }, 400);
}
const existing = await dbGet(
(db as any)