/** * Create (or promote) an admin user with a hashed email/password credential. * * Usage: npx tsx scripts/create-admin.ts [name] * * Passwords are hashed with Better Auth's own hasher and stored in the * `account` table — never in plaintext. If the user already exists they are * promoted to admin and their password is reset. */ import "dotenv/config"; import { prisma } from "@/lib/db"; import { auth } from "@/lib/auth/auth"; async function main() { const [email, password, ...nameParts] = process.argv.slice(2); if (!email || !password) { console.error("Usage: npx tsx scripts/create-admin.ts [name]"); process.exit(1); } const name = nameParts.join(" ") || email.split("@")[0]; const ctx = await auth.$context; const passwordHash = await ctx.password.hash(password); const existing = await prisma.user.findUnique({ where: { email } }); const user = existing ? await prisma.user.update({ where: { id: existing.id }, data: { role: "admin", emailVerified: true, name: existing.name || name }, }) : await prisma.user.create({ data: { name, email, role: "admin", emailVerified: true }, }); // Upsert the credential account (no compound unique on account, so do it manually). const cred = await prisma.account.findFirst({ where: { userId: user.id, providerId: "credential" }, select: { id: true }, }); if (cred) { await prisma.account.update({ where: { id: cred.id }, data: { password: passwordHash } }); } else { await prisma.account.create({ data: { accountId: user.id, providerId: "credential", userId: user.id, password: passwordHash }, }); } console.log(`\nāœ… Admin ${existing ? "updated" : "created"}: ${email}`); console.log(` id: ${user.id}`); console.log(` role: ${user.role}`); console.log(` login: ${email} / (the password you provided)\n`); } main() .catch((err) => { console.error("create-admin failed:", err?.message ?? err); process.exitCode = 1; }) .finally(() => prisma.$disconnect());