← back to Govarbitrage

scripts/set-admin.ts

43 lines

// Set/reset the admin login credential without running the full seed.
// Usage:
//   ADMIN_EMAIL=admin@agentabrams.com ADMIN_PASSWORD='...' npx tsx scripts/set-admin.ts
//   (omit ADMIN_PASSWORD to GENERATE a strong random one and print it once)
// Never hardcodes a real password.

import { randomBytes } from "node:crypto";
import { prisma } from "../src/lib/db";
import { hashPassword } from "../src/lib/password";

// Readable strong password: 3 base32-ish blocks, ~120 bits.
function generatePassword(): string {
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no ambiguous 0/O/1/I
  const bytes = randomBytes(18);
  const chars = Array.from(bytes, (b) => alphabet[b % alphabet.length]);
  return `${chars.slice(0, 6).join("")}-${chars.slice(6, 12).join("")}-${chars.slice(12, 18).join("")}`;
}

async function main() {
  const email = process.env.ADMIN_EMAIL || process.argv[2] || "admin@agentabrams.com";
  const provided = process.env.ADMIN_PASSWORD || process.argv[3];
  const generated = !provided;
  const password = provided || generatePassword();
  const hash = hashPassword(password);
  const user = await prisma.user.upsert({
    where: { email },
    update: { passwordHash: hash, role: "ADMIN" },
    create: { email, name: "Admin", role: "ADMIN", passwordHash: hash },
  });
  console.log(`Admin credential set: ${user.email} (role ${user.role}).`);
  if (generated) {
    // Printed ONCE so the operator can capture it; never stored to disk/git.
    console.log(`GENERATED_PASSWORD=${password}`);
  }
  await prisma.$disconnect();
}

main().catch(async (e) => {
  console.error("[set-admin] ERROR:", e.message);
  await prisma.$disconnect();
  process.exit(1);
});