Add multi-admin account management

Schema:
- New AdminUser model with bcrypt-hashed password (cost 10)
- Roles: admin (full) / operator (read-only ops)
- Status: active / disabled
- MustChangePassword flag forces first-login password change

Backend:
- store: Add admins [] + CRUD methods (ListAdmins strips PasswordHash)
- service: SeedDefaultAdminIfEmpty (uses env credentials on first run),
  CreateAdmin, ChangePassword, ResetPassword, SetAdminStatus, DeleteAdmin
- middleware: JWT now carries user_id (UUID)
- api: login() uses bcrypt + updates last_login_at/ip, blocks disabled
- api: me() returns role + must_change_password
- api: new endpoints:
    POST /api/me/password       (self password change)
    GET  /api/admins
    POST /api/admins             (create)
    POST /api/admins/:id/password (reset by admin)
    POST /api/admins/:id/status   (enable/disable)
    DELETE /api/admins/:id        (with self/last-admin guard)

Frontend:
- Login: must_change_password=true triggers forced change-password dialog
- Layout: admin dropdown shows role tag + 修改密码 / 退出登录
- New /admins page (admin only) with table + create/reset/status/delete
- Router guard hides /admins from non-admin accounts
- API client: Auth.changePassword, Admins.{list,create,resetPassword,setStatus,delete}

Security:
- PasswordHash stored as bcrypt $2a$10$... in db.json
- ListAdmins always returns PasswordHash=''; never leaks via API
- Login returns 403 for disabled accounts

Verified: 21/21 API tests + browser E2E (first-login forced change,
restart persistence, admin list without hash, role-based menu)
This commit is contained in:
cnbugs
2026-08-09 21:45:41 +08:00
parent 4c8b7b5188
commit 09f6918aeb
12 changed files with 778 additions and 23 deletions
+35 -4
View File
@@ -50,6 +50,7 @@
| 客户端证书 | 一键签发,自动生成 `.ovpn`(内嵌 CA/Cert/Key/TLS-Auth),无需额外文件 |
| 固定 IP | 通过 CCD (`client-config-dir`) 为指定用户分配固定 VPN IP |
| **访问控制(白名单)** | 实例/用户两层 allow_networks,合并生效;服务端 iptables 强制隔离 |
| **多账号管理** | db.json 中存 bcrypt 哈希,可创建多个 admin/operator 账号,首登强制改密 |
| 启停控制 | Web 一键启动/停止实例,显示 PID 与状态 |
| 流量审计 | 解析 `status-version 3` 输出,记录上下行字节/连接时长 |
| 证书到期提醒 | 仪表盘统计 30 天内到期的证书,单独证书管理页查看完整清单 |
@@ -283,17 +284,47 @@ sudo ./scripts/install.sh -u
⚠️ **生产环境第一步**
当前版本通过 systemd 环境变量修改密码:
### 首次登录强制改密
首次访问用默认账号 `admin/admin123` 登录后,系统**强制**弹出"修改初始密码"对话框,
必须改成 ≥6 位的强密码才能继续使用,无法跳过。这一步密码修改后会写入 `data/db.json`,
**之后不再需要重启服务**,密码以 bcrypt 哈希持久化。
### 通过 Web 修改自己的密码
右上角 admin 下拉 → "修改密码" → 填写当前密码 + 新密码 → 保存,立即生效。
### 创建/管理其他账号
Web → 左侧"管理员"菜单(admin role 才可见):
| 操作 | 说明 |
| ---- | ---- |
| 新建账号 | 设置用户名、密码、角色(admin / operator) |
| 重置密码 | 不知道旧密码也能强制改密,新密码立即生效 |
| 启用/禁用 | 禁用后该账号无法登录(已登录的 token 仍可短期使用) |
| 删除 | 不能删除自己、不能删除最后一个 admin |
所有账号操作都进入审计日志。
### 通过 systemd 环境变量重置(忘记密码时)
如果你把所有 admin 都禁用/删除了,或者忘了所有密码,可以临时用环境变量密码
覆盖(只用于"首次登录然后改密",不持久):
```bash
systemctl edit --full openvpn-manager
# 找到 OVPNMGR_ADMIN_PASS 一行,改成你的密码
# 也建议修改 OVPNMGR_JWT_SECRET 为随机串
# 找到 OVPNMGR_ADMIN_PASS 一行,改成你临时用的密码
systemctl daemon-reload
systemctl restart openvpn-manager
# 用新密码登录,系统会触发 SeedDefaultAdminIfEmpty
# 注:此路径只在 db.json 中无任何 admin 时生效;否则会校验 db.json 中的账号
```
或重新跑 `install.sh --pass 新密码`
更可靠的做法:**直接编辑 `data/db.json`**,把 `password_hash` 替换为 bcrypt 哈希
(用 `htpasswd -bnBC 10 "" yourpass | tr -d ':\n'` 生成)后重启服务。
> 环境变量里的 `OVPNMGR_ADMIN_USER` / `OVPNMGR_ADMIN_PASS` 仅在数据库为空时作为初始 seed 使用。
## 创建第一个实例