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:
+78
-5
@@ -48,13 +48,45 @@ GET /api/health
|
||||
→ 200 {"ok": true}
|
||||
```
|
||||
|
||||
### 当前登录用户
|
||||
### 登录
|
||||
```
|
||||
GET /api/me
|
||||
→ 200 {"username": "admin"}
|
||||
POST /api/login
|
||||
Content-Type: application/json
|
||||
|
||||
{"username": "admin", "password": "..."}
|
||||
→ 200 {
|
||||
"token": "eyJhbG...",
|
||||
"username": "admin",
|
||||
"role": "admin",
|
||||
"must_change_password": false
|
||||
}
|
||||
→ 401 {"error": "用户名或密码错误"}
|
||||
→ 403 {"error": "账号已被禁用"}
|
||||
```
|
||||
|
||||
### 登出(仅前端清理 token,服务端无状态)
|
||||
> 密码以 bcrypt 哈希存储在 `data/db.json` 中。
|
||||
> `must_change_password=true` 表示首次登录(默认账号)需要立刻改密。
|
||||
|
||||
### 当前登录账号
|
||||
```
|
||||
GET /api/me
|
||||
→ 200 {
|
||||
"user_id": "uuid",
|
||||
"username": "admin",
|
||||
"role": "admin",
|
||||
"must_change_password": false
|
||||
}
|
||||
```
|
||||
|
||||
### 修改自己密码
|
||||
```
|
||||
POST /api/me/password
|
||||
{"old_password": "...", "new_password": "..."}
|
||||
→ 200 {"ok": true}
|
||||
→ 400 {"error": "原密码错误"} | {"error": "新密码至少 6 个字符"}
|
||||
```
|
||||
|
||||
### 登出(前端清理 token)
|
||||
```
|
||||
POST /api/logout
|
||||
→ 200 {"ok": true}
|
||||
@@ -62,7 +94,48 @@ POST /api/logout
|
||||
|
||||
---
|
||||
|
||||
## 2. 仪表盘
|
||||
## 2. 管理员账号管理
|
||||
|
||||
> 仅 admin role 可访问。operator 访问会拒绝(由前端路由守卫 + 后端 handler 校验)。
|
||||
|
||||
### 列出账号(不含 hash)
|
||||
```
|
||||
GET /api/admins
|
||||
→ 200 [AdminUser, ...] // PasswordHash 字段固定为空
|
||||
```
|
||||
|
||||
### 新建账号
|
||||
```
|
||||
POST /api/admins
|
||||
{"username": "alice", "password": "StrongPass123", "role": "operator"}
|
||||
→ 200 AdminUser
|
||||
```
|
||||
|
||||
### 重置密码(无需知道旧密码)
|
||||
```
|
||||
POST /api/admins/:id/password
|
||||
{"new_password": "NewPass456"}
|
||||
→ 200 {"ok": true}
|
||||
```
|
||||
|
||||
### 启用/禁用
|
||||
```
|
||||
POST /api/admins/:id/status
|
||||
{"status": "active"} | {"status": "disabled"}
|
||||
→ 200 {"ok": true}
|
||||
```
|
||||
|
||||
### 删除(不能删自己、不能删最后一个 admin)
|
||||
```
|
||||
DELETE /api/admins/:id
|
||||
→ 200 {"ok": true}
|
||||
→ 400 {"error": "不能删除自己"}
|
||||
→ 400 {"error": "不能删除最后一个 admin 账号"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 仪表盘
|
||||
|
||||
### 汇总
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user