// One-off: creates a superadmin user. // Run from monorepo root: npx tsx scripts/create-admin.ts import path from 'node:path'; import { fileURLToPath } from 'node:url'; import dotenv from 'dotenv'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); dotenv.config({ path: path.resolve(__dirname, '../.env') }); import argon2 from 'argon2'; import { eq } from 'drizzle-orm'; import { getDb, getPool, users } from '@lawdesk/db'; // Credentials come from the environment or argv — never hardcode them in a tracked file. // ADMIN_EMAIL=you@example.com ADMIN_PASSWORD='...' ADMIN_NAME='You' npx tsx scripts/create-admin.ts // or: npx tsx scripts/create-admin.ts you@example.com 'password' 'Your Name' const EMAIL = process.env.ADMIN_EMAIL ?? process.argv[2]; const PASSWORD = process.env.ADMIN_PASSWORD ?? process.argv[3]; const NAME = process.env.ADMIN_NAME ?? process.argv[4] ?? 'Admin'; if (!EMAIL || !PASSWORD) { console.error( 'Missing credentials.\n' + "Usage: ADMIN_EMAIL=you@example.com ADMIN_PASSWORD='...' [ADMIN_NAME='...'] npx tsx scripts/create-admin.ts", ); process.exit(1); } if (PASSWORD.length < 10) { console.error('ADMIN_PASSWORD must be at least 10 characters.'); process.exit(1); } async function main() { const db = getDb(); const passwordHash = await argon2.hash(PASSWORD, { type: argon2.argon2id, memoryCost: 64 * 1024, timeCost: 3, parallelism: 1, }); const existing = await db.select({ id: users.id }).from(users).where(eq(users.email, EMAIL)); if (existing.length > 0) { await db.update(users).set({ passwordHash, isSuperadmin: true, isSuspended: false, emailVerifiedAt: new Date(), updatedAt: new Date(), }).where(eq(users.email, EMAIL)); console.log(`Updated existing user → superadmin: ${EMAIL}`); } else { const [u] = await db.insert(users).values({ email: EMAIL, passwordHash, fullName: NAME, role: 'owner', isSuperadmin: true, emailVerifiedAt: new Date(), }).returning(); console.log(`Created superadmin: ${u.email} (id: ${u.id})`); } await getPool().end(); } main().catch((err) => { console.error(err); process.exit(1); });