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
+37 -3
View File
@@ -1,9 +1,9 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, dbGet, paymentOptions, eventPaymentOverrides, events } from '../db/index.js';
import { db, dbGet, paymentOptions, eventPaymentOverrides, events, tickets } from '../db/index.js';
import { eq } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { requireAuth, getAuthUser } from '../lib/auth.js';
import { generateId, getNow, convertBooleansForDb } from '../lib/utils.js';
const paymentOptionsRouter = new Hono();
@@ -40,6 +40,23 @@ const updatePaymentOptionsSchema = z.object({
allowDuplicateBookings: booleanOrNumber.optional(),
});
/** Strip bank account numbers from payment options for anonymous callers. */
function publicPaymentOptions(merged: Record<string, any>) {
return {
...merged,
bankName: null,
bankAccountHolder: null,
bankAccountNumber: null,
bankAlias: null,
bankPhone: null,
tpagoLink: null,
tpagoLink2: null,
tpagoLink3: null,
tpagoLink4: null,
tpagoLink5: null,
};
}
// Schema for event-level overrides
const updateEventOverridesSchema = z.object({
tpagoEnabled: booleanOrNumber.optional().nullable(),
@@ -151,6 +168,7 @@ paymentOptionsRouter.put('/', requireAuth(['admin']), zValidator('json', updateP
// Get payment options for a specific event (merged with global)
paymentOptionsRouter.get('/event/:eventId', async (c) => {
const eventId = c.req.param('eventId');
const ticketId = c.req.query('ticketId');
// Get the event first to verify it exists
const event = await dbGet(
@@ -229,8 +247,24 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
cashInstructionsEs: overrides?.cashInstructionsEs ?? global.cashInstructionsEs,
};
// Full bank/TPago credentials are only returned when the caller proves they hold
// a valid ticket for this event (the ticket UUID is the booking capability token),
// or when an authenticated admin/organizer requests them.
let revealSensitive = false;
const authUser: any = await getAuthUser(c);
if (authUser && ['admin', 'organizer'].includes(authUser.role)) {
revealSensitive = true;
} else if (ticketId) {
const ticket = await dbGet<any>(
(db as any).select().from(tickets).where(eq((tickets as any).id, ticketId))
);
if (ticket && ticket.eventId === eventId && ticket.status !== 'cancelled') {
revealSensitive = true;
}
}
return c.json({
paymentOptions: merged,
paymentOptions: revealSensitive ? merged : publicPaymentOptions(merged),
hasOverrides: !!overrides,
});
});