Bundle
dsh-totp
TOTP-only access control for DeepSeek Harness Web, with in-app enrollment, recovery codes, live protection controls and lock-all.
- Source
- SodaZheng
- License
- MIT
- Updated
- Updated yesterday
Readme
# dsh-totp
**为个人 DeepSeek Harness Web 实例提供 TOTP 访问验证。**
**简体中文** · [English](README.en.md)

在 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 包安装:
```sh
dsh plugin --profile web add dsh-totp
```
包已发布到 [公共 npm registry](https://www.npmjs.com/package/dsh-totp)。如果镜像尚未同步,可指定官方源:
```sh
dsh plugin --profile web add dsh-totp --registry=https://registry.npmjs.org
```
开发或检查当前源码时,在本仓库目录执行:
```sh
npm ci
dsh plugin --profile web add "$PWD"
```
插件按 profile 安装。上述命令添加到 `web`;如果使用其他 Web profile,请将 `web` 替换成相应名称。
本插件会替换该 profile 的 WebServer,并接管对外 HTTP 入口。请勿在同一 profile 叠加其他替换 WebServer 或接管登录流程的网关插件。插件直接分发 JavaScript,无需构建,也没有安装期脚本;普通打包不会运行测试或创建绑定数据。
### 2. 启动 DSH
如果 Web 实例已经运行,先停止它,再启动一次,让插件配置生效:
```sh
dsh --profile web --no-open
```
打开终端中 `dsh-totp HTTP entry` 打印的地址。
**首次安装时,访问保护默认关闭。** 你可以直接进入 DSH,再自行绑定验证器。升级、重装或普通重启会保留已有绑定和保护开关,不会把它们重置成默认值。
### 3. 扫码绑定
1. 打开 DSH 的 **设置 → 访问验证**,点击 **绑定验证器**。
2. 在 Google Authenticator 或 Microsoft Authenticator 中添加账号,扫描页面上的二维码。
3. 输入新验证器当前显示的 **6 位动态码**,点击 **确认绑定并开启保护**。
4. 保存页面给出的 **10 个一次性恢复码**,点击 **已保存,继续使用 DSH**。
绑定成功后,保护立即开启。**当前绑定页面继续可用,直接回到设置页,无需再次登录或等待动态码刷新。** 其他已打开页面的旧授权会失效。
仅生成二维码或取消绑定,不会开启保护。绑定和换绑均在网页中完成,不提供 CLI 绑定命令。
## 日常使用
| 操作 | 如何操作 | 结果 |
| --- | --- | --- |
| 进入 DSH | 保护开启时,打开或刷新页面并输入动态码 | 获得当前页面的临时访问授权 |
| 开启 / 关闭保护 | 在设置页点击开关,输入一组新的动态码确认 | 立即生效,旧页面授权撤销;关闭后可直接访问 |
| 锁定所有设备 | 点击对话页右下角的「锁定」,或设置页的「锁定所有设备」 | 当前页和其他已授权页面立即锁定,无需动态码 |
| 更新恢复码 | 在设置页点击「更新恢复码」,输入新的动态码 | 生成 10 个新恢复码,旧恢复码失效 |
| 更换验证器 | 验证当前动态码,再扫描新二维码并确认新动态码 | 新验证器生效;当前页面可继续使用,其他页面重新验证 |
**锁定按钮的状态:**
- 未绑定验证器:置灰,提示先到设置页绑定。
- 已绑定但保护关闭:置灰,提示先到设置页开启保护。
- 已绑定且保护开启:可直接锁定,无需再次验证。
锁定会关闭访问连接,插件不会主动取消 DSH 后台任务。具体任务在连接中断后的行为仍由 DSH 和所用插件决定。
### 手机不可用时
在登录页点击 **无法使用验证器**,输入一个未使用过的恢复码,然后重新绑定验证器。
恢复码验证成功后,旧验证器和旧页面授权立即失效。在新验证器确认前,恢复授权仅允许绑定,不能访问 DSH 数据。确认新动态码并保存新恢复码后,可以直接进入 DSH,无需再登录一次。
每个恢复码只能使用一次。更新恢复码或成功换绑后,请用新的恢复码替换旧备份。
## 工作原理
插件在 DSH 的 Web 服务前增加一个统一访问入口,在服务端判断是否允许访问。下图描述的是**访问保护开启时**的请求路径:
```mermaid
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 访问:
```sh
dsh --profile web --host 0.0.0.0 --port 3080 --no-open
```
使用域名、公网地址或端口映射时,额外声明浏览器访问的 `host:port`。例如:
```sh
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 和受限资源请求;第三方插件自建连接路径可能需要额外适配。
- 状态损坏、密钥缺失或数据库故障时拒绝访问。卸载插件会移除这层访问控制,不等同于在设置页关闭保护。
## 配置与本机管理
<details>
<summary>展开查看配置、数据目录和本机命令</summary>
### 可选配置
可在对应 profile 的 `cordis.patch.yml` 中配置:
```yaml
- 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 实例应使用不同目录。普通重启保留绑定和保护状态,但不保留页面授权。
### 本机命令
在本仓库目录执行:
```sh
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`。本机命令必须与插件使用同一数据目录。它们不支持扫码绑定或换绑。
</details>
## 从源码检查与打包
```sh
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 升级后,请在目标环境复核绑定、登录、保护开关和锁定流程。
收录条目及评审证据见 [收录准备说明](https://github.com/SodaZheng/dsh-totp/blob/main/docs/submission/README.md),实现取舍见 [同类插件源码对照](https://github.com/SodaZheng/dsh-totp/blob/main/docs/plugin-conventions.md)。
## 许可证
[MIT](LICENSE)
Install
dsh plugin --profile web add github:SodaZheng/dsh-totp
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-totp from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.