feat: 管理员账号体系 + 动态邀请码管理; 各端内置数据固定 uuid 对齐

This commit is contained in:
root
2026-10-16 15:24:49 +08:00
parent e0866aa690
commit dc520bfac0
7 changed files with 305 additions and 134 deletions
+29 -5
View File
@@ -4,14 +4,28 @@ import { getDb } from './db.mjs';
export function register({ username, password, device_name, invite_code }) {
const d = getDb();
// 多用户管控:邀请码(设置后必填)+ 用户数上限(0=不限)
const invite = process.env.INVITE_CODE;
if (invite && invite_code !== invite) throw new Error('邀请码无效');
// 多用户管控:管理员颁发邀请码;env INVITE_CODE 作为静态兜底
const maxUsers = Number(process.env.MAX_USERS || 0);
if (maxUsers > 0) {
const cnt = d.prepare('SELECT COUNT(*) AS c FROM users').get().c;
if (cnt >= maxUsers) throw new Error('注册名额已满,请联系管理员');
}
const userCount = d.prepare('SELECT COUNT(*) AS c FROM users').get().c;
let isAdmin = 0;
if (userCount === 0) {
isAdmin = 1; // 首位注册者自动成为管理员
} else {
const code = String(invite_code || '').trim().toUpperCase();
if (code) {
const row = d.prepare('SELECT id, valid FROM invite_codes WHERE code=?').get(code);
if (!row || !row.valid) throw new Error('邀请码无效或已使用');
var __usedInviteId = row.id; // eslint-disable-line no-var
} else if (process.env.INVITE_CODE && invite_code === process.env.INVITE_CODE) {
// 静态兜底码
} else {
throw new Error('需要邀请码,请联系管理员');
}
}
if (!/^[a-zA-Z0-9_-]{3,32}$/.test(username || '')) throw new Error('用户名需为 3-32 位字母数字-_');
if (!password || password.length < 8) throw new Error('密码至少 8 位');
const exists = d.prepare('SELECT 1 FROM users WHERE username=?').get(username);
@@ -20,8 +34,12 @@ export function register({ username, password, device_name, invite_code }) {
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.scryptSync(password, salt, 64).toString('hex');
const user_id = crypto.randomUUID();
d.prepare('INSERT INTO users (id, username, password_hash, salt) VALUES (?,?,?,?)')
.run(user_id, username, hash, salt);
d.prepare('INSERT INTO users (id, username, password_hash, salt, is_admin) VALUES (?,?,?,?,?)')
.run(user_id, username, hash, salt, isAdmin);
if (typeof __usedInviteId === 'number') {
d.prepare("UPDATE invite_codes SET used_by=?, used_at=datetime('now','localtime'), valid=0 WHERE id=?")
.run(user_id, __usedInviteId);
}
return issueToken(user_id, device_name);
}
@@ -56,6 +74,12 @@ function issueToken(user_id, device_name) {
return { token, user_id, device_id, device_name: name, expires_at: expires };
}
export function isUserAdmin(user_id) {
const d = getDb();
const row = d.prepare('SELECT is_admin FROM users WHERE id=?').get(user_id);
return !!row && !!row.is_admin;
}
export function authUser(req) {
const d = getDb();
const header = req.headers['authorization'] || '';