feat: styled login page + structured JSONL audit logging
- Browser navigation now gets a self-contained login page (no JS, CSP hardened, XSS-escaped, cross-origin POST rejected) instead of the native Basic dialog; API clients keep 401 + WWW-Authenticate. - Login/logout endpoints (/panel-auth/login, /panel-auth/logout) issue and revoke the signed cookie, then 303 back to the original target. - Structured audit log (login-ok/login-fail/logout/challenge/reject with username, IP, UA, reason), default $DSH_HOME/panel-auth-audit.jsonl, 5MB rotation; X-Forwarded-For honored behind the reverse proxy. - Function-plugin form, per-request config, fail-open when unconfigured. - Tests extended to 15 flows including login page, login POST, logout, audit assertions and XSS escaping.
This commit is contained in:
@@ -1,17 +1,39 @@
|
||||
# panel-auth — DSH 面板访问密码插件
|
||||
|
||||
给 `dsh web` 面板加一层原生 HTTP 认证(Basic + 签名 Cookie),挂载在
|
||||
`/root/.dsh/profiles/web/cordis.patch.yml` 的用户补丁层,升级 DSH 不会丢。
|
||||
给 `dsh web` 面板加一层原生 HTTP 认证(自绘登录页 + Basic + 签名 Cookie),
|
||||
挂载在 `/root/.dsh/profiles/web/cordis.patch.yml` 的用户补丁层,升级 DSH 不会丢。
|
||||
|
||||
## 认证逻辑
|
||||
|
||||
- 每个 HTTP 请求 / WebSocket 升级都必须携带以下二者之一:
|
||||
1. 有效的 `dsh_panel` 签名 Cookie(登录成功后签发,浏览器自动带上,WS 握手也带);
|
||||
2. 有效的 HTTP Basic 凭据(`config.users` 中的用户名 + scrypt 哈希)。
|
||||
- 匿名请求返回 `401` + `WWW-Authenticate`,浏览器弹原生密码框。
|
||||
- 凭 Basic 成功登录时签发 Cookie(默认 30 天),之后无需重复输入。
|
||||
- **Fail-open**:`users` 为空或 `secret` 缺失/过短时插件不拦截任何请求,
|
||||
配置写错不会把面板锁死。
|
||||
- 浏览器访问(`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 自动轮转为 `<path>.1`。
|
||||
- 查看示例:`tail -f /root/.dsh/panel-auth-audit.jsonl | jq .`
|
||||
|
||||
> IP 说明:面板经 Caddy 反代时,直连 socket 是回环地址;插件在检测到回环
|
||||
> 来源时会取 `X-Forwarded-For` 的首个值作为真实 IP(Caddy 默认会带上该头)。
|
||||
|
||||
## 修改密码
|
||||
|
||||
@@ -20,11 +42,8 @@ cd /root/.dsh/profiles/web/panel-auth
|
||||
node hash.js admin 新密码 # 输出新 passwordHash
|
||||
```
|
||||
|
||||
把输出的 `passwordHash` 替换进 `../cordis.patch.yml` 后:
|
||||
|
||||
```bash
|
||||
systemctl restart dsh-web # 或利用 loader 对 cordis.patch.yml 的 HMR 自动生效
|
||||
```
|
||||
把输出的 `passwordHash` 替换进 `../cordis.patch.yml` 后(loader HMR 热应用;
|
||||
如未生效则 `systemctl restart dsh-web`)。
|
||||
|
||||
## 新增用户
|
||||
|
||||
@@ -37,7 +56,7 @@ node hash.js --secret # 生成新 secret
|
||||
```
|
||||
|
||||
替换 `cordis.patch.yml` 的 `secret` 后重启。注意:更换密钥会使所有已签发
|
||||
Cookie 立即失效,所有人需重新输入密码。
|
||||
Cookie 立即失效,所有人需重新登录。
|
||||
|
||||
## 应急解锁(忘记密码时)
|
||||
|
||||
@@ -45,10 +64,9 @@ Cookie 立即失效,所有人需重新输入密码。
|
||||
- 临时把 `users` 置为 `[]`(fail-open,面板恢复无密码状态),或
|
||||
- 用上面的命令生成新哈希替换。
|
||||
|
||||
改动会由 loader 的用户补丁层 HMR 热应用,无需重启。
|
||||
|
||||
## 文件
|
||||
|
||||
- `index.js` — 插件本体(webServer 包装 + 认证守卫)
|
||||
- `index.js` — 插件本体(登录页、认证守卫、审计日志、webServer 包装)
|
||||
- `crypto.js` — scrypt 哈希 / HMAC Cookie 签名(无依赖,纯 node:crypto)
|
||||
- `hash.js` — 生成密码哈希与随机密钥的 CLI
|
||||
- `test.mjs` — 单元测试(`node test.mjs`,覆盖 15 组流程)
|
||||
Reference in New Issue
Block a user