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
+28 -1
View File
@@ -5,7 +5,7 @@ import { uniqueSlug } from '../lib/slugify.js';
const dbType = process.env.DB_TYPE || 'sqlite';
console.log(`Database type: ${dbType}`);
console.log(`Database URL: ${process.env.DATABASE_URL?.substring(0, 30)}...`);
// Do not log DATABASE_URL: it may contain credentials.
async function migrate() {
console.log('Running migrations...');
@@ -43,6 +43,9 @@ async function migrate() {
try {
await (db as any).run(sql`ALTER TABLE users ADD COLUMN account_status TEXT NOT NULL DEFAULT 'active'`);
} catch (e) { /* column may already exist */ }
try {
await (db as any).run(sql`ALTER TABLE users ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0`);
} catch (e) { /* column may already exist */ }
// Magic link tokens table
await (db as any).run(sql`
@@ -541,6 +544,9 @@ async function migrate() {
try {
await (db as any).execute(sql`ALTER TABLE users ADD COLUMN account_status VARCHAR(20) NOT NULL DEFAULT 'active'`);
} catch (e) { /* column may already exist */ }
try {
await (db as any).execute(sql`ALTER TABLE users ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0`);
} catch (e) { /* column may already exist */ }
// Magic link tokens table
await (db as any).execute(sql`
@@ -974,6 +980,27 @@ async function migrate() {
`);
}
// Indexes on foreign-key / hot-filter columns (CREATE INDEX IF NOT EXISTS works on both engines)
const indexStatements = [
`CREATE INDEX IF NOT EXISTS tickets_event_id_idx ON tickets(event_id)`,
`CREATE INDEX IF NOT EXISTS tickets_user_id_idx ON tickets(user_id)`,
`CREATE INDEX IF NOT EXISTS tickets_booking_id_idx ON tickets(booking_id)`,
`CREATE INDEX IF NOT EXISTS tickets_status_idx ON tickets(status)`,
`CREATE INDEX IF NOT EXISTS payments_ticket_id_idx ON payments(ticket_id)`,
`CREATE INDEX IF NOT EXISTS payments_status_idx ON payments(status)`,
`CREATE INDEX IF NOT EXISTS email_logs_event_id_idx ON email_logs(event_id)`,
`CREATE INDEX IF NOT EXISTS magic_link_tokens_token_idx ON magic_link_tokens(token)`,
];
for (const stmt of indexStatements) {
try {
if (dbType === 'sqlite') {
await (db as any).run(sql.raw(stmt));
} else {
await (db as any).execute(sql.raw(stmt));
}
} catch (e) { /* index may already exist */ }
}
// Backfill slugs for any events that don't have one yet (shared across DB types).
// Ordered by creation so duplicate titles get deterministic -2, -3 suffixes.
const allEvents = await dbAll<{ id: string; title: string; slug: string | null }>(