DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

runfali /

dsh-login-gateway

Verified

dsh 登录门卫插件:让只监听本机的 dsh Web UI 安全地对外开放——登录墙 + 全量反代,零依赖、零侵入。

★ 2 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@c9fc7fd9

dsh-login-gateway

DeepSeek Harness(dsh)的登录门卫插件。dsh 的 Web UI 默认只监听 127.0.0.1:3080,禁止外部访问;本插件在外部再开一个入口(默认 0.0.0.0:3081),访问者先通过用户名密码登录,登录成功后流量被全量反向代理到 dsh 的 Web UI(HTTP 与 WebSocket 都支持),功能零缺失。

零运行时依赖(只用 Node.js 内置模块),Node 22+ ESM。


它解决什么问题

  • 想从局域网/公网访问本机 dsh,但 dsh 只监听 loopback;
  • 直接改 dsh 让它监听 0.0.0.0 会裸奔在公网上,任何人可访问;
  • 本插件提供:外部入口 + 登录墙 + 全量反代,dsh 本身继续只听 127.0.0.1。

效果预览

首次访问未初始化时,会进入 /setup 引导页,输入一次性令牌并创建管理员账号:

首次访问初始化页面

工作架构

浏览器 ──HTTP/WS──▶ 0.0.0.0:3081(门卫:登录校验 + 会话 Cookie)
                         │ 通过校验后全量反代(改写 Host/Origin/Sec-Fetch-Site 为 loopback 形态)
                         ▼
                    127.0.0.1:3080(dsh Web UI,信任围栏放行,特权 API 全可用)

关键点:反代时把请求头里的 Host/Origin/Sec-Fetch-Site 改写为 loopback 形态,让 dsh 把请求当作“本机请求”信任放行;WebSocket 升级请求同样先校验会话再转发。

快速开始(首次安装)

  1. 把本项目放到 dsh 服务器上(例如 /data/dsh-login-gateway),并安装依赖(仅 devDependencies,运行时无依赖):

    cd /data/dsh-login-gateway
    npm install
    
  2. 在 dsh 的 profile 配置里挂载插件(编辑 cordis.patch.yml,见下文“安装与卸载”),然后重启 dsh。

  3. 获取一次性初始化令牌(二选一):

    • 方式 A:看 dsh 启动终端或服务日志输出

      插件会直接输出一行提示,内容类似:

      [login-gateway] 登录门卫未初始化,请访问 http://<主机>:3081/setup 并输入一次性令牌:ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ12-3456
      

      日志示例:

      初始化令牌日志输出

    • 方式 B:读取令牌文件

      令牌同时会写入用户文件同目录下的 setup-token.txt(权限 0600,内容只有令牌本身):

      cat ~/.dsh-login-gateway/setup-token.txt
      
  4. 浏览器打开 http://<主机>:3081/setup,输入令牌、管理员用户名、密码(至少 8 位)与确认密码,点击“完成设置”。

  5. 初始化完成后会跳转到登录页,用刚创建的账号登录,即可进入 dsh。

说明:新装没有默认账号,必须走 /setup 引导创建。未初始化时访问 / 会自动 302 跳转到 /setup。令牌只在未初始化阶段生成;初始化完成后 /setup 会返回 410,setup-token.txt 也会被自动删除。

安装与卸载(cordis.patch.yml)

安装

在 dsh 的 profile 目录(如 /root/.dsh/profiles/web/)的 cordis.patch.yml 中追加挂载项:

- insert:
    - id: login-gateway
      name: '/data/dsh-login-gateway/src/index.js'
      config:
        listenHost: '0.0.0.0'
        listenPort: 3081
        targetHost: '127.0.0.1'
        targetPort: 3080
        sessionTtlHours: 24
        maxLoginAttempts: 5
        lockMinutes: 5

保存后重启 dsh(或使用 dsh 的热重载功能)。新装无需预先配置任何账号:重启后进入“未初始化”状态,按上文“快速开始”拿一次性令牌,打开 /setup 创建管理员账号即可。

卸载

  1. 从 cordis.patch.yml 删除上面的挂载项;
  2. 重启 dsh;
  3. 门卫端口 3081 停止服务,外部访问恢复为“无法访问”。

可选清理:删除门卫的用户数据目录(账号、令牌文件):

rm -rf ~/.dsh-login-gateway/

是否影响 dsh 本身

零侵入。门卫只做三件事:在外部开一个登录入口、校验会话、把通过校验的请求反代到 dsh 的 loopback 端口。它不修改 dsh 安装目录的任何文件,也不改 dsh 自身配置逻辑;唯一改动就是 cordis.patch.yml 里挂载它的那一段,卸载时删掉即可。卸载后 dsh 与安装前一致。

配置项

所有配置都有默认值,新装默认配置即可工作。完整配置表如下:

配置项 默认值 说明
listenHost 0.0.0.0 门卫监听地址,暴露给外部
listenPort 3081 门卫监听端口
targetHost 127.0.0.1 dsh Web UI 监听地址
targetPort 3080 dsh Web UI 监听端口
sessionTtlHours 24 登录会话有效期(小时)
maxLoginAttempts 5 同一 IP 连续失败多少次后锁定
lockMinutes 5 锁定持续分钟数
setupMaxAttempts 5 /setup 初始化时,同一 IP 连续失败多少次后锁定
setupLockMinutes 30 /setup 初始化锁定持续分钟数
proxyTimeoutMs 60000 反代上游响应头等待超时(毫秒),超时返回 504;WS 握手超时取与 15s 的较小值
streamIdleTimeoutMs 1800000 反代响应流空闲超时(毫秒,默认 30 分钟)
maxConnections 512 HTTP 服务最大并发连接数,超出后新连接被丢弃
userStorePath ~/.dsh-login-gateway/users.json 用户数据文件路径(可自定义)

使用说明

  • 登录:打开 http://<主机>:3081/,输入用户名密码。成功后会种下会话 Cookie(dsh_gw_session,HttpOnly + SameSite=Strict),之后访问全部走反代,包括 WebSocket。
  • 登出:POST /logout。页面无入口时可直接调用:curl -X POST http://<主机>:3081/logout。
  • 未登录访问:/ 返回登录页;其余路径返回 401 JSON。
  • 登录限速:同一 IP 连续输错 maxLoginAttempts 次会被锁定 lockMinutes 分钟。
  • 账号锁定:同一用户名跨 IP 累计失败 maxLoginAttempts 次也会被锁定,可防代理池分布式爆破。
  • 初始化限速:/setup 同样按 IP 限速,令牌错误、用户名空、密码过短、两次密码不一致都计失败。

安全说明

  • 务必走 HTTPS:门卫本身只做 HTTP 登录 + 反代,公网直接暴露会有明文传输风险。建议前置 Nginx/Caddy/云负载均衡做 TLS 终止(例如 443 -> 127.0.0.1:3081)。
  • 会话 Cookie 使用 HttpOnly + SameSite=Strict,页面无 XSS 注入点。
  • 一次性令牌为 32 位随机十六进制(128 bit 熵),只在未初始化时有效;初始化完成后立即失效并删除令牌文件。
  • 用户文件默认在 ~/.dsh-login-gateway/users.json,内含 scrypt 哈希(不可逆);写入时自动使用 0600 权限、目录自动 0700。
  • 登录/初始化限速按来源 IP 与用户名双维度独立计算,失败记录带 TTL,避免内存无限膨胀。
  • 反代超时:上游响应头等待超 proxyTimeoutMs 返回 504;响应头到达后改用 streamIdleTimeoutMs 空闲超时,SSE 长间隔输出不会被正常打断。
  • 并发连接上限:maxConnections 默认 512,并显式收紧 headersTimeout、requestTimeout、keepAliveTimeout,减少慢连接占用。
  • 安全响应头:门卫自己生成的响应统一带 X-Frame-Options: DENY、Referrer-Policy: no-referrer 和 CSP;反代透传的 dsh 响应保持原样。
  • 登录/初始化接口有请求体大小上限(100KB),防止恶意超大请求。

重置与常见问题

  • 重置管理员账号:删除用户文件后重启 dsh,会再次进入“未初始化”状态,并重新打印一次性令牌:

    rm -f ~/.dsh-login-gateway/users.json
    
  • 忘记或没看到一次性令牌:可以看 dsh 启动终端/服务日志,或读取 ~/.dsh-login-gateway/setup-token.txt。如果两者都没有,删掉用户文件并重启 dsh,会重新生成令牌。

  • /setup 返回 410:说明已初始化完成,设置入口已关闭,属正常现象。如需重新初始化,先删用户文件再重启。

  • 访问 http://<主机>:3081/ 打不开:检查 dsh 是否已启动、插件挂载是否生效、端口是否被防火墙拦截。

  • 登录后页面或接口 502:门卫反代目标 127.0.0.1:3080 不可达,确认 dsh Web UI 进程仍在运行。

  • 用户文件损坏:启动会直接报错并给出文件路径,不会静默重置。按上面的“重置管理员账号”处理即可。

技术实现

  • 认证:node:crypto scrypt(scrypt$N$r$p$salt$hash 自描述格式),恒定时间比较防时序攻击。
  • 会话:内存 Map + 过期清理(30 分钟定时 sweep)。
  • 反代:流式透传(SSE 长连接友好),剔除 hop-by-hop 头,WebSocket 升级用后端 rawHeaders 原样构造 101 响应。
  • 零运行时依赖,所有依赖仅存在于开发/测试环境。

目录结构

src/
  index.js        插件主入口:配置校验、路由分发、setup 引导、HTTP 服务 + WS 升级
  auth.js         密码哈希(scrypt)、会话存储、登录限速
  proxy.js        HTTP 反代(头改写 + hop-by-hop 剔除)与 WebSocket 升级转发
  user-store.js   用户文件存储(JSON + 原子写入)
  login-page.js   登录页 HTML(深色主题,单文件内联)
  setup-page.js   首次启动引导页 HTML(深色主题,单文件内联)
bin/
  hash.js         密码哈希生成 CLI
—/ 5

No ratings yet

Verified DSH bundle

Commit c9fc7fd9a734

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout