# panel-auth — DSH 面板访问密码插件 给 `dsh web` 面板加一层原生 HTTP 认证(自绘登录页 + Basic + 签名 Cookie), 挂载在 `/root/.dsh/profiles/web/cordis.patch.yml` 的用户补丁层,升级 DSH 不会丢。 ## 认证逻辑 - 浏览器访问(`Accept: text/html`):匿名时返回**内置登录页**(无 JS、CSP 收紧), 表单 POST 到 `/panel-auth/login`,成功后签发 Cookie 并跳回原目标地址。 - API / 脚本(fetch、curl 等):保持经典 `401 + WWW-Authenticate: Basic` 契约, 仍可用 Basic 凭据直接调用,成功后同样签发 Cookie。 - WebSocket 升级:匿名一律 `401`,带 Cookie 放行(浏览器对 WS 不保证携带 Authorization 头,因此依赖 Cookie)。 - `/panel-auth/logout`:清除 Cookie 并回到登录页。 - Cookie 默认 30 天(`cookieTtlSeconds`),HttpOnly + SameSite=Lax。 - **Fail-open**:`users` 为空或 `secret` 缺失/过短时不拦截任何请求, 配置写错不会把面板锁死。配置每次请求实时读取,改密码热生效。 ## 登录日志(审计) - 所有登录活动写入结构化 JSONL 文件,默认 `/root/.dsh/panel-auth-audit.jsonl`(可用 `auditLogPath` 配置修改)。 - 事件类型: | event | 含义 | | --- | --- | | `login-ok` | 登录成功(含用户名、IP、User-Agent) | | `login-fail` | 登录失败(含用户名、IP、UA、`reason`:`bad-credentials` / `missing-fields` / `cross-origin` / `body-too-large`) | | `logout` | 主动登出 | | `challenge` | 匿名浏览器导航被重定向到登录页 | | `reject` | 匿名 API / WebSocket 请求被拒绝(含方法、路径) | - 每条含 `ts`(ISO 时间)、`ip`(优先 `X-Forwarded-For`,见下)、`ua`。 - 文件超过 5MB 自动轮转为 `.1`。 - 查看示例:`tail -f /root/.dsh/panel-auth-audit.jsonl | jq .` > IP 说明:面板经 Caddy 反代时,直连 socket 是回环地址;插件在检测到回环 > 来源时会取 `X-Forwarded-For` 的首个值作为真实 IP(Caddy 默认会带上该头)。 ## 防爆破(默认开启) - **按 IP 计数**:同一 IP 在 `failureWindowSeconds`(默认 300 秒)窗口内连续 `maxFailures`(默认 5)次密码错误后进入锁定期。 - **阶梯式锁定**:首次锁 `lockoutBaseSeconds`(默认 30 秒),再次触发翻倍, 上限 `lockoutMaxSeconds`(默认 3600 秒)。 - 锁定期内:登录页返回 `429 + Retry-After`("尝试次数过多"),API 返回 `429 {"error":"too many attempts"}`,且**不再执行 scrypt 校验**(不消耗 CPU)。 - **失败延迟**:每次密码错误额外等待 `failedLoginDelayMs`(默认 300ms), 拖慢单连接暴力尝试。 - **成功即清零**;锁定状态为进程内存态,面板重启后清空(重启面板需要 root,攻击者无法自行重置)。 - Basic 认证路径同样计入;IP 取自反代 `X-Forwarded-For` 的最后一跳 (Caddy 会覆写伪造值,见下)。 - 配置(`cordis.patch.yml` 的 `config` 中可调): ```yaml bruteProtection: true # 关闭设为 false maxFailures: 5 lockoutBaseSeconds: 30 lockoutMaxSeconds: 3600 failureWindowSeconds: 300 failedLoginDelayMs: 300 ``` > 局限:分布式攻击(每尝试换一个 IP)不受单 IP 锁定约束;这由 > 300ms 失败延迟 + scrypt 慢哈希兜底。公网反代场景建议再配合 Caddy > 层的 IP 白名单/云防火墙(如 Cloudflare)使用。 ## 修改密码 ```bash cd /root/.dsh/profiles/web/panel-auth node hash.js admin 新密码 # 输出新 passwordHash ``` 把输出的 `passwordHash` 替换进 `../cordis.patch.yml` 后(loader HMR 热应用; 如未生效则 `systemctl restart dsh-web`)。 ## 新增用户 在 `cordis.patch.yml` 的 `users` 列表里再加一组 `username`/`passwordHash`。 ## 更换签名密钥 ```bash node hash.js --secret # 生成新 secret ``` 替换 `cordis.patch.yml` 的 `secret` 后重启。注意:更换密钥会使所有已签发 Cookie 立即失效,所有人需重新登录。 ## 应急解锁(忘记密码时) 编辑 `/root/.dsh/profiles/web/cordis.patch.yml`: - 临时把 `users` 置为 `[]`(fail-open,面板恢复无密码状态),或 - 用上面的命令生成新哈希替换。 ## 文件 - `index.js` — 插件本体(登录页、认证守卫、审计日志、webServer 包装) - `crypto.js` — scrypt 哈希 / HMAC Cookie 签名(无依赖,纯 node:crypto) - `hash.js` — 生成密码哈希与随机密钥的 CLI - `test.mjs` — 单元测试(`node test.mjs`,覆盖 15 组流程)