/** * Create (or reset) an admin account with TOTP. * pnpm admin:create [password] * Prints the TOTP secret + otpauth URL once. Never logs the password. */ import { adminUsers, closeDb, db, eq } from "@spinza/database"; import { encrypt, generateTotpSecret, hashPassword, otpauthUrl, randomToken } from "./lib/crypto"; async function main() { const [username, passwordArg] = process.argv.slice(2); if (!username) { console.error("usage: admin:create [password]"); process.exit(1); } const password = passwordArg ?? randomToken(12); const secret = generateTotpSecret(); const passwordHash = await hashPassword(password); const existing = await db.query.adminUsers.findFirst({ where: eq(adminUsers.username, username.toLowerCase()) }); if (existing) { await db.update(adminUsers).set({ passwordHash, totpSecret: encrypt(secret), disabled: false }).where(eq(adminUsers.id, existing.id)); console.log(`Admin "${username}" reset.`); } else { await db.insert(adminUsers).values({ username: username.toLowerCase(), passwordHash, totpSecret: encrypt(secret) }); console.log(`Admin "${username}" created.`); } console.log(`Password: ${passwordArg ? "(as provided)" : password}`); console.log(`TOTP secret (base32): ${secret}`); console.log(`otpauth URL: ${otpauthUrl(username, secret)}`); console.log("Add the secret to an authenticator app now — it is not shown again."); await closeDb(); } main().catch((e) => { console.error(e); process.exit(1); });