#!/usr/bin/env node /** * Seed four super_admin control-panel users from config/omnl-super-admins.v1.json. * Requires DATABASE_URL and bcrypt (token-aggregation service deps). * * Usage: * node scripts/deployment/seed-omnl-super-admin-users.mjs * node scripts/deployment/seed-omnl-super-admin-users.mjs --dry-run * * Passwords: generated per user unless OMNL_SUPER_ADMIN__PASSWORD env is set. * Output: one-time credentials printed to stdout (never logged to git). */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import crypto from 'crypto'; import { createRequire } from 'module'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, '../..'); const taDir = path.join(repoRoot, 'services/token-aggregation'); const require = createRequire(path.join(taDir, 'package.json')); const bcrypt = require('bcrypt'); const pg = require('pg'); const configPath = path.join(repoRoot, 'config/omnl-super-admins.v1.json'); const dryRun = process.argv.includes('--dry-run'); const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')); const databaseUrl = process.env.DATABASE_URL; function passwordFor(admin) { const envKey = `OMNL_SUPER_ADMIN_${admin.id.replace(/-/g, '_').toUpperCase()}_PASSWORD`; const fromEnv = process.env[envKey]?.trim(); if (fromEnv) return fromEnv; return crypto.randomBytes(18).toString('base64url'); } async function hashPassword(plain) { return bcrypt.hash(plain, 12); } const rows = []; for (const admin of cfg.superAdmins) { const password = passwordFor(admin); rows.push({ admin, password, hash: dryRun ? '(dry-run)' : await hashPassword(password) }); } if (dryRun) { console.log(JSON.stringify(rows.map(({ admin, password }) => ({ id: admin.id, username: admin.username, email: admin.email, portalCtid: admin.portalCtid, passwordWouldGenerate: !process.env[`OMNL_SUPER_ADMIN_${admin.id.replace(/-/g, '_').toUpperCase()}_PASSWORD`], })), null, 2)); process.exit(0); } if (!databaseUrl) { console.error('Set DATABASE_URL (PostgreSQL) to seed control-panel super_admin users.'); process.exit(1); } const client = new pg.Client({ connectionString: databaseUrl }); await client.connect(); await client.query(` CREATE TABLE IF NOT EXISTS admin_users ( id SERIAL PRIMARY KEY, username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255), password_hash TEXT NOT NULL, role VARCHAR(50) NOT NULL DEFAULT 'admin', is_active BOOLEAN DEFAULT true, last_login TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); `); for (const { admin, password, hash } of rows) { await client.query( `INSERT INTO admin_users (username, email, password_hash, role, is_active) VALUES ($1, $2, $3, 'super_admin', true) ON CONFLICT (username) DO UPDATE SET email = EXCLUDED.email, password_hash = EXCLUDED.password_hash, role = 'super_admin', is_active = true, updated_at = NOW()`, [admin.username, admin.email, hash], ); } await client.end(); console.log('=== OMNL super_admin accounts (one-time — distribute securely) ==='); for (const { admin, password } of rows) { console.log(`${admin.displayName}`); console.log(` Portal CT ${admin.portalCtid} · ${admin.products.join(', ')}`); console.log(` Username: ${admin.username}`); console.log(` Email: ${admin.email}`); console.log(` Password: ${password}`); console.log(''); }