# dsh-auth-gateway

dsh-auth-gateway banner

npm version npm total downloads npm license

Language: 简体中文 | English

为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web 提供认证门禁的 Cordis 插件:**密码认证 + TOTP 双因素认证 + 多层防爆破 + 会话管理 + 登录审计**,并在网关层**真实拦截每一个请求**(HTTP 与 WebSocket),未认证流量无法触及后端。 `dsh web` 的官方认证只面向本机回环:dsh 0.1.2 起内部 webserver 启用内置浏览器认证(BrowserAuth),但它只是一枚绑定 authority 的 bearer cookie——设计说明写明「没有登出操作」(*"There is no logout operation"*),且「认证不配置 TLS、转发头解释或代理」(*"Authentication does not configure TLS, forwarding-header interpretation, or proxies"*)。dsh 0.2.1 虽为直连场景补上了原生 HTTPS(`--tls-cert` / `--tls-key`)与具体 IP 绑定(CLI 仅拒绝 `0.0.0.0` 等通配地址,接受具体地址字面量),但认证层本身依旧**没有登出、没有密码 / 多因素、没有防爆破、没有登录审计,也没有为「前面再套一层认证网关」预留任何集成通道**。本插件以进程内网关的形态补上这层缺失的远程访问认证:对外端口由网关独占,内部 webserver 由 bundle patch 绑定在本地回环,网关是唯一入口。 本项目支持 `0.1.5-rc.2 <= dsh <= 0.2.1-rc.2`,均已实测。 > **已知缺陷**:**远程访问(域名 / 反向代理 / 局域网 IP 等非回环 http 入口)下无法登录 DeepSeek 账号**——点左下角「登录 DeepSeek 账号」会立即报「登录失败」,**其余功能均正常**(会话、模型、工具、设置面板等)。根因在 dsh 上游:账号登录要求**浏览器 origin** 是带显式非零端口的回环 http 地址,而回环地址只在**浏览器所在机器**上解析——**本插件无法在服务端绕过它**(远程访问正是本插件提供的形态,所以该限制在这个部署形态下必然遇到)。判别与证据见 [docs/zh/TROUBLESHOOTING.md](docs/zh/TROUBLESHOOTING.md) 第 10 节。 > > **手动解决方式**(详细操作见 [docs/zh/DEPLOYMENT.md](docs/zh/DEPLOYMENT.md) 的「远程访问下登录 DeepSeek 账号」一节): > > 1. **本机回环访问**:浏览器与 dsh 同机时,直接打开 `http://127.0.0.1:<网关端口>`; > 2. **SSH 本地端口转发**:浏览器在异地时执行 `ssh -L 8080:127.0.0.1:<网关 listenPort> <部署机>`(冒号前是本机端口,任选空闲端口),再浏览 `http://127.0.0.1:8080`; > 3. **其它回环转发方式**:任何能把 `http://127.0.0.1:<显式端口>` 映射到远端网关的隧道 / 端口转发工具(VS Code、`kubectl port-forward`、`gost` 等); > 4. **改用 API Key 通道**:在账号弹窗里点「添加 API Key」,纯域名 / 反代部署下的既定替代方案。 ## 安装和卸载 ```bash # 安装(从 npm registry) dsh plugin --profile web add dsh-auth-gateway # 启动(对外端口 8080,内部 webserver 自动挪到 8081;--no-open 让 dsh 不自己开浏览器) dsh web --port 8080 --no-open # 卸载(先清凭据,再移除插件) ~/.dsh/profiles/web/node_modules/.bin/dsh-auth-gateway-uninstall dsh plugin --profile web remove dsh-auth-gateway ``` - 支持从 GitHub / 本地目录安装,见 [docs/zh/INSTALL.md](docs/zh/INSTALL.md); - 忘记密码用 `dsh-auth-gateway-reset` 重置(重启后控制台打印新初始密码); - 部署指南:[docs/zh/DEPLOYMENT.md](docs/zh/DEPLOYMENT.md) ## 功能特性 - **密码认证**:首次部署自动生成初始密码(控制台打印,一次性),登录后引导设置个人密码(scrypt 哈希存储),之后每次访问需登录; - **双因素认证(TOTP)**:可选启用,兼容 Google Authenticator、Authy、1Password 等主流认证器;含一次性备份代码(scrypt 哈希存储、单次使用),设备丢失时可恢复访问;**OTP 密钥以 AES-256-GCM 加密存储**(主密钥来自环境变量 `DSH_AUTH_GATEWAY_MASTER_KEY` 或自动生成的 `auth-gateway/otp-master.key`),磁盘泄露不再直接暴露第二因素根密钥; - **真实请求拦截**:未认证 `/api/*` 返回 401、页面类路径 302 到登录页、WebSocket 升级直接拒绝;**上游自有的一次性握手路径**(`publicPaths`,默认 `/oauth/callback`)是唯一的**匿名上游握手**例外(公开静态资产本就匿名放行)——dsh 账号登录的回调由平台以**跨站顶层导航**回跳,浏览器必然不带会话 Cookie,见 [SECURITY.md](docs/zh/SECURITY.md#匿名可达的上游握手路径);认证通过后请求透明转发(Host/Origin 规范化,兼容内部 trust fence); - **登录审计**:登录成功 / 失败 / 登出 / 改密与暴力破解告警(锁定/限流)均输出审计日志(`ctx.logger.info`/`warn`,含来源 IP 与失败原因,不记录任何凭据),并**持久化落盘** `$DSH_HOME/auth-gateway/log/audit.log`(JSONL,按天轮转、保留 90 天),形成完整可审计闭环; - **多层防爆破**:密码失败按来源锁定(默认 5 次/5 分钟)+ 全局速率限制(默认 60 次/分钟)+ OTP/备份码独立限流(默认 10 次/分钟),scrypt 在 libuv 线程池异步执行,登录洪峰不阻塞事件循环; - **会话管理**:内存 256-bit token(30 天),HttpOnly + SameSite=Strict Cookie,修改密码/禁用 OTP 吊销全部会话; - **合规形态**:host-only 插件(零构建、零运行时依赖)+ 可选 client 半(设置面板,源码构建),主体全部经 dsh 官方扩展点(`ctx.effect`、`webServer.tapIndex`、`ctx.slots`);唯有一项记录在案的安全例外——LAN trust(为域名/反代访问下模型设置页可用而对 connection 注册做最小介入,见 [TROUBLESHOOTING §1](docs/zh/TROUBLESHOOTING.md))。 ## 本插件不做的事情 以下需求在"单实例"前提下**无法真正实现**——它们的前提是进程/OS 强制的执行与存储隔离(独立 OS 账号、容器或沙盒),而本插件只是运行在 dsh 进程内的认证网关,提供不了这层隔离。列出它们是为了明确预期、避免误导: - **多账号登录 / 多租户**:dsh 是单用户工具——一个 Home、一份模型凭据,全部会话与数据(`sessions/`、`workspace/`、`.credentials.yaml`)都以运行 dsh 的 OS 账号权限存放在本地。网关叠加"账号体系"只能区分**谁在登录**(访问控制 + 审计),无法隔离**谁能看到什么**:任何通过认证的用户都能经 dsh 的工具执行读取同一 Home 下的全部会话与凭据。**没有 OS/容器/沙盒隔离就没有真正的多租户**——本插件不做,也无法做到。 - **角色权限限制(用户/管理员)**:同理,角色只能在网关自身的 HTTP 路由层生效(例如限制网关管理功能),挡不住 dsh 内部的能力面——普通用户一旦通过认证门,即拥有该实例的完整能力(工具执行、会话读写、配置与凭据访问)。需要"普通用户受限"的场景请用 OS 级隔离的多实例部署并自行管理账号。本插件的职责是:**认证门禁(谁能进入)+ 拦截与审计(谁做了什么),不承担、也无法承担授权与隔离模型**。 ## 工作原理 ```mermaid flowchart LR B[浏览器] --> G["dsh-auth-gateway 网关
对外端口 · 运行在 dsh 进程内"] G --> C{"认证检查
会话表 O(1)"} C -->|未认证| U["/api/* → 401
页面 → 302 /login
WS 升级 → 拒绝"] C -->|未通过 2FA| O["/otp/verify"] C -->|已认证| F["转发
Host/Origin 改写为回环"] F --> W["dsh webserver
127.0.0.1:内部端口"] ``` - 网关生命周期与 dsh 绑定:随 dsh 启动/退出,无独立进程; - bundle patch 将 webserver 移到回环端口(对外 = `--port`,内部 = 对外 + 1),远程无法绕过网关直连后端; - 网关在 DSH 的 `__ModuleLoader__` 加载 connection 模块时、Settings 等消费者启动前建立客户端 loopback trust——这是**唯一记录在案的安全例外**(仅拦截 connection 注册,其他插件原样通过);该兼容层不替代登录、HTTP/WebSocket 门禁或服务端 fence,详见 [TROUBLESHOOTING §1](docs/zh/TROUBLESHOOTING.md); - 认证状态机:`首次部署 → 初始密码登录 → 引导(设置个人密码)→ 登录 →(可选)OTP 验证 → 会话`;未完成引导或 2FA 的会话仅能访问对应验证端点。 ## 界面预览
引导页(设置个人密码)
引导页(初始密码登录后)
登录(含 2FA 验证码)
登录(含 2FA 验证码)
2FA 登录成功
2FA 登录成功
OTP 设置(QR 码)
OTP 设置(QR 码)
设置菜单(含认证设置入口)
设置菜单(含"认证设置"入口)
认证设置面板
认证设置面板
## 快速开始 1. 启动 `dsh web`:首次部署自动生成**初始密码**并打印在控制台(醒目提示块);请复制备用; 2. 打开 Web UI,用初始密码登录——将进入**引导页**:设置你自己的访问密码(至少 8 位,包含大小写字母或特殊字符;**强制**,设置完成前所有功能不可用);初始密码为一次性凭据,设置后自动失效; 3. 登录后可访问 `/otp/setup` 启用 TOTP(扫码或手动输入密钥,输入验证码确认;同时生成备份代码请妥善保存); 4. 已启用 OTP 后,登录需密码 + 验证码(或备份代码); 5. 修改密码:访问 `/login`(已登录时显示改密表单),或经"认证设置"面板。 ## 配置 以下字段为 bundle patch / profile patch 中 `dsh-auth-gateway` 行的 `config`(Standard Schema 校验): | 字段 | 默认 | 含义 | |---|---|---| | `listenHost` / `listenPort` | `0.0.0.0` / `3080` | 网关对外监听地址与端口 | | `upstreamHost` / `upstreamPort` | `127.0.0.1` / `3081` | 内部 webserver 地址与端口 | | `basePath` | `/` | 反向代理子路径前缀(如 `/dsh`);**默认 `/`(根路径)**。字符集限 `A-Za-z0-9._~/-`,拒绝 `..`、`//`、引号、空白、尖括号(该值会内嵌进页面脚本与链接,故按白名单校验;不合规配置会在加载时被拒绝)。子路径部署时在**部署方 profile patch** 中配置,不随插件分发 | | `cookieSecure` | `auto` | 会话 Cookie 是否携带 `Secure` 属性。`auto`:请求经 TLS(反向代理透传 `X-Forwarded-Proto: https`)时自动附加;`true`:强制附加;`false`:显式关闭。纯 HTTP 下强制开启会使浏览器拒绝 Cookie。**面板可直接修改**:覆盖值持久化于凭据记录,优先于部署配置,直至恢复 | | `publicPaths` | `['/oauth/callback']` | **匿名可达的上游握手路径**(精确匹配、仅 GET)。默认值就是 dsh 账号登录的回调:平台以跨站顶层导航回跳过来,浏览器不会带上网关会话 Cookie,落在门后该功能就永久不可用。只接受精确绝对路径(不接受通配符、查询串),字符集同 `basePath`(`A-Za-z0-9._~/-`);数组或逗号分隔字符串均可,重复项自动去重;保留路径(`/` 与其别名 `/index.html`、`/api*`、`/connect`、网关自有页面与已公开静态资产)会在加载期被拒绝。设为 `[]` 关闭放行。条目按 **basePath 剥离后**的路径匹配(与上游注册路径同坐标系),子路径部署**不要**带 basePath 前缀。取舍与边界见 [SECURITY.md](docs/zh/SECURITY.md#匿名可达的上游握手路径);该项只影响**回调那一段**——非回环 origin(域名 / 反代)下账号登录在**发起**阶段即被上游拒绝(`account: protocol`),与本项取值无关,见 [TROUBLESHOOTING §10](docs/zh/TROUBLESHOOTING.md) | | `minPasswordLength` | `8` | 密码最小长度(4–128) | | `requireMixedCase` / `requireSpecial` | `true` / `true` | 密码复杂度:大小写混合或特殊字符二选一满足 | | `maxLoginFailures` / `lockMinutes` | `5` / `5` | 密码失败锁定阈值与时长 | | `maxGlobalAuthAttemptsPerMinute` | `60` | 全局登录尝试速率上限 | | `maxOtpAttemptsPerMinute` | `10` | 单来源 OTP/备份码验证速率上限 | | `otpIssuer` / `otpPeriod` / `otpDigits` / `otpWindow` | `dsh-auth-gateway` / `30` / `6` / `1` | TOTP 参数(显示名、周期、位数、窗口) | | `backupCodeCount` / `backupCodeLength` | `10` / `8` | 备份代码数量与长度 | | `updateCheck` | `false` | 打开「认证设置 → 关于」时**自动**检查新版本。默认关闭:全新安装不发起任何对外请求。置 `true` 后在面板打开时自动查询一次公共 npm registry 的 `latest`(本插件唯一的对外请求,成功缓存 6h / 失败 15min,超时 3s,不含任何凭据)。**无论此项如何**,面板上的「检查更新」按钮都可手动发起一次检查 | ## 安全模型 认证状态变更(启用/禁用 OTP、修改密码)均要求完整验证:2FA 激活时禁用 OTP 需当前密码 + 验证码或备份代码;未完成 2FA 的会话不能访问敏感端点。OTP 验证防重放(记录已接受时间步)、防伪造(`x-forwarded-for` 不计入来源)。**OTP 密钥在落盘前以 AES-256-GCM 密封**,读取需主密钥——默认自动生成 `auth-gateway/otp-master.key`(0600),也可经环境变量 `DSH_AUTH_GATEWAY_MASTER_KEY`(hex/base64,32 字节)注入以隔离磁盘泄露。登录审计只记录事件种类、来源 IP 与失败原因,不落任何凭据。完整威胁模型、已知限制与恢复路径见 [docs/zh/SECURITY.md](docs/zh/SECURITY.md)。 ## 文档 | 文档 | 内容 | |---|---| | [docs/zh/INSTALL.md](docs/zh/INSTALL.md)([English](docs/en/INSTALL.md)) | 安装、更新、卸载、凭据重置的完整操作步骤 | | [docs/zh/NGINX-DEPLOYMENT.md](docs/zh/NGINX-DEPLOYMENT.md)([English](docs/en/NGINX-DEPLOYMENT.md)) | 配合 nginx 部署:裸金属直连 / 子域名 / 子路径 / Docker nginx 容器四种拓扑与配置示例 | | [docs/zh/SECURITY.md](docs/zh/SECURITY.md)([English](docs/en/SECURITY.md)) | 威胁模型、OTP 安全设计、已知限制与恢复路径 | | [docs/zh/DEPLOYMENT.md](docs/zh/DEPLOYMENT.md)([English](docs/en/DEPLOYMENT.md)) | 端口与监听、LAN 部署、HTTPS 建议、nginx 反向代理、故障排查 | | [docs/zh/TROUBLESHOOTING.md](docs/zh/TROUBLESHOOTING.md)([English](docs/en/TROUBLESHOOTING.md)) | 实机故障案例:域名下模型页不可用、原生依赖构建被拦、bundle 加载失败、版本线凭据格式、跨境超时优化 | | [docs/zh/TESTING.md](docs/zh/TESTING.md)([English](docs/en/TESTING.md)) | 单元测试、端到端(Playwright)、API/WebSocket 门禁验证 | | [docs/DEVELOPMENT.md](docs/zh/DEVELOPMENT.md)([English](docs/en/DEVELOPMENT.md)) | 架构说明、构建、开发统计 | ## 致谢 - **@adra2n** — 实现 OTP 双因素认证([PR #1](https://github.com/xbzbing/dsh-auth-gateway/pull/1)),并添加 OTP 密钥 AES-256-GCM 静态加密存储与解密路径错误分类([PR #6](https://github.com/xbzbing/dsh-auth-gateway/pull/6)); - **@meowtech** — 报告并初步实现了 dsh 新版本(rc8+ 配置平面收归 loopback)下 LAN 浏览器设置不可用问题的修复([PR #7](https://github.com/xbzbing/dsh-auth-gateway/pull/7));该实现(loader 包装 + provide 劫持)随后被证实会破坏共存插件,本仓库已改用最小介入方案重写。 - **@LuckVd** — 修复 dsh ≥ 0.1.2 上游浏览器认证(BrowserAuth)导致的转发 401([PR #12](https://github.com/xbzbing/dsh-auth-gateway/pull/12)):经官方 `credentials` 通道读取密钥并为回环一跳铸造同构 cookie,经评审补齐官方通道读取、轮换即时重铸、部署清单与双语文档后合入。 - **@pjj1234** — 实现 dsh 账号登录「OAuth 回调路径匿名放行」([PR #29](https://github.com/xbzbing/dsh-auth-gateway/pull/29)):新增 `publicPaths` 配置(默认放行 `GET /oauth/callback`),平台跨站回跳的回调不再被认证门挡下,修掉「账号登录停在『等待登录』、之后平台一直返回『授权失败』(`ALREADY_CONSUMED`)」(见 [docs/zh/TROUBLESHOOTING.md](docs/zh/TROUBLESHOOTING.md) 第 9 节);合入时由本仓库补全 basePath 匹配语义与「不铸上游 bearer」的测试和文档。 ## 验证概览 - 单元与契约测试:`npm test`(覆盖 basePath 路由/重定向/转发、PWA 元数据放行、登录审计、审计日志轮转/清理、OTP 安全回归、client 契约、patch 端口推导) - 部署流水线:`npm run deploy`(语法检查 → 全量测试 → 同步到 DSH 安装目录 → 安装后验证) - 实机端到端:`node scripts/e2e.mjs`(Playwright,登录/2FA/改密全流程) - 门禁验证:`./scripts/verify.sh`(curl,401/302/WS 拒绝/锁定) 详见 [docs/zh/TESTING.md](docs/zh/TESTING.md)。 ## Model Experience None,本包是浏览器与内部 dsh webserver 之间的认证载体,不会进入任何模型请求。 #### KV Cache effect None;本包既不组装也不发送 provider 请求。 ## License MIT