SPB Git
2.2 KB · 41 lines typescript
1/**2 * npm run setup:owner — interactively sets (or resets) the single owner account.3 * Prompts for a password without echo, stores only its Argon2id hash. Never logs the password.4 */5import postgres from "postgres";6import { hash } from "@node-rs/argon2";7import readline from "node:readline";89const url = process.env.DATABASE_URL;10if (!url) { console.error("DATABASE_URL is required"); process.exit(1); }11const sql = postgres(url, { max: 1, onnotice: () => {} });1213function ask(q: string, hidden = false): Promise<string> {14  return new Promise(resolve => {15    const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });16    if (hidden) {17      const anyRl = rl as unknown as { _writeToOutput: (s: string) => void; output: NodeJS.WritableStream };18      const orig = anyRl._writeToOutput;19      anyRl._writeToOutput = (s: string) => { if (s.includes(q)) orig.call(rl, q); else if (s === "\r\n" || s === "\n") orig.call(rl, s); };20    }21    rl.question(q, a => { rl.close(); if (hidden) process.stdout.write("\n"); resolve(a); });22  });23}2425async function main() {26  const existing = await sql`SELECT username FROM owner WHERE id = 1`;27  if (existing.length) console.log(`Owner already configured (${existing[0].username}). Continuing will RESET the password and revoke all sessions.`);28  const username = (await ask(`Username [${existing[0]?.username ?? "simon"}]: `)).trim() || existing[0]?.username || "simon";29  const p1 = await ask("New password (min 12 chars): ", true);30  if (p1.length < 12) { console.error("Password too short."); process.exit(1); }31  const p2 = await ask("Confirm password: ", true);32  if (p1 !== p2) { console.error("Passwords do not match."); process.exit(1); }33  const h = await hash(p1, { memoryCost: 65536, timeCost: 3, parallelism: 2, algorithm: 2 });34  await sql`INSERT INTO owner (id, username, password_hash) VALUES (1, ${username}, ${h})35            ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username, password_hash = EXCLUDED.password_hash, updated_at = now()`;36  await sql`UPDATE sessions SET revoked_at = now() WHERE revoked_at IS NULL`;37  console.log(`Owner "${username}" saved (Argon2id). All sessions revoked.`);38  await sql.end();39}40main().catch(e => { console.error(e); process.exit(1); });41
Files · 7d61c36e2fa5e0d19df2d38831a92de25c4fc209
Menu

Theme