Development & Runtime
dsh-auth-proxy
wxyzh/dsh-auth-proxy · ★ 0
A token-auth reverse-proxy plugin for the DSH Web GUI: puts a static-token login wall in front of dsh web. The host side listens on 127.0.0.1: (default 8443), shows a built-in login page for unauthenticated visits, issues an HttpOnly session cookie after token validation, then forwards HTTP and WebSocket traffic to the loopback dsh webserver. Zero DSH source changes
README body is shown in Chinese.
Manifest details
安装
dsh plugin --profile web add github:wxyzh/dsh-auth-proxyGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
DSH Web GUI 的令牌鉴权反向代理插件:在 dsh web 前面加一道静态令牌登录墙。宿主侧在
127.0.0.1:<port>(默认 8443)监听,未认证访问显示内置登录页,校验令牌后签发 HttpOnly
会话 cookie,再把 HTTP 与 WebSocket 流量转发给回环 dsh webserver。零 DSH 源码改动:
webserver 保持监听 127.0.0.1:3080,本插件持有对外 socket。
browser ──► auth proxy :8443 (127.0.0.1) ──► dsh webserver 127.0.0.1:3080
├─ 未认证 → 内置登录页
├─ POST 令牌 → HttpOnly 会话 cookie
└─ 已认证 → 转发 HTTP + WebSocket
功能
- 内置登录页(POST
/__dsh_auth/login校验令牌),登出走 POST/__dsh_auth/logout。 - 令牌比对使用 SHA-256 +
timingSafeEqual,避免时序侧信道。 - 会话 cookie:
HttpOnly; SameSite=Lax; Path=/,永久有效(Max-Age 10 年)且无状态: 值为payload.signature(HMAC-SHA256,密钥由令牌派生),服务端不存会话,重启后 cookie 依然有效; 更换令牌会使所有已登录会话立即失效(全体下线),登出仅清除客户端 cookie。 - IP 白名单(支持 CIDR,IPv4)可整体绕过令牌;失败登录锁定(按 IP,阈值与时长可配)。
- 配置实时可改:设置页注册成一个独立的
settings.section分区(与 Models/General 同级, 非插件列表内的折叠卡片),经ctx.settingsScope读写dsh-auth-proxy命名空间——收 revision 栅栏、 由 Host 的 settingsvalidate校验并持久化,非自建 HTTP 写接口;对纯 HTTP 局域网地址自动注入crypto.randomUUIDpolyfill,保证前端 RPC 可用。 - 局域网地址可正常编辑 web-ui 设置(主题、语言、插件配置等):dsh web 客户端按页面源判定回环,
非回环来源会把设置面降级为只读;代理转发 HTML 时注入 loopback-compat 脚本,把
connection.isLoopback打开——与代理把 Host/Origin 改回回环后服务端按回环放行的事实一致, 鉴权仍由本插件令牌墙把关。 - 状态可视:宿主终端直接打印
dsh-auth-proxy: listening on http://<host>:<port>(console.log 镜像, dsh web 不把插件ctx.logger打到终端),设置卡片同样显示实际监听地址;通过accessUrls声明的 入口地址(可含 HTTPS 域名)会展示在卡片与登录页,多域名场景一眼可知该用哪个 URL。 - 品牌层(proxy 前置重写):
brand.enabled开启后,转发页改标题(含document.title拦截)、 换 favicon(内联 SVG 或本机 SVG 文件)、改写 PWA manifest(name/short_name/icons)、可选注入 Copilot 侧边栏/英雄区视觉(brand.logo,包裹官方 brand 图像 —— 无需新增 client 包)。所有重写都 发生在代理 forward 路径,内网直连 127.0.0.1:3080 完全不受影响;关闭开关则零干预。 - web-all remote-web-ui 兼容(
/remote前缀还原):web-all 0.3.13 起dsh-remote-web-ui的 client 端嵌在web-all/client.js聚合包内无法摘除,在非回环来源页面(即经本代理访问)会把/api/*、/sidebar/*、/git/*、/pet/*改写为/remote/api/*;该插件 host 半通常保持禁用(配对 vs 令牌验证),/remote镜像路由不存在,改写后的请求上游即 405(设置/模型选择/workspace 会话全挂)。代理在转发 HTTP 与 WebSocket upgrade 时剥离/remote门控前缀、还原原始路径,让 RPC/流式接口可达;非/remote门控的请求原样透传。 - 移动端设置面板适配(≤768px 两步式交互):web-all 设置对话框在窄屏仍用固定 188px 左导航列,内容区被挤到 ~132px。代理注入纯表现层 CSS+JS:打开设置时先展示完整左侧导航(188px 带文字标签、内容隐藏),点选分区后导航自动收成 44px 图标 rail、内容区展开占满;左下角浮动
≡按钮随时展开/收起导航。选择器按结构匹配([role="dialog"][data-dsh-surface] > nav、[class*="nav*"])而非 css-module hash 类名,web-all 升级 rehash 不破坏。 - 移动端设置对话框 transform 包含块中和:web-all 移动端响应式层给侧边栏抽屉设
transform: translateX(0)(展开动画用),即便恒等矩阵也创建包含块——嵌套其内的position:fixed设置对话框退化为抽屉定位(inset:0只盖 320px 而非全视口)。设置对话框打开时经:has()中和该 transform([data-pane="sidebar"]:has([role="dialog"][data-dsh-surface]) { transform: none }),overlay 恢复全视口、面板达到calc(100vw - 48px)(内容区 276→298px)。该选择器仅在设置对话框存在时命中,会话列表抽屉展开动画不受影响。 - 无 TLS,禁绑通配/公网:监听地址仅允许回环与内网(默认
127.0.0.1);0.0.0.0、::与公网 IP 在保存与启动时都会被拒绝。外部访问请在前面挂 TLS 反向代理,回指本监听地址。
安装与卸载
dsh plugin 把参数转发给 pnpm,在 profile 目录安装依赖,并按 dsh.bundle 声明自动把插件
挂进 web profile roster(即 cordis.patch.yml 的 auth-proxy 行),无需额外激活步骤。
安装(npm 发布版与 GitHub 直装二选一):
# npm 发布版
dsh plugin --profile web add @wxyzh/dsh-auth-proxy
# 或 GitHub 直装(固定 tag 版本)
dsh plugin --profile web add github:wxyzh/dsh-auth-proxy#v0.1.0
本地开发用 link 方式(无需发布,直接挂源码目录):
dsh plugin --profile web add link:<abs 路径>/dsh-auth-proxy
卸载(无论以哪种方式安装,都用包名):
dsh plugin --profile web remove @wxyzh/dsh-auth-proxy
说明:dsh plugin 需要 pnpm 在 PATH 上;GitHub 直装时若 pnpm 拦截 prepare 构建脚本,
按 CLI 提示在 profile 的 pnpm-workspace.yaml 添加 allowBuilds 键后重试。本插件是宿主 +
浏览器双半插件:宿主侧 src/index.ts,浏览器侧 src/client/(设置卡片)。
配置
组合入口配置(cordis.patch.yml):
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
host |
127.0.0.1 |
监听地址(仅回环与内网;无 TLS,禁止 0.0.0.0/::/公网 IP) |
port |
8443 |
对外监听端口 |
targetHost |
127.0.0.1 |
回环转发目标 |
targetPort |
3080 |
回环转发端口 |
token |
见下 | 共享访问令牌 |
brandTitle |
'Harness'(已弃用,映射到 brand.title) |
转发页签标题品牌(旧字段;新配置见下 brand.*) |
brand.enabled |
false |
品牌层总开关:开启后才做标题/favicon/PWA/侧边栏视觉的 proxy-前置重写;内网直连 3080 不受影响 |
brand.title |
'' |
转发页签标题 + PWA name/short_name:替换硬编码的 DeepSeek Harness(静态 <title> 也整体换成该值;空串不改) |
brand.wordmark |
'Copilot' |
侧边栏标记旁的文字 |
brand.logo |
false |
注入 Copilot 侧边栏/英雄区视觉(包裹官方 brand 图像开口,无需新增 client 包) |
brand.icon.inline |
'' |
品牌图标内联 SVG(favicon + PWA icons);空=默认星光 |
brand.icon.file |
'' |
品牌图标本机 SVG 文件路径(serve 时读取;内联优先) |
banner |
'' |
登录页横幅文案 |
allowedIps |
[] |
IP 白名单(如 ["127.0.0.1", "10.0.0.0/8"]),空 = 一律要令牌 |
accessUrls |
[] |
对外访问地址(可含 HTTPS 域名,多域名逗号分隔),仅用于展示 |
maxFailures |
0 |
失败锁定阈值(0 = 关闭锁定) |
lockoutMinutes |
15 |
锁定时长(分钟) |
令牌:推荐用环境变量引用,不要把字面量写进配置:
token: !!js process.env.DSH_AUTH_TOKEN
安全默认:令牌为空或仍是占位符 change-me 时,代理保持禁用、绝不监听——只有配了真实令牌才会开放端口。
配置分层(改动前先读 AGENTS.md):dsh-settings scope 是唯一写入路径——Host 用
installSettingsSection 注册 dsh-auth-proxy 命名空间(base = 组合入口,用户文档层由部署的
settings provider(如 dsh-settings-file)持久化,无 settings 服务时退化为组合入口);设置分区经
ctx.settingsScope 读写,revision 栅栏 + Host validate 把关,监听 host 策略在 validate 钩子拒绝。
不存在自建的卡片配置文件(旧的 ~/.dsh/dsh-auth-proxy.json 权威已废弃)。令牌占位符是合法存储值,
代理保持禁用。运行态只读探测走 GET /api/dsh-auth-proxy/status(listening / tokenSet / accessUrls),
永不回传令牌值,只回 tokenSet 布尔。
安全说明
- 代理即网络边缘:不信任
X-Forwarded-For(客户端可伪造,信任即白名单/锁定绕过), 转发前剥离x-forwarded-for/x-real-ip。 - 监听地址策略:无 TLS 意味着绑到通配(
0.0.0.0/::)或公网 IP 会把明文令牌暴露到网络, 因此默认127.0.0.1,且仅接受回环与内网地址(127/8、10/8、172.16/12、192.168/16、169.254/16、::1);保存时由 settingsvalidate钩子拒绝、启动时由sync()拒绝,双重把关,日志与卡片会给出原因。外部访问请用 TLS 反向代理 (nginx/Caddy 等)终止加密后回指127.0.0.1:8443,accessUrls里填对外域名。 - 无 TLS:即使仅内网监听,令牌仍经明文 HTTP 传输,同网段可嗅探。这是为局域网 HTTP 可用性做的取舍; 需要加密传输时请在前方套 TLS 终结。
- 会话为无状态签名 cookie(HMAC 密钥派生自令牌,重启不失效);无单点剔除能力——更换令牌即全体下线,
登出仅清客户端 cookie;失败计数由定时清理兜底(每 30 分钟扫描,闲置超 1 小时的记录清除)。锁定为按 IP,IPv6 字面量(如
::1)不匹配 IPv4 白名单。
开发
- 构建:
npm run build(tsc出宿主侧lib/types/+tsdown出浏览器侧lib/client.js)。 - 本机坑(junction 开发副本):
npm run build/pnpm run build会触发prepare里的 pnpm install,reify 跟随node_modules/@deepseek-aijunction 可能破坏全局 dsh 内核。只改宿主侧(src/index.ts)时用tsc -p <临时 host-only tsconfig>(include 仅src/index.ts、excludesrc/client)输出到lib/types/,不要跑npm run build;详见AGENTS.md。 - 类型检查:
npm run typecheck。 - 冒烟测试:
npm run smoke(驱动构建产物,覆盖空/占位令牌不监听、登录流程、XFF 伪造不绕过白名单、 热更新不重建、改端口重建、占位令牌禁用、拒绝公网监听地址、HTML 双脚本注入、无状态会话重启存活与换令牌全体下线)。 - 仓库规则与安全不变式见
AGENTS.md;许可见LICENSE(MIT)。
已知限制
- 会话永久有效且无状态(签名 cookie,重启不失效);无单点剔除,改令牌即全体下线、登出仅清 cookie,属预期;失败计数表由定时清理兜底。
- 移动端适配耦合 web-all 结构:设置面板折叠/宽度优化依赖 web-all 当前的 DOM/CSS 结构(已尽量用结构选择器抗 css-module rehash,但 web-all 语义级重构仍可能失效);注入的选择器未来若不再匹配,需按新版结构复查。
/remote前缀剥离是通用的前缀还原:转发层对所有以/remote开头的路径做剥离(当前仅 web-all remote-web-ui client 会生成此类路径);若未来有其他插件主动使用/remote/*路径,会被一并还原,需要留意。- 当前目录已是 git 仓库(初始提交已建)。