Skip to main content
Back to plugins
J

dsh-auth-gate

jiang539/dsh-auth-gate

DSH Web UI 的认证门禁插件,提供 SVG 图形验证码与防暴力破解保护

Install

dsh plugin --profile web add github:jiang539/dsh-auth-gate

README

dsh-auth-gate

English: README.en.md · 简体中文

面向 DeepSeek Harness(DSH)的安全认证插件。 它在 DSH Web UI 前加一道登录门,并提供 Nginx auth_request 可强制校验的 /auth API, 让局域网或公网部署获得身份验证与防暴力破解能力,且无需改动 DSH 本身。

对接真实的 DSH 插件 API(ctx.webServer.register、Cordis 槽位系统、dsh.client 打包契约)实现。

工作原理

外网用户 → Nginx (HTTPS + 限流)
         → auth_request (Nginx 层认证校验, 子请求到 /auth/verify)
         → DSH Web UI (插件登录门 + 登录页)

DSH 内部: dsh-auth-gate 插件注册 /auth/* 路由
         - GET  /auth/captcha → 图形验证码 { svg, uuid }   (一次性)
         - POST /auth/login   → 校验 验证码+账号+密码 → { token }
         - GET  /auth/verify  → 校验 Token (供 Nginx auth_request 调用)
         - POST /auth/logout  → 销毁会话
         - POST /auth/username → 修改自己的账号名称 (需登录)
         - POST /auth/password → 修改自己的密码 (需登录, 校验旧密码)
         - GET  /auth/security  → 安全设置状态与实时数据 (需登录)
         - POST /auth/security  → 修改安全设置并持久化 (需登录)

两层相互独立的强制校验:

机制说明
Nginx 层auth_request /_auth → 子请求 GET /auth/verify没有合法 Token 的请求在到达 DSH 之前就被 401 拒绝
插件层客户端登录门 + 服务端会话浏览器打开页面时校验 Token;未登录时整个 UI 被登录页遮挡,服务端不签发会话

功能特性

  • SVG 图形验证码 — 一次性使用、过期自动失效、剔除 0o1i 等易混淆字符;插件级按 IP 限流(每 60 秒 60 次,存储上限 5000 条),无 Nginx 前置时同样有界
  • 防暴力破解(按 IP) — 同一 IP 在 blockDuration 窗口内的连续失败(验证码错误、验证码过期/无效或密码错误均计数)达到 maxLoginAttempts 后锁定 blockDuration 秒;另有 Nginx 限流兜底
  • 防暴力破解(按账号) — 任一账号(不限 IP)在 blockDuration 窗口内的凭据失败达到 accountMaxLoginAttempts 后锁定该账号,IP 轮换无法绕过;验证码错误不计入此计数(避免被用于投毒锁定他人账号)
  • 会话管理 — 服务端内存存储 Token + 滑动过期(/auth/verify 每次调用顺延 sessionTimeout
  • 会话绑定 IP — 可选能力:将 Token 绑定到登录时的客户端 IP(bindSessionToIp),在其他 IP 上使用立即失效并销毁会话,被 XSS/日志窃取的 Token 无法异地使用;默认关闭(客户端 IP 不固定的部署保持关闭,否则 IP 变化会强制下线)
  • 单点登录 — 每个账号同时只允许一个登录会话(singleSessionPerUser,默认开启):在别处再次登录会立即使该账号之前的所有会话失效(旧 Token 下次校验即 401,被挤下线),登录响应中的 kickedPrevious 标记可让新登录方感知这一行为
  • 密码安全 — bcrypt 哈希存储(username:bcrypt_hash,权限 0600,新哈希轮数 12),绝不存明文;创建/重置密码强制至少 8 个字符
  • 请求约束 — 所有 /auth 请求体必须为 application/json(否则 415),且限 64 KB(否则 413)
  • 审计日志 — 登录成功/失败(含锁定、验证码错误、凭据错误)均写入日志(含 IP 与用户名,绝不记录密码)
  • 双端集成 — Host 端注册 /auth/* 路由;Client 端通过 DSH 官方 Slot 机制注册登录页(root slot 优先级 -1 覆盖布局,登录成功后自动释放)
  • 信任代理 — 支持从 X-Forwarded-For 获取真实客户端 IP;仅当直接对端是回环地址(同机 Nginx)时才信任该头,且取代理追加的最后一项,客户端伪造的前缀无法绕过锁定

安装

开箱即用(默认凭据):如果启动时密码文件 ~/.dsh/auth.passwd没有任何账号, 插件会自动创建初始账号 admin,默认密码为 admin123,并在本次 DSH 启动日志中打印一次。 默认凭据是公开值:首次登录会被强制修改账号名与密码后才能进入系统, 且 admin 这个名称之后被保留禁用(任何账号都不能改名为它,改名后旧名立即失效)。 不需要此机制时,在配置中将 autoProvisionAdmin 设为 false(见「配置」)。

# 1. 将插件添加到 profile(会作为 profile 的依赖安装)
dsh plugin --profile web add dsh-auth-gate

# 2.(可选,推荐)创建带 bcrypt 哈希的密码文件(每行一个用户)——不执行此步则使用上面的默认账号
mkdir -p ~/.dsh
npx dsh-auth-passwd set admin            # 交互式输入密码,权限 0600
#   或手动生成(⚠️ 明文会出现在 shell 历史与进程列表中,仅限一次性使用):
node -e "console.log(require('bcryptjs').hashSync('你的密码', 10))" > ~/.dsh/auth.passwd

# 3. 重启 DSH
dsh web

DSH 启动时,插件的 cordis.patch.yml 会把 dsh-auth-gate 条目写入 profile, 客户端部分由 Web 客户端注册表(dsh.client 声明)自动加载。打开 Web UI 即可看到登录门。

本地开发安装:dsh plugin --profile web add ./path/to/dsh-auth-gate

验证是否生效

# 获取验证码
curl http://127.0.0.1:3080/auth/captcha

# 登录(将验证码答案和 uuid 替换为上一步返回的值)
curl -X POST http://127.0.0.1:3080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"your-password","captcha":"abcd","uuid":"<uuid>"}'

# 校验 Token(Nginx auth_request 即调用此接口)
curl -H "Authorization: Bearer <token>" http://127.0.0.1:3080/auth/verify   # → 200 + X-Auth-User

# 防暴力破解:连续 5 次错误 → 429,并返回 blockedUntil

配置

所有选项都在插件条目的 config 中设置(可在 profile 的 cordis.patch.yml--patch 覆盖层中修改):

Key默认值说明
passwordFile~/.dsh/auth.passwd密码文件路径(~ 展开为操作系统用户主目录)
sessionTimeout3600会话有效期(秒),滑动窗口续期
captchaExpires300验证码有效期(秒)
maxLoginAttempts5同一 IP 连续失败多少次后锁定
accountMaxLoginAttempts10任一账号(不限 IP)凭据连续失败多少次后锁定该账号(防 IP 轮换;验证码错误不计入)
blockDuration300锁定持续时长(秒,按 IP 与按账号共用)
bindSessionToIpfalse会话是否绑定登录时的客户端 IP(Token 离开该 IP 立即失效);默认关闭(本部署客户端 IP 不固定);仅当客户端 IP 固定时可设为 true
singleSessionPerUsertrue单点登录:同一账号再次登录会使之前所有会话立即失效(旧 Token 下次校验即 401,即"只能在一个地方登录,其它地方登录后前面登录的掉线");需要多处同时登录可设为 false
trustProxyfalse是否信任 X-Forwarded-For(仅当 Nginx 与 DSH 同机、直接对端为回环地址时生效;取代理追加的最后一项)
devCaptchaTextfalse仅限开发 — 在 /auth/captcha 中回显验证码答案,便于 curl 调试;生产环境切勿开启
defaultAdminUseradmin初始账号的用户名(仅当密码文件为空且 autoProvisionAdmin 开启时自动创建)。该名称是保留名:登录时会被强制改名,且任何账号都不能改名为它
defaultPasswordadmin123初始账号的默认密码(公开值;首次登录强制修改;至少 8 个字符)
autoProvisionAdmintrue启动时若密码文件里没有任何账号,是否自动创建初始账号并把默认密码打印到日志

profile 覆盖示例:

# ~/.dsh/profiles/web/cordis.patch.yml
- id: dsh-auth-gate
  config:
    sessionTimeout: 7200
    maxLoginAttempts: 10
    blockDuration: 600

passwordFiledevCaptchaText 外,其余选项(sessionTimeoutcaptchaExpiresmaxLoginAttemptsblockDurationtrustProxysingleSessionPerUser)都可以在登录后通过 设置 → 个人配置 → 安全设置 在线修改:修改立即生效,并持久保存到 密码文件同目录的 auth-gate.security.json(权限 0600),重启后依然有效; profile 中的值作为基准层,运行时覆盖层在其之上。为保证安全,数值有上下限 (会话超时 60–86400 秒、验证码有效期 30–3600 秒、失败阈值 1–100 次、锁定 时长 30–86400 秒),越界请求会被拒绝;devCaptchaText 刻意不提供在线开关 (仅限开发,任何环境都不应在生产开启)。

密码文件

格式:每行一个 username:bcrypt_hash(以 # 开头为注释),权限 0600。

npx dsh-auth-passwd hash            # 打印一个哈希,供手动使用
npx dsh-auth-passwd set <user>      # 添加/更新用户(交互式输入密码)
npx dsh-auth-passwd list            # 列出用户
npx dsh-auth-passwd delete <user>   # 删除用户

密码策略:sethash 均要求密码至少 8 个字符(与 Web 端修改密码一致)。

初始账号自动创建:当 autoProvisionAdmin 开启(默认)且密码文件中没有任何账号时, 插件启动时会创建 defaultAdminUser(默认 admin)并写入 defaultPassword(默认 admin123) 的 bcrypt 哈希(权限 0600),同时在本次启动日志中打印一次默认密码; 首次登录会被强制修改账号名与密码(改名后旧名称 admin 立即失效,且任何账号都不能再改名为它)。 已有账号的密码文件永远不会被改动。

个人配置(修改用户名 / 修改密码 / 退出登录)

登录后打开左下角 设置 面板,导航栏第一项即为 个人配置(通过官方 settings.section 槽位注册,位于「通用设置」之上),提供账号自助与安全设置: 修改用户名修改密码安全设置退出登录;样式使用 shell 的 --dsw-* 设计变量,自动跟随明暗主题。

安全设置 卡片展示每项防护措施的启用状态与当前参数(图形验证码、防暴力破解、 会话管理、单点登录、信任代理、请求体限制),并附在线会话数与当前锁定 IP 数; 下方可在线调整会话超时、验证码有效期、连续失败锁定阈值、锁定时长、信任代理开关与单点登录开关, 保存后立即生效并持久化(见「配置」一节)。

修改用户名需要输入新名称(1-32 个字符,不含空格和冒号;不能使用保留名 defaultAdminUser,默认 admin); 修改密码需要输入旧密码和新密码; 退出登录需二次确认,会调用 /auth/logout 销毁服务端会话并清除本地 Token;不影响正在执行的任务(子代理、后台作业等继续在服务端运行,重新登录后可见)。

服务端强制规则:

  • 必须已登录(Authorization: Bearer <token>);会话失效返回 401。
  • 旧密码必须与存储的 bcrypt 哈希匹配(否则 403;失败会计入与登录相同的按 IP 锁定)。
  • 新密码至少 8 个字符、最多 72 字节(bcrypt 上限)。
  • 修改成功后,该用户的其他登录会话全部失效,当前会话保持有效。

密码文件以原子方式重写(临时文件 + rename,权限 0600),读-验-写在同一串行队列中完成; 注释和其他用户的条目都会保留。管理员仍可直接在服务器上重置任意用户密码:

npx dsh-auth-passwd set <user>   # 覆盖任意用户的密码

Nginx 反向代理(公网 / 局域网部署)

DSH 刻意只监听 127.0.0.1。用同一台机器上的 Nginx 做前置(auth_request + 限流) 即可安全地对外暴露 — 公网 HTTPS 见 docs/nginx.conf.example, 局域网 HTTP 见 docs/nginx.conf.lan.example

nginx -V 2>&1 | grep -- 'http_auth_request_module'   # 检查模块是否可用
sudo nginx -t && sudo systemctl restart nginx

要点:

  • location /auth_request /_auth;子请求携带浏览器的 Authorization 头转发到 /auth/verify。 2xx 放行,401/403 拒绝。
  • /auth/login/auth/captcha 放行通过,但按 IP 限流。
  • limit_req 区域提供粗粒度的传输层限流;插件的失败计数存储提供细粒度的账号锁定。
  • HTTPS 使用 Let's Encrypt:apt install certbot python3-certbot-nginx && certbot --nginx -d your-domain.com

局域网 HTTP(IP 直连)

局域网内不需要 HTTPS 时,直接以 IP + 80 端口访问即可。把公网配置中的 listen 443 ssl 换成 listen 80、去掉 ssl_* 指令即可,auth_request 认证流程与传输层加密无关,行为完全一致 — 完整示例见 docs/nginx.conf.lan.example

# 只需把 server 块改成:
listen 80;
# server_name 留空或用本机 IP,如 server_name 192.168.1.10;

⚠️ HTTP 是明文传输:账号密码和会话 Token 在局域网内可被抓包看到。 仅建议在可信内网使用;任何对公网开放的部署都应使用上方的 HTTPS 配置。

安全说明

  • 默认凭据是公开的:开箱即用的 admin / admin123 会被打印到启动日志并写入文档, 任何读到这些信息的人都能在改密前登录。首次登录强制改名 + 改密只是缩短暴露窗口, 请务必在部署完成后立即完成这两步(改名后 admin 名称即被保留禁用), 或在不需要开箱即用时将 autoProvisionAdmin 设为 false,改用 dsh-auth-passwd set 创建自己的账号。注意:命令行工具 dsh-auth-passwd 属于服务器管理员工具,仍然可以直接 创建名为 admin 的账号——Web 端无法做到的事,管理员在服务器上始终可以做。
  • 插件信任:第三方 DSH 插件在启动时可以改写完整配置树(本门禁正是借此自启的)。 只安装可信来源的插件、锁定版本,并审查其 cordis.patch.yml
  • 内存态存储:会话、验证码和失败计数都存在内存中。进程重启会登出所有用户。 多实例部署时,可将这些 Map 换成共享存储(如 Redis)— 处理器被隔离在小型函数后面,替换很容易。
  • Nginx 才是强制校验点:插件的登录门只是隐藏了 UI,公网部署下对 /api 流量的权威校验 在 Nginx 的 auth_request 层。没有 Nginx 前置时,DSH 只会在回环地址上提供服务。
  • 密码文件:保持在 ~/.dsh/auth.passwd,权限 0600;用 dsh-auth-passwd set 轮换哈希(bcrypt 轮数 = 12,新哈希;旧哈希仍按各自轮数校验;创建/重置密码强制 至少 8 个字符)。
  • trustProxy 务必与部署形态匹配:仅在「同机 Nginx 反代」时开启(插件启动时会打警告)。 直接部署(无反代)时开启 trustProxy,任何能直连回环地址的进程都可伪造 X-Forwarded-For 绕过按 IP 锁定,甚至把任意 IP 投毒进锁定状态;反之,有反代却关闭 trustProxy 会让所有远端用户共享同一个(回环)锁定桶。
  • Token 存储于 localStorage:浏览器内的 XSS 可读取会话 Token(滑动窗口使其在被 使用期间一直有效)。高安全场景建议:为 Web UI 配置严格的 CSP。
  • 分布式暴力破解:插件内置的按账号锁定(accountMaxLoginAttempts)已覆盖 IP 池/轮换攻击;高安全场景可再叠加 Nginx 全局限流与 fail2ban 作纵深防御。

开发

npm install
npm run build          # tsc 构建 host → lib/host,esbuild 构建 client → lib/client.js
npm run typecheck
npm test              # 构建 host 后运行集成测试(覆盖验证码/锁定/改密/信任代理/并发写)

目录结构:

dsh-auth-gate/
├── package.json            # dsh.bundle.patch + dsh.client(platform: web)
├── cordis.patch.yml        # 向 profile 插入 host 条目
├── src/
│   ├── host/index.ts       # /auth/* 服务(captcha、login、verify、logout)
│   ├── client/index.tsx    # 登录门(root slot,优先级 -1)
│   ├── client/login.css    # 登录门样式
│   └── shared/types.ts     # 双端共享的线上类型
├── bin/dsh-auth-passwd.mjs # 密码文件 CLI
├── scripts/build-client.mjs# 将 esbuild 产物包装进 __ModuleLoader__.load()
└── lib/                    # 构建产物
    ├── host/               # host ESM(tsc)
    └── client.js           # client bundle(esbuild,CJS-in-loader 包装)

客户端 bundle 由 DSH 自身的模块系统在 /plugins/dsh-auth-gate/client.js 提供, 并自动注入 window.__DSH_BOOT__ — 无需额外接线。

Related plugins