SPB Git

Repository Public

bootstrap-owner.ts 1.5 KB 29 lines typescript

1import "./_env";2import { sql } from "../src/lib/db";3import { hashPassword, randomPassword } from "../src/lib/password";45/**6 * Creates the first studio owner if none exists. Idempotent.7 * Prints a one-time password exactly once; it must be changed at first login. Nothing is hardcoded.8 */9async function main() {10  const email = (process.env.OWNER_EMAIL ?? "").trim().toLowerCase();11  if (!email) throw new Error("OWNER_EMAIL is not set");12  const [existing] = await sql<{ id: string }[]>`SELECT id FROM users WHERE role = 'owner' LIMIT 1`;13  if (existing && !process.argv.includes("--reset")) { console.log("owner exists"); await sql.end(); return; }14  const password = randomPassword();15  const hash = await hashPassword(password);16  await sql`INSERT INTO users (email, name, role, password_hash, must_change_password) VALUES (${email}, 'Owner', 'owner', ${hash}, true)17            ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, must_change_password = true, role = 'owner'`;18  await sql`DELETE FROM sessions WHERE user_id = (SELECT id FROM users WHERE email = ${email})`;19  const out = process.env.OWNER_OTP_FILE;20  if (out) {21    // Deployment path: the one-time password never appears in deploy logs.22    const { writeFileSync } = await import("node:fs");23    writeFileSync(out, `${password}\n`, { mode: 0o600 });24    console.log(`owner created; one-time password written to ${out}`);25  } else console.log(`OWNER_ONE_TIME_PASSWORD=${password}`);26  await sql.end();27}28main().catch((e) => { console.error(e.message); process.exit(1); });29
Files main
Menu

Theme