From 2eef08ecfe1a5b20987e9a9527fd5c2740c21105 Mon Sep 17 00:00:00 2001 From: Tom Blaymire Date: Mon, 16 Mar 2026 22:30:06 +0000 Subject: [PATCH] fix: use millisecond timestamps in e2e seed and add production safety guard seed-db.ts used Unix seconds but Better Auth expects timestamp_ms (milliseconds), causing all seeded dates to land in 1970 and rejecting login on ephemeral preview environments. Also adds assertSafeToSeed() to block seeding against production and updates global-setup.ts to allow seeding ephemeral PR databases. --- apps/dashboard/e2e/global-setup.ts | 10 ++++++++-- apps/dashboard/e2e/seed-db.ts | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/e2e/global-setup.ts b/apps/dashboard/e2e/global-setup.ts index 02c61b7..213f164 100644 --- a/apps/dashboard/e2e/global-setup.ts +++ b/apps/dashboard/e2e/global-setup.ts @@ -2,6 +2,12 @@ import { seedDatabase } from './seed-db' export default async function globalSetup() { const dbUrl = process.env['TURSO_DATABASE_URL'] - if (!dbUrl?.startsWith('file:')) return // Only seed local file DBs - await seedDatabase(dbUrl) + if (!dbUrl) return + + const isSeedable = dbUrl.startsWith('file:') || /^libsql:\/\/switchflag-pr-\d+-/.test(dbUrl) + + if (!isSeedable) return + + const authToken = process.env['TURSO_AUTH_TOKEN'] + await seedDatabase(dbUrl, authToken) } diff --git a/apps/dashboard/e2e/seed-db.ts b/apps/dashboard/e2e/seed-db.ts index 5797e04..399df50 100644 --- a/apps/dashboard/e2e/seed-db.ts +++ b/apps/dashboard/e2e/seed-db.ts @@ -13,7 +13,7 @@ const personaIds = Object.values(PERSONAS) export async function seedDatabase(dbUrl: string, authToken?: string) { const client = createClient({ url: dbUrl, ...(authToken ? { authToken } : {}) }) - const now = Math.floor(Date.now() / 1000) + const now = Date.now() // Clean existing seed data (idempotent) await client.executeMultiple(` @@ -82,6 +82,24 @@ export async function seedDatabase(dbUrl: string, authToken?: string) { console.log('Seed complete — created org + project + %d personas', Object.keys(PERSONAS).length) } +function assertSafeToSeed(url: string): void { + // Layer 1: Block production Vercel environment + if (process.env['VERCEL_ENV'] === 'production') { + throw new Error('FATAL: refusing to seed — VERCEL_ENV is production') + } + + // Layer 2: Allowlist — only known-safe URL patterns + const isSafe = + url.startsWith('file:') || // Local file DB (CI + local dev) + /^libsql:\/\/switchflag-pr-\d+-/.test(url) // Ephemeral PR DB (preview-db.yml) + + if (!isSafe) { + throw new Error( + `FATAL: refusing to seed "${url}" — only local file DBs and ephemeral PR databases (switchflag-pr-*) are allowed` + ) + } +} + // CLI entry point: tsx e2e/seed-db.ts if (import.meta.url === `file://${process.argv[1]}`) { const url = process.env['TURSO_DATABASE_URL'] @@ -89,6 +107,7 @@ if (import.meta.url === `file://${process.argv[1]}`) { console.error('TURSO_DATABASE_URL is required') process.exit(1) } + assertSafeToSeed(url) const token = process.env['TURSO_AUTH_TOKEN'] await seedDatabase(url, token) }