dsh-totp
为个人 DeepSeek Harness Web 实例提供 TOTP 访问验证。
简体中文 · English

在 DSH 入口输入身份验证器里的 6 位动态码,再进入你的工作区。支持 Google Authenticator、Microsoft Authenticator 等标准 TOTP 应用,绑定、保护开关、恢复码和锁定都在 DSH 内完成。
适合希望给个人 DSH Web 实例增加访问控制的用户,尤其是在局域网或远程访问场景中。它使用 TOTP 作为登录凭据,不依赖 Google / Microsoft 账号登录,也不需要额外搭建登录服务。
兼容目标:DSH 0.1.2-rc.1 · Node.js 24+ · MIT License
为什么使用它
| 你想解决的问题 | dsh-totp 提供的能力 |
|---|---|
| 不希望别人知道地址就能进入 DSH | 开启保护后,本机、内网和公网访问同一入口都需要动态码 |
| 不想维护另一套账号或登录站点 | 在 DSH 的「设置 → 访问验证」中扫码绑定 |
| 希望自己决定什么时候启用验证 | 首次安装默认关闭,绑定成功后开启,之后可随时切换 |
| 暂时离开时希望立即收回访问权限 | 一键锁定所有已授权页面和设备,无需再输入动态码 |
| 担心换手机后无法访问 | 提供一次性恢复码和验证器更换流程 |
| 不希望绑定完成后再等一轮验证码 | 当前绑定页保持可用,保存恢复码后直接继续使用 |
快速开始
1. 添加插件
通过已发布的 npm 包安装:
dsh plugin --profile web add dsh-totp
包已发布到 公共 npm registry。如果镜像尚未同步,可指定官方源:
dsh plugin --profile web add dsh-totp --registry=https://registry.npmjs.org
开发或检查当前源码时,在本仓库目录执行:
npm ci
dsh plugin --profile web add "$PWD"
插件按 profile 安装。上述命令添加到 web;如果使用其他 Web profile,请将 web 替换成相应名称。
本插件会替换该 profile 的 WebServer,并接管对外 HTTP 入口。请勿在同一 profile 叠加其他替换 WebServer 或接管登录流程的网关插件。插件直接分发 JavaScript,无需构建,也没有安装期脚本;普通打包不会运行测试或创建绑定数据。
2. 启动 DSH
如果 Web 实例已经运行,先停止它,再启动一次,让插件配置生效:
dsh --profile web --no-open
打开终端中 dsh-totp HTTP entry 打印的地址。
首次安装时,访问保护默认关闭。 你可以直接进入 DSH,再自行绑定验证器。升级、重装或普通重启会保留已有绑定和保护开关,不会把它们重置成默认值。
3. 扫码绑定
- 打开 DSH 的 设置 → 访问验证,点击 绑定验证器。
- 在 Google Authenticator 或 Microsoft Authenticator 中添加账号,扫描页面上的二维码。
- 输入新验证器当前显示的 6 位动态码,点击 确认绑定并开启保护。
- 保存页面给出的 10 个一次性恢复码,点击 已保存,继续使用 DSH。
绑定成功后,保护立即开启。当前绑定页面继续可用,直接回到设置页,无需再次登录或等待动态码刷新。 其他已打开页面的旧授权会失效。
仅生成二维码或取消绑定,不会开启保护。绑定和换绑均在网页中完成,不提供 CLI 绑定命令。
日常使用
| 操作 | 如何操作 | 结果 |
|---|---|---|
| 进入 DSH | 保护开启时,打开或刷新页面并输入动态码 | 获得当前页面的临时访问授权 |
| 开启 / 关闭保护 | 在设置页点击开关,输入一组新的动态码确认 | 立即生效,旧页面授权撤销;关闭后可直接访问 |
| 锁定所有设备 | 点击对话页右下角的「锁定」,或设置页的「锁定所有设备」 | 当前页和其他已授权页面立即锁定,无需动态码 |
| 更新恢复码 | 在设置页点击「更新恢复码」,输入新的动态码 | 生成 10 个新恢复码,旧恢复码失效 |
| 更换验证器 | 验证当前动态码,再扫描新二维码并确认新动态码 | 新验证器生效;当前页面可继续使用,其他页面重新验证 |
锁定按钮的状态:
- 未绑定验证器:置灰,提示先到设置页绑定。
- 已绑定但保护关闭:置灰,提示先到设置页开启保护。
- 已绑定且保护开启:可直接锁定,无需再次验证。
锁定会关闭访问连接,插件不会主动取消 DSH 后台任务。具体任务在连接中断后的行为仍由 DSH 和所用插件决定。
手机不可用时
在登录页点击 无法使用验证器,输入一个未使用过的恢复码,然后重新绑定验证器。
恢复码验证成功后,旧验证器和旧页面授权立即失效。在新验证器确认前,恢复授权仅允许绑定,不能访问 DSH 数据。确认新动态码并保存新恢复码后,可以直接进入 DSH,无需再登录一次。
每个恢复码只能使用一次。更新恢复码或成功换绑后,请用新的恢复码替换旧备份。
工作原理
插件在 DSH 的 Web 服务前增加一个统一访问入口,在服务端判断是否允许访问。下图描述的是访问保护开启时的请求路径:
flowchart LR
A["身份验证器<br/>生成 6 位 TOTP"] -. "用户读取并输入" .-> B["浏览器<br/>本机 / 内网 / 公网"]
subgraph H["运行 DSH 的主机"]
G["dsh-totp<br/>统一访问入口"]
V{"页面授权有效?"}
L["显示登录页<br/>拒绝受保护的数据请求"]
W["DSH WebServer<br/>127.0.0.1 · 动态内部端口"]
D[("本地状态<br/>绑定 / 限流 / 恢复码摘要")]
G --> V
V -- "否" --> L
V -- "是" --> W
G <--> D
end
B -- "HTTP / WebSocket" --> G
- 入口统一。 插件替换原有 WebServer 的监听配置,由受保护的入口对外提供访问,内部 DSH WebServer 限定在回环地址。
- 同一进程、同一访问地址。 插件运行在 DSH 进程内,扫码和管理使用同一个对外端口,不启动独立绑定服务。
- 授权落实在服务端。 页面携带临时凭据访问 API 和 WebSocket;隐藏按钮或登录界面本身不是安全边界。
- 原生凭据留在服务端。 网关完成 DSH 原生认证,再代理请求;不会将这组原生 token / Cookie 下发给浏览器。
- 绑定与登录衔接。 新动态码确认成功后,当前绑定页面的授权更新到新状态;其他页面的授权被撤销。
保护关闭时,入口允许直接进入 DSH。这个开关控制的是实际访问权限。
内网与远程访问
允许其他设备通过本机 IP 访问:
dsh --profile web --host 0.0.0.0 --port 3080 --no-open
使用域名、公网地址或端口映射时,额外声明浏览器访问的 host:port。例如:
dsh --profile web --host 0.0.0.0 --port 3080 --no-open \
--trusted-host dsh.example.com:3080
请替换示例域名。--trusted-host 检查的是请求访问的 Host,不是来源 IP 白名单,也不会自动配置 DNS、防火墙或路由器端口映射。本机 IPv4 网卡地址会自动加入允许的访问地址。
开启保护后,localhost、内网 IP 和公网入口遵循相同的动态码验证规则。
验证规则
| 项目 | 当前行为 |
|---|---|
| 动态码 | 标准 TOTP,HMAC-SHA-1,6 位,每 30 秒更新,时间容差 ±1 步 |
| 防重放 | 同一验证器时间步只接受一次,消费记录持久化,重启后仍有效 |
| 单来源失败限制 | 服务端看到的每个来源 IP,5 分钟窗口最多 5 次失败 |
| 全局失败限制 | 所有来源合计,1 分钟窗口最多 10 次失败 |
| 页面授权 | 默认无用户操作 15 分钟到期,最长 8 小时 |
| 绑定流程 | 5 分钟内有效 |
| 恢复码 | 10 个一次性恢复码;更新或重新绑定后旧码失效 |
登录、恢复和涉及动态码的管理操作共用失败额度。成功验证、格式错误和已使用码不计入失败次数;成功验证也不会抹去此前的失败记录。达到限制后,页面会显示等待倒计时。
出现「此动态码已使用」怎么办? 已验证的 DSH 页面可继续使用;若要进入另一个页面,或执行另一个需要动态码的操作,等待验证器刷新后再输入。无需为了结束绑定流程等待新码。
安全边界
支持 HTTP,不代表提供链路加密。 HTTP 下的二维码、动态码、页面凭据和 DSH 数据可能被链路上的攻击者窃取或篡改。TOTP 访问控制无法替代 HTTPS 或其他受保护的传输通道,也不提供防钓鱼能力。
此外,请按以下能力范围使用:
- 这是面向个人实例的 TOTP 访问验证,不是多用户账号、角色权限或“密码 + TOTP”双因素系统。
- 当前入口直接处理 HTTP,不提供 TLS 终止,也尚未适配 HTTPS 反向代理的 Origin 校验;远程访问应使用受保护的网络或隧道。
- 保护关闭时,能访问地址的人可进入 DSH,并使用该实例开放的文件、命令等能力。
- TOTP 种子在本地使用 AES-256-GCM 加密,主密钥单独保存;这无法防御已取得主机权限、能够同时读取数据库和主密钥的攻击者。
- 内部回环监听减少网络暴露,不隔离同一主机上的本地进程。插件不提供工作区隔离或执行沙箱。
- 目前适配 DSH 的 Fetch、XHR、
/api/WebSocket 和受限资源请求;第三方插件自建连接路径可能需要额外适配。 - 状态损坏、密钥缺失或数据库故障时拒绝访问。卸载插件会移除这层访问控制,不等同于在设置页关闭保护。
配置与本机管理
展开查看配置、数据目录和本机命令可选配置
可在对应 profile 的 cordis.patch.yml 中配置:
- id: dsh-totp
config:
host: '0.0.0.0'
port: 3080
allowedHosts:
- 'dsh.example.com:3080'
idleMs: 900000
maxMs: 28800000
修改监听地址、端口或数据目录需要重启;页面内的保护启停和锁定立即生效。
DSH 会通过插件导出的 Config 校验配置。host 支持 127.0.0.1 和 0.0.0.0;port 为 0–65535 的整数(0 表示动态端口);idleMs 不得大于 maxMs,两者为正整数且最长不超过 24 小时。allowedHosts 填写 host[:port],不带协议或路径。
数据目录
默认依次使用 DSH_TOTP_DATA_DIR、$DSH_HOME/dsh-totp、~/.dsh/dsh-totp。插件配置的 dataDir 可覆盖默认路径。
每个数据目录只允许一个运行中的插件实例。多个 DSH 实例应使用不同目录。普通重启保留绑定和保护状态,但不保留页面授权。
本机命令
在本仓库目录执行:
node src/cli.js status
node src/cli.js doctor
node src/cli.js enable
node src/cli.js disable
node src/cli.js lock
这些是持有本机管理权限的恢复与运维入口,不要求手机动态码。lock 命令会开启保护并锁定所有页面,因此与保护关闭时置灰的网页按钮不同;未绑定时不能开启保护。
可附加 --data-dir /path/to/data。本机命令必须与插件使用同一数据目录。它们不支持扫码绑定或换绑。
从源码检查与打包
npm ci
npm run verify
npm pack
check 检查 JavaScript 语法,并运行 TOTP、持久化、防重放、限流、恢复、页面授权、HTTP / WebSocket 网关和发布脚本测试。测试使用临时目录、回环端口和模拟上游,不读取你的 DSH 数据,也不向 npm 或 GitHub 发布。
verify 依次运行 check 和 check:package。后者解析 bundle YAML,校验版本、发布清单、运行入口与本地引用,并验证前端模块能按包名注册设置插槽。yaml 仅用于开发检查,不是运行依赖。
npm pack 直接生成安装包;npm publish 通过 prepublishOnly 自动运行 verify。发布内容包含运行源码、插件配置、README、封面及说明和许可证,不包含本地依赖或测试数据。
GitHub Actions 配置为在 Node.js 24 的 Linux、macOS 和 Windows 上运行这两项检查。自动测试使用模拟 DSH 上游,不能代替目标 DSH 环境的浏览器交互验证。
封面是概念插画,架构图以本仓库实现为准。DSH 升级后,请在目标环境复核绑定、登录、保护开关和锁定流程。
收录条目及评审证据见 收录准备说明,实现取舍见 同类插件源码对照。
No comments yet. Be the first to write one.