// 带外创建/重置【管理员账号】。 // // 为什么需要它:超管资格 = .env 的 SUPER_ADMIN_EMAILS 白名单里的邮箱,谁注册了谁就是超管。 // 但自助注册【已被安全策略禁止用白名单邮箱】(否则管理员邮箱尚无账号时,任何人都能抢注→拿到全部后台 // 权限:改大师/生成深伪/结算分成/读 PII)。所以管理员账号只能【带外预置】——就是这个脚本。 // // 用法(在服务器上,后端目录内): // ADMIN_EMAIL=you@example.com ADMIN_PASSWORD='你的强密码' node scripts/create-admin.js // // 幂等:账号已存在 → 重置密码;不存在 → 新建。密码只用 scrypt 存 hash(与 routes/auth.js 完全一致), // 脚本【绝不打印密码】。 import 'dotenv/config'; import crypto from 'node:crypto'; import { connectDb, getDb, closeDb } from '../src/db.js'; // 与 routes/auth.js 的 hash() 完全一致,否则建出来的号登不进去。 function hash(password, salt) { return crypto.scryptSync(password, salt, 64).toString('hex'); } async function run() { const email = String(process.env.ADMIN_EMAIL || '').trim().toLowerCase(); const password = String(process.env.ADMIN_PASSWORD || ''); if (!email || !email.includes('@')) { console.error('❌ 需要 ADMIN_EMAIL(合法邮箱)'); process.exit(1); } if (password.length < 8) { console.error('❌ 需要 ADMIN_PASSWORD,且至少 8 位(管理员账号别用弱密码)'); process.exit(1); } await connectDb(); const db = getDb(); const now = new Date(); const salt = crypto.randomBytes(16).toString('hex'); const existing = await db.collection('users').findOne({ email }); if (existing) { await db.collection('users').updateOne( { _id: existing._id }, { $set: { salt, passwordHash: hash(password, salt), updatedAt: now } } ); console.log(`✅ 账号已存在 → 已重置密码:${email}`); } else { const name = email.split('@')[0]; await db.collection('users').insertOne({ email, name, nickname: name, salt, passwordHash: hash(password, salt), entitlements: [], createdAt: now, updatedAt: now, }); console.log(`✅ 已新建账号:${email}`); } // 提醒:账号有了还不等于是超管——还要这个邮箱在 .env 的 SUPER_ADMIN_EMAILS 里。 const whitelist = (process.env.SUPER_ADMIN_EMAILS || '') .split(',').map((s) => s.trim().toLowerCase()).filter(Boolean); if (whitelist.includes(email)) { console.log(`✅ 该邮箱已在 SUPER_ADMIN_EMAILS 白名单里 → 登录后即为超管。`); } else { console.log(`⚠ 该邮箱【不在】SUPER_ADMIN_EMAILS 白名单里,现在登录只是普通账号。`); console.log(` 请把 .env 的 SUPER_ADMIN_EMAILS 设为 ${email} 并重启后端(grant-superadmin 工作流会做这步)。`); } console.log('(本脚本从不打印密码。请用你设置的密码登录,登录后建议立刻改成只有你知道的强密码。)'); await closeDb(); } run().catch((e) => { console.error(e); process.exit(1); });