SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
1.5 KB · 37 lines typescript
Raw Blame History
1/**2 * Create (or reset) an admin account with TOTP.3 *   pnpm admin:create <username> [password]4 * Prints the TOTP secret + otpauth URL once. Never logs the password.5 */6import { adminUsers, closeDb, db, eq } from "@spinza/database";7import { encrypt, generateTotpSecret, hashPassword, otpauthUrl, randomToken } from "./lib/crypto";89async function main() {10  const [username, passwordArg] = process.argv.slice(2);11  if (!username) {12    console.error("usage: admin:create <username> [password]");13    process.exit(1);14  }15  const password = passwordArg ?? randomToken(12);16  const secret = generateTotpSecret();17  const passwordHash = await hashPassword(password);18  const existing = await db.query.adminUsers.findFirst({ where: eq(adminUsers.username, username.toLowerCase()) });19  if (existing) {20    await db.update(adminUsers).set({ passwordHash, totpSecret: encrypt(secret), disabled: false }).where(eq(adminUsers.id, existing.id));21    console.log(`Admin "${username}" reset.`);22  } else {23    await db.insert(adminUsers).values({ username: username.toLowerCase(), passwordHash, totpSecret: encrypt(secret) });24    console.log(`Admin "${username}" created.`);25  }26  console.log(`Password: ${passwordArg ? "(as provided)" : password}`);27  console.log(`TOTP secret (base32): ${secret}`);28  console.log(`otpauth URL: ${otpauthUrl(username, secret)}`);29  console.log("Add the secret to an authenticator app now — it is not shown again.");30  await closeDb();31}3233main().catch((e) => {34  console.error(e);35  process.exit(1);36});37