#!/usr/bin/env node import crypto from "crypto"; import pg from "pg"; const { Pool } = pg; const SCRYPT_PARAMS = { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }; const SCRYPT_KEYLEN = 32; const PASSWORD_PREFIX = "scrypt:v1:"; function hashPassword(password) { const salt = crypto.randomBytes(16); return new Promise((resolve, reject) => { crypto.scrypt(password, salt, SCRYPT_KEYLEN, SCRYPT_PARAMS, (err, derived) => { if (err) { reject(err); return; } resolve( `${PASSWORD_PREFIX}${salt.toString("base64url")}:${derived.toString("base64url")}`, ); }); }); } async function main() { const email = process.env.LOCAL_AUTH_EMAIL?.trim().toLowerCase(); const password = process.env.LOCAL_AUTH_PASSWORD; const isOperator = process.env.LOCAL_AUTH_OPERATOR === "1"; const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) { throw new Error("DATABASE_URL is required"); } if (!email) { throw new Error("LOCAL_AUTH_EMAIL is required"); } if (!password || password.length < 8) { throw new Error("LOCAL_AUTH_PASSWORD must be at least 8 characters"); } const pool = new Pool({ connectionString: databaseUrl }); const client = await pool.connect(); try { const passwordHash = await hashPassword(password); const userId = crypto.randomUUID(); const existing = await client.query( `SELECT id FROM auth.users WHERE lower(email) = lower($1) LIMIT 1`, [email], ); if (existing.rows.length > 0) { throw new Error(`User already exists for ${email}`); } await client.query( `INSERT INTO auth.users (id, email, password_hash, is_operator) VALUES ($1, $2, $3, $4)`, [userId, email, passwordHash, isOperator], ); console.log( `Created local auth user ${email} (operator=${isOperator ? "yes" : "no"})`, ); } finally { client.release(); await pool.end(); } } main().catch((err) => { console.error(err.message || err); process.exit(1); });