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:
@@ -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 }>(
|
||||
|
||||
@@ -18,6 +18,8 @@ export const sqliteUsers = sqliteTable('users', {
|
||||
googleId: text('google_id'),
|
||||
rucNumber: text('ruc_number'),
|
||||
accountStatus: text('account_status', { enum: ['active', 'unclaimed', 'suspended'] }).notNull().default('active'),
|
||||
// Incremented to invalidate previously issued JWTs (logout-everywhere, password change/reset)
|
||||
tokenVersion: integer('token_version').notNull().default(0),
|
||||
createdAt: text('created_at').notNull(),
|
||||
updatedAt: text('updated_at').notNull(),
|
||||
});
|
||||
@@ -359,6 +361,8 @@ export const pgUsers = pgTable('users', {
|
||||
googleId: varchar('google_id', { length: 255 }),
|
||||
rucNumber: varchar('ruc_number', { length: 15 }),
|
||||
accountStatus: varchar('account_status', { length: 20 }).notNull().default('active'),
|
||||
// Incremented to invalidate previously issued JWTs (logout-everywhere, password change/reset)
|
||||
tokenVersion: pgInteger('token_version').notNull().default(0),
|
||||
createdAt: timestamp('created_at').notNull(),
|
||||
updatedAt: timestamp('updated_at').notNull(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user