DeepSeek Harness 插件

dsh-login-islibaod

Single-password authentication gateway plugin for the DSH Web GUI(英文原文)

跳到安装方式

来源信息

GitHub 仓库
islibaodong/dsh-login
最近更新
2026年8月21日
分类
界面增强
GitHub stars
1
载体类型
plugin
目录证据
上游声明已找到 dsh.bundle
证据路径
package.json#dsh.bundle
核对版本
0.1.0-rc.8
上游核对日期
2026-08-20

该证据由上游目录提供。本站没有安装、运行或安全审核这个插件。

安装

默认先复制一段 Prompt,让 Agent 读 GitHub 仓库和源码;需要自己装时再切到命令。

复制这段 Prompt,发给 DSH、Codex 或其他 Agent,让它先读 GitHub 仓库和源码。

请先不要安装或执行任何命令。阅读这个插件的 GitHub 仓库、README 和关键源码,然后用清楚、直接的方式回答以下问题,帮助我判断它是否适合我的需求:

1. 这个插件是什么,解决什么问题;
2. 适合哪些用户和典型使用场景;
3. 安装后如何使用,并给出一个最小使用示例;
4. 有哪些已知限制,以及隐私、安全、兼容性或维护风险;
5. 给出“推荐 / 有条件推荐 / 不推荐”的明确建议和理由。

请区分仓库明确说明、根据源码推断和未知信息。证据不足时请明确说明,不要猜测或照抄 README。

GitHub:https://github.com/islibaodong/dsh-login
插件名:dsh-login-islibaod
作者:islibaodong

检查来源文件

安装前先看这个插件目录里的 README 和其他文件。

文件资源管理器4 个文件
README.zh.md来源说明 · 只读预览
README 语言

dsh-login

English | 简体中文

DeepSeek Harness Web GUI 加上登录页、用户账号和会话隔离的插件:访问先登录,普通用户互相看不见对话,管理员在 GUI 内管理所有账号。

登录页用户管理(设置 → 用户管理)
![登录页](images/login.png)![用户管理](images/users.png)

这个插件解决什么问题

DSH 的 Web GUI 本身没有登录——它按“单用户、localhost”设计。只要把服务绑到 0.0.0.0(手机访问、局域网共享、团队共用),网络上任何人都能直接打开你的 GUI:看到全部对话、用你配置的模型密钥消耗额度,甚至修改宿主配置。

dsh-login 把它变成一个多用户部署:

  • 🔐 登录墙 —— 页面、静态资源、SPA 路由、API、WebSocket 全部要求有效会话,未登录一律跳转 /login
  • 👥 多账号 —— 首次访问创建管理员账号,其余用户由管理员在 GUI 里直接新建,无需命令行
  • 🙈 会话隔离 —— 普通用户只能看到、操作自己的对话(含其派生的子代理/分叉);其他人的会话、消息、工作区一律不可见;凭据、宿主设置等管理域整体禁用
  • 🛠 用户管理 —— 设置 → 用户管理:最后登录时间、在线会话数、重置密码、禁用、删除;禁用/删除/改密会立即吊销该用户的现有会话
  • 👑 管理员例外 —— 管理员不受隔离限制,可见全部会话,可配置宿主
  • 🚪 登出 —— 每个用户的设置面板里都有登出入口

快速开始

无需环境变量、无需改配置文件,三步:

# 1. 安装(web 就是启动 Web GUI 的 profile)
dsh plugin --profile web add github:islibaodong/dsh-login

2. 初始化:重启 dsh web 并打开 GUI,首次访问会出现「创建管理员账号」页面,选好用户名密码即完成 3. 添加用户:以管理员登录 → 设置 → 用户管理 → 新建用户

卸载:

dsh plugin --profile web remove @islibaodong/dsh-login

> 为什么 --profile web?DSH 插件按 profile 目录安装($DSH_HOME/profiles/<name>);web 就是启动 Web GUI 的 profile,用自定义 profile 的话换成对应名字即可。

常见问题

  • 重启 DSH 后要重新登录? 是——登录会话只存内存,进程重启即失效(正常运行下 Cookie 有效期默认 7 天)。
  • 普通用户能做什么? 正常使用对话:新建/打开/继续自己的会话、派生子代理、管理工作区里自己的内容。除此之外(他人会话、凭据、插件/预设/宿主设置、模型密钥管理)一律拒绝。
  • 从旧版(单密码)升级? 旧的单密码凭据不再能登录任何人;升级后首次访问会引导创建新的管理员账号(细节见下方「迁移说明」)。

---

技术细节

> 以下内容面向二次开发、安全审阅与排障;日常使用不需要阅读。

安装时发生了什么

dsh plugin add 读取本包声明的 cordis.patch.yml(bundle patch),自动完成:

  • 挂载 dsh-login 插件行(配置默认值即可用;distIndex 自动解析前端 dist 目录)
  • 禁用 web-runtime 行(dsh-web-app 通过它挂载 frontend-static fallback);dsh-login 接管 fallback 席位并重新提供 webRuntime 服务(/api 信任围栏的 LAN 信任 + DSH_WEB_URL 环境变量)
  • 禁用自带的 connection 行(/api 通道);dsh-login 挂载自己的身份感知接管插件,并提供配套的浏览器 bundle dist/client.js

手动安装(可选)

希望自己管理 patch 文件时,把以下内容加进 profile 的 cordis.patch.yml

- insert:
    - id: dsh-login
      name: '@islibaodong/dsh-login'
      config:
        password: DSH_LOGIN_PASSWORD   # 凭据引用名;派生用户存储引用(<名称>_USERS)
        distIndex: ''                  # 留空则自动解析前端 dist
        dataDir: ''                    # 留空则解析为 <DSH_HOME>/.dsh-login(所有权索引)
        sessionTtl: 604800             # 会话有效期,7 天(默认)
        enabled: true                 # 设为 false 可临时禁用

# 重要:dsh-login 接管 fallback 席位,必须禁用 web-runtime 行
# (dsh-web-app 通过该行挂载 frontend-static;dsh-login 会重新提供 webRuntime 服务)
- id: web-runtime
  disabled: true

# 重要:WebServer 拒绝重复的 /api 前缀注册,自带的 connection 行必须保持禁用;
# dsh-login 自己挂载身份感知的 /api 接管插件(含浏览器 bundle dist/client.js)
- id: connection
  disabled: true

> 注意:新增行必须写在 - insert: 下——顶层直接写行会被当成对已存在行的覆盖,对不存在的行是静默空操作;禁用行的键是 disabled(不是 disable)。

首次设置流程

1. 首次访问(无任何用户)-> /login 显示「创建管理员账号」页面(用户名 + 密码) 2. 用户选择凭据 -> POST /api/auth/setup 创建强制管理员的第一个账号(scrypt 哈希,存入 ${password}_USERS 凭据引用,默认 DSH_LOGIN_PASSWORD_USERS)并自动登录 3. 后续访问 -> /login 显示正常的用户名/密码登录表单 4. 用户管理 -> 管理员在 GUI「设置 → 用户管理」列出(最后登录/在线状态)、创建、禁用/启用、删除用户及重置密码(/api/auth/admin/* JSON 路由;删除最后一个管理员会被拒绝) 5. 安全保护 -> 已有用户后 /api/auth/setup 返回 403,防止劫持

> 迁移说明: 旧版单一密码凭据(默认引用 DSH_LOGIN_PASSWORD)不再能登录任何人。它保持已配置状态但认证不再使用——password 配置项现在只用于派生用户存储引用(${password}_USERS)。因此从单密码部署升级后,首次访问需要重新引导创建一个管理员账号。

工作原理

请求 -> WebServer
  ├─ /login (精确匹配)        -> 设置页(无用户时)或 登录页(有用户时)
  ├─ /api/auth/setup (精确)   -> POST: 首次创建管理员(已有用户则 403)
  ├─ /api/auth/login (精确)   -> POST: 验证 {username,password},设置 Cookie
  ├─ /api/auth/logout (精确)  -> POST: 撤销会话,清除 Cookie
  ├─ /logout (精确)           -> GET: 同样撤销,重定向到 /login
  ├─ /api/auth/me (精确)      -> GET: 当前会话身份
  ├─ /api/auth/admin/* (精确) -> 管理员 JSON API(users、password、disable、remove)
  ├─ /api/* (前缀匹配)        -> dsh-login 通道接管:
  │                             ├─ 主机不可信 -> 403
  │                             ├─ 无有效 Cookie -> 401
  │                             ├─ 事件路径上的 GET -> 426(需要升级)
  │                             └─ 按用户经代理过滤后分发
  ├─ /api/events.mux + /api/events.host(WS 升级)-> 同样的信任 + Cookie 检查,
  │                             之后按用户过滤事件下联
  └─ fallback (兜底)          -> dsh-login: 认证网关 + 静态文件服务
                                  ├─ 无有效 Cookie -> 302 重定向到 /login
                                  └─ 有有效 Cookie -> serveStatic() 提供文件
  • Cookie 名称dsh_session,HttpOnly、SameSite=Strict、Path=/
  • 会话令牌:32 字节随机值(256 位),内存存储,带 TTL 自动过期;会话携带用户名与管理员标记,进程重启后失效
  • 密码存储:scrypt 哈希(每用户独立盐),存于 DSH 凭据系统的 ${password}_USERS 引用

多用户权限模型

  • 普通用户只能使用会话功能。 通过 /api 接管,他们只能看到和操作自己的会话及其派生子会话(子代理/分叉——所有权沿 parentSessionId 传递),工作区视图也被过滤为仅含自己的会话。其余一律禁止:

- 物理层允许清单:固定的一组 session.*subagent.*workspace.*goal.* 方法,加上 skill.listhost.describellm.providers/llm.modelsrespond;其他任何线上方法在到达 harness 之前就是 403 - 管理员专属域:credentials.*settings.*agentPresets.* 整体禁用 - 同样禁止:llm.discoverModels 以及特权 host.* 目录对话框(pickDirectorylistDirectorycreateDirectoryopenPath) - 物理层 session.export 通道(目标在查询字符串中、不走信封)在通道层按所有权校验 - 事件流(mux/host WebSocket 帧)按所有权过滤,其他用户的流量不会到达浏览器

  • 管理员可见可做一切: 不受限的 API 访问、所有会话/工作区可见,以及「设置 → 用户管理」设置分区。
  • 登出: 设置面板的「用户管理/账户」分区为每个用户提供登出入口(POST /api/auth/logout/login);GET /logout 可作为普通链接使用。
  • 管理员用户管理(设置 → 用户管理): 通过浏览器 bundle 内置在 GUI 设置面板中,无独立页面。用户列表显示每个账号的最后登录时间(每次成功登录时落盘;功能上线后从未登录过的账号显示「从未登录」)、在线会话数与禁用标记;每行提供重置密码、禁用/启用、删除操作(单行右对齐不换行)。普通用户则得到「账户」分区(身份信息 + 登出入口)。面板样式全部走框架的 --dsw-alias-* 主题令牌,自动跟随应用皮肤(浅色/深色)。

数据位置

数据位置
用户账号(scrypt 哈希)DSH 凭据系统,引用 ${password}_USERS(默认 DSH_LOGIN_PASSWORD_USERS
会话→用户所有权索引<DSH_HOME>/.dsh-login/ownership.json(可用 dataDir 配置;DSH_HOME 环境变量或 ~/.dsh
登录会话仅内存(DSH 重启后需重新登录)

/api 通道接管与客户端 bundle

本插件替换自带的 /api connection 行:cordis.patch.yml 将其禁用(WebServer 拒绝重复的 /api 前缀注册,因此自带行必须保持关闭),dsh-login 以子插件形式挂载自己的身份感知通道(src/connection.ts)——同样的主机信任围栏,但每个请求都从会话 Cookie 解析身份并按用户分发。

浏览器端的协议不变,但 GUI 的线上客户端必须继续由本包提供:client-modules 扫描器会把被禁用行的浏览器半边从启动图中剔除。因此 dsh-login 声明了自身的 dsh.client 并随包发布 bundle dist/client.js——它是自带 connection 客户端的重新打标副本(src/connection.client.ts 原样转发导出),外加第二个模块注册:设置面板包装器(src/settings-panel.client.js),它原样应用线上客户端并注册「设置 → 用户管理/账户」设置分区(样式走框架 --dsw-alias-* 主题令牌;样式表按框架 bundle 预置的形状预打 data-plugin/data-plugin-css 标签)。dsh.client.inject 字段遵循生态惯例——填浏览器半边所需服务背后的包 id@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-locale),而非服务名;运行时纤维自身导出的 inject 才是权威依赖。React 与 UI 原语经平台模块表种子解析,任何 bundle 都可合法 require。重新生成:

npm run build:client   # node scripts/build-client.mjs;使用 node_modules 或 $DSH_HARNESS_CHECKOUT

升级 @deepseek-ai/dsh-client-connection 或修改 src/settings-panel.client.js 之后必须重新执行,否则浏览器 bundle 会与新通道脱节。

安全说明

网关保护范围

资产保护方式
页面导航 (/)未认证时 302 重定向到 /login
静态资源 (/assets/*.js.css 等)同样的网关检查
SPA 路由 (/conversations/settings 等)同样的网关检查

通道接管保护范围

资产保护方式
API 请求 (/api/*)isTrustedApiRequest 主机信任检查 加上 有效 dsh_session Cookie(缺失则 401);普通用户调用不允许的方法返回 403
WebSocket (/api/events.mux/api/events.host)升级时同样的主机信任 + Cookie 检查;帧按用户所有权过滤

公网暴露建议

1. 将 trustedHosts 设置为仅允许需要访问 API 的特定主机 2. 在 DSH 前部署反向代理(nginx/caddy)进行 TLS 终结 3. 网关 Cookie 为 SameSite=Strict,可防止针对登录/登出端点的 CSRF 攻击

架构说明:fallback vs prefix /

网关使用 registerFallback() 而非 register({ kind: 'prefix', path: '/' }),因为 DSH WebServer 的前缀匹配逻辑检查 pathname.startsWith(prefix + '/')。当 prefix 为 / 时,拼接结果为 //,而正常路径不会以 // 开头——所以 prefix / 路由只能精确匹配 / 这一个路径。fallback 处理器能捕获所有未被命名路由匹配的请求,这才是认证网关所需的 catch-all 行为。

WebServer 只有一个 fallback 席位。dsh-web-app 的 web-runtime 行会无条件挂载 frontend-static 占据它,因此使用 dsh-login 时必须禁用 web-runtime 行;dsh-login 会重新提供它负责的 webRuntime 服务(LAN 信任、DSH_WEB_URL),组合其余部分不受影响。

运行测试

# 标准全量测试(134 项;需要 DSH 源码做包解析——
# 设置 DSH_HARNESS_CHECKOUT,或在默认路径旁运行)
npx vitest run

.spec.ts 文件是标准的 vitest 测试定义,含多用户套件(usersownershipapi-filterconnectionadmin-apimultiuser-e2eclient-bundlesettings-panel)。tests/runner.mjstests/integration-runner.mjs 是针对原单密码核心的沙箱兼容运行器,未随多用户功能扩展。

项目结构

src/
├── index.ts          # Cordis 插件入口:注册路由、fallback、所有权 + 通道子插件
├── config.ts         # schemastery 配置 schema(password、distIndex、dataDir、sessionTtl 等)
├── users.ts          # UserStore:用户记录、scrypt 哈希、凭据系统持久化
├── session.ts        # SessionStore:内存会话(用户 + 管理员标记)+ TTL 过期
├── ownership.ts      # OwnershipIndex: sessionId → 用户名索引(去抖写 JSON 文件)
├── api-filter.ts     # 按用户的 ApiProxy 装饰器:允许清单、所有权守卫、帧过滤
├── connection.ts     # dsh-login-connection:/api 通道接管 + WS 下联(子插件)
├── connection.client.ts  # 浏览器半边:原样转发自带 connection 客户端
├── settings-panel.client.js  # 设置面板浏览器半边(纯 JS):用户管理/账户分区,主题令牌样式
├── admin-api.ts      # /api/auth/me + /api/auth/admin/* JSON 路由(设置面板后端)
├── auth.ts           # Cookie 管理 + 常量时间比较工具
├── gateway.ts        # 认证网关 handler(fallback + serveStatic)
├── login-api.ts      # POST /api/auth/login + logout + setup
├── login-page.ts     # 登录页与设置页 HTML
├── http-json.ts      # readBody/sendJson 工具 + resolveDshHome
└── web-runtime.ts    # webRuntime 接管:LAN 信任 + DSH_WEB_URL
dist/client.js        # 构建产物浏览器 bundle(npm run build:client)
scripts/build-client.mjs  # 重新生成 dist/client.js:自带通道 bundle + 设置面板
tests/
├── *.spec.ts         # vitest 测试定义
└── memory-credentials.ts   # 测试用内存凭据提供器

许可证

MIT