dsh-totp
sodazheng/dsh-totp
Control de acceso solo TOTP para instancias personales de DeepSeek Harness Web, con inscripción mediante QR en la aplicación, códigos de recuperación de un solo uso, controles de protección en vivo y revocación lock-all del acceso a la página.
Instalar
dsh plugin --profile web add github:sodazheng/dsh-totpREADME
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 升级后,请在目标环境复核绑定、登录、保护开关和锁定流程。
收录条目及评审证据见 收录准备说明,实现取舍见 同类插件源码对照。