The seed script previously imported hashPassword from src/lib/passwords.ts,
which has 'import "server-only"' at the top. That guard throws when the
file is loaded outside a Next.js server runtime (which is what 'tsx db/seed.ts'
is — plain Node). Inline the same scrypt format (matches verifyPassword)
so the seed runs from a CLI.
- db/seed.ts: upserts admin@route-commerce.local with a scrypt password
hash (default password 'admin', override with SEED_ADMIN_PASSWORD)
and grants them the platform_admin role on the Tuxedo tenant so
getAdminUser() resolves it for the Credentials provider's authorize
function
- src/app/login/page.tsx: reads ?error=... from the URL and passes
hasCredentials + seededEmail + error to the client so the form
pre-fills in dev and surfaces 'Invalid email or password' cleanly
- src/app/login/LoginClient.tsx: adds the email + password form below
the Google button, with a divider, dev-mode pre-fill, and local error
handling for client-side failures (server-side failures come back
through the ?error=... param)
- Migration 0002 adds nullable password_hash to users (idempotent)
- src/lib/passwords.ts: encode/verify with self-describing format
(algo$N$salt$hash) so we can migrate to a stronger KDF later
- Schema adds passwordHash column; OAuth-only users leave it null