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:
@@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user