// Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: chat.spboucher.ai // Creates or updates the single user. Usage: // node scripts/set-password.mjs [password] // If no password is given, a strong one is generated and printed once. import crypto from "node:crypto"; import path from "node:path"; import fs from "node:fs"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); const Database = require("better-sqlite3"); const { hashSync } = require("@node-rs/argon2"); const username = process.argv[2]; const password = process.argv[3] ?? crypto.randomBytes(18).toString("base64url"); if (!username) { console.error("Usage: node scripts/set-password.mjs [password]"); process.exit(1); } const dataDir = path.resolve(process.env.CHAT_DATA_DIR || "./data"); fs.mkdirSync(dataDir, { recursive: true }); const db = new Database(path.join(dataDir, "chat.db")); db.pragma("journal_mode = WAL"); // Make sure the users table exists even before the app has booted once. db.exec(`CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, totp_secret TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL )`); const hash = hashSync(password, { memoryCost: 65536, timeCost: 3, parallelism: 2 }); const now = Date.now(); const existing = db.prepare("SELECT id FROM users WHERE username = ?").get(username); if (existing) { db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE username = ?").run(hash, now, username); db.prepare("DELETE FROM sessions WHERE user_id = ?").run(existing.id); console.log(`Password updated for "${username}" (all sessions revoked).`); } else { db.prepare( "INSERT INTO users (id, username, password_hash, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" ).run(crypto.randomUUID(), username, hash, now, now); console.log(`User "${username}" created.`); } if (!process.argv[3]) { console.log(`Generated password (store it now, it is not shown again):\n${password}`); }