v2.5 安全加固 + 权限体系 + 封面网络化

- 双密码(管理员/用户) + requireAdmin 角色隔离
- 登录失败按IP锁定 + 搜索缓存限流
- 封面远程URL优先+本地回退
- sessions.role / songs.cover_remote 运行时迁移
This commit is contained in:
Xiao-no-love committed 2026-09-25 22:25:25 +08:00
1 parent 16df0a7d08
commit f07fbbc21f
13 files changed
+236 -40

No files matched your search

+42
View File
@@ -415,3 +415,45 @@ python test_enrich.py # 给 5 首补封面(已跑过,5/5 成功)
- `login.html` 独立暗色主题(自己的 `:root`)。
- `body` 字体栈、`--log-bg` 终端风格:保留。
---
## 18. v2.5 安全加固 + 权限体系 + 封面网络化(已完成 2026-09-25)
> 老板提出 6 点问题,本轮解决其中 5 点(延迟调节因复杂度暂缓)。
### 18.1 双密码 + 角色权限(核心)
- **config.js**:`ADMIN_PASSWORD`(兼容旧 `PANEL_PASSWORD`)/ `USER_PASSWORD`;新增 `LOGIN_MAX_FAILS`/`LOGIN_LOCK_MS`/`SEARCH_CACHE_TTL_MS`/`SEARCH_RATE_MAX`/`SEARCH_RATE_WINDOW_MS`。
- **db.js**:`sessions` 表加 `role` 列;`songs` 表加 `cover_remote` 列。均带**运行时迁移**(PRAGMA table_info 检测后 ALTER)。
- **auth.js**:重写。`verifyLogin` 返回 `"admin"|"user"|null`;`createSession(role)`;新增 `requireAdmin` 中间件、`getRole/isAdmin`;**按 IP 登录失败锁定**(默认 5 次锁 10 分钟,内存 Map)。
- **routes/auth.js**:登录返回 `{ok, role}`;锁定返回 429;`/me` 返回 role。
- **权限矩阵**:用户 = 管理员 − 上传 − 设置页。`/api/music/import` 加 `requireAdmin`;`/api/system/*` 整块 `requireAdmin`。其余接口(资源/流/播放列表/搜索/下载)两者一致。
- **前端**:`init()` 先查 `/api/auth/me` 拿 role → `applyRoleUI()` 隐藏设置页 tab + 本地导入卡片;`loadSettings()` 非管理员直接 return。
### 18.2 搜索缓存 + 限流(防服务器 IP 被封)
- **实测确认**:网易云搜索 API **无 CORS 头**(返回 text/plain),**无法下放到浏览器**,必须后端代理。
- **对策**:`routes/music.js` 的 `/search` 加 ①内存缓存(同关键词 TTL 5 分钟内直接返回,上限 200 条 LRU);②按 IP 限流(默认 60 秒 20 次,超限 429)。命中缓存不消耗限流额度。
### 18.3 封面网络化(远程优先 + 本地回退)
- **实测确认**:网易云图片 CDN 返回 `Access-Control-Allow-Origin: *` 且不校验 Referer,浏览器可直接加载。
- **改动**:`enrichSong` 下载封面时**同时**存远程 URL(`cover_remote`)和本地(`cover`);API 返回 `albumImage`(远程优先)+ `coverLocal`(本地兜底)。
- **前端**:`coverSrcAttr(item)` 生成 `<img src=远程 data-local=本地 onerror=回退>`;mini player / 收听页封面同样 onerror 回退。
### 18.4 刷新优化
- init 时统一获取角色;SSE 事件保持增量刷新(`loadPlaylist`+`refreshLive` 节流)。
### 18.5 验证
- 回归:**api 16/16、e2e 17/17、write 5/5** 全绿。
- 新增功能手测(curl):
- admin 登录 → `/api/system/cache` 200、`/logs` 200;user 登录 → 同接口 **403**,`/api/music/import` **403**,`/api/resources` 200、`/api/streams` 200。
- `/api/auth/me` 正确返回 role。
- 搜索同关键词第二次返回 `cached:true`。
### 18.6 启动方式
- `start.cmd` 更新为:`set "ADMIN_PASSWORD=test123"` + `set "USER_PASSWORD=user123"` + `node index.js`。
- 托管启动:`cmd /c "set ADMIN_PASSWORD=test123&& set USER_PASSWORD=user123&& node index.js"`(注意用 `&&` 且不加尾空格,否则密码带空格)。
### 18.7 未做
- **播放延迟调节**(HLS_TIME 运行时热更新):老板明确暂缓。
- 用户密码/管理员密码的 UI 设置界面(现走环境变量)。
- 会话表已有 role 列,但登录失败锁定的 failMap 是内存态,重启清零(可接受)。