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:
@@ -6,6 +6,22 @@ import { getNow, generateId } from '../lib/utils.js';
|
||||
|
||||
const faqRouter = new Hono();
|
||||
|
||||
// Upper bounds for admin-supplied FAQ content (guards against accidental/abusive huge payloads)
|
||||
const MAX_QUESTION_LEN = 1000;
|
||||
const MAX_ANSWER_LEN = 20000;
|
||||
|
||||
// Returns an error message if any provided field exceeds its limit, else null.
|
||||
function faqLengthError(fields: { question?: any; questionEs?: any; answer?: any; answerEs?: any }): string | null {
|
||||
const check = (v: any, max: number, label: string) =>
|
||||
typeof v === 'string' && v.length > max ? `${label} must be at most ${max} characters` : null;
|
||||
return (
|
||||
check(fields.question, MAX_QUESTION_LEN, 'Question') ||
|
||||
check(fields.questionEs, MAX_QUESTION_LEN, 'Question (ES)') ||
|
||||
check(fields.answer, MAX_ANSWER_LEN, 'Answer') ||
|
||||
check(fields.answerEs, MAX_ANSWER_LEN, 'Answer (ES)')
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Public Routes ====================
|
||||
|
||||
// Get FAQ list for public (only enabled; optional filter for homepage)
|
||||
@@ -98,6 +114,11 @@ faqRouter.post('/admin', requireAuth(['admin']), async (c) => {
|
||||
return c.json({ error: 'Question and answer (EN) are required' }, 400);
|
||||
}
|
||||
|
||||
const lengthError = faqLengthError({ question, questionEs, answer, answerEs });
|
||||
if (lengthError) {
|
||||
return c.json({ error: lengthError }, 400);
|
||||
}
|
||||
|
||||
const now = getNow();
|
||||
const id = generateId();
|
||||
|
||||
@@ -157,6 +178,11 @@ faqRouter.put('/admin/:id', requireAuth(['admin']), async (c) => {
|
||||
return c.json({ error: 'FAQ not found' }, 404);
|
||||
}
|
||||
|
||||
const lengthError = faqLengthError({ question, questionEs, answer, answerEs });
|
||||
if (lengthError) {
|
||||
return c.json({ error: lengthError }, 400);
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {
|
||||
updatedAt: getNow(),
|
||||
};
|
||||
@@ -209,6 +235,15 @@ faqRouter.post('/admin/reorder', requireAuth(['admin']), async (c) => {
|
||||
return c.json({ error: 'ids array is required' }, 400);
|
||||
}
|
||||
|
||||
// Verify every id exists before applying ranks (prevents silent no-ops on bad input).
|
||||
const existingRows = await dbAll<any>(
|
||||
(db as any).select({ id: (faqQuestions as any).id }).from(faqQuestions)
|
||||
);
|
||||
const existingIds = new Set(existingRows.map((r: any) => r.id));
|
||||
if (ids.some((id: string) => !existingIds.has(id))) {
|
||||
return c.json({ error: 'One or more FAQ ids are invalid' }, 400);
|
||||
}
|
||||
|
||||
const now = getNow();
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
await (db as any)
|
||||
|
||||
Reference in New Issue
Block a user