lloydzhou/dsh-feishu-oauth:给 DSH 接上飞书 OAuth 登录

前言

DSH(DeepSeek Harness)默认对匿名访问开放:打开首页就能进入会话,同一个进程的 owner 权限没有按人切分。把它挂到内网或团队环境里时,问题就出现了——谁登录过、谁还在用,只能去看进程日志;泄露出去的访问地址,任何能打开页面的人都能用。社区里已有的做法通常是再写一个独立的登录中间件,或者在前面挂一层反代做鉴权,结果要么和 DSH 的原生会话脱节,要么要多维护一个组件。

下面介绍 lloydzhou/dsh-feishu-oauth。它由 lloydzhou 维护,分类 admin-security,把飞书 OAuth 接进 DSH 的原生认证流程:登录成功后直接复用 DSH 自己的会话 Cookie,插件既不修改核心,也不自行签发 Cookie。也就是说,登出、会话有效期、token 恢复仍由 DSH 原生机制负责,插件只是替换了入口那一步。

需要注意的前提:这个插件不是多用户权限系统。所有能用飞书应用登录的人,拿到的都是同一个 DSH owner 的完整权限,没有独立账户、用户或租户白名单、逐用户审计或撤销机制。README 明确写了「仅供可信人员共享使用」,人员范围需要靠飞书应用的可用范围来限制。

一、它解决什么问题,以及它的边界

插件做的事情只有一件事:把 DSH 的入口从「匿名直接进」换成「飞书授权后再进」。

具体能力如下:

  • 匿名访问 / 显示一张「DSH × 飞书登录」卡片,提供授权登录按钮,以及原生 token URL 的登录提示。卡片支持中英文、窄屏和系统深色模式,不加载任何外部字体、图片或脚本。
  • 登录成功后仍停留在 / 显示 DSH 页面,地址栏不会跳到别处。成功回调用零延迟的 meta refresh 返回同源 /,不需要用户再点一次确认,同时结束跨站重定向链,以兼容原生 SameSite=Strict Cookie。
  • /?token=... 作为恢复入口,始终交给原生认证处理。
  • API 和 WebSocket 的鉴权不受影响,继续走原生认证。
  • 登录页样式通过固定 CSP 哈希授权,不依赖运行时引入的外部资源。

边界也要说清楚:

  • 飞书撤权、用户退出或关闭插件,都不会让已经签发的 DSH Cookie 失效,其有效期由原生会话管理。README 建议保留原生 token 恢复方式,并明确不要把这个插件当成多用户权限系统。
  • OAuth 关闭或应用凭据缺失时,插件不注册登录路由,行为回退到原生入口。
  • 保存配置成功,不等于飞书凭据或回调登记有效;README 没有承诺保存后一定可用。

二、安装与启用

环境要求:Node.js >=24.9.0,以及提供 webServer.readResource()、connection.browserPeer 和原生认证接口的 DSH。清单声明的 DSH 版本下限是 >=0.2.1-rc.3,但 README 同时说明不保证较旧版本具备这些 API,作者用提取的 0.2.2-rc.1 原生模块做过兼容测试,这不等于所有版本或真实飞书端到端验证通过。

安装命令来自 README 原文,需要在插件源码目录内执行:

git clone https://github.com/lloydzhou/dsh-feishu-oauth.git
cd dsh-feishu-oauth
npm ci
npm run build:client
dsh plugin --profile web add .

逐步说明:

  1. npm ci 按锁文件安装依赖,esbuild 固定版本并由锁文件锁定。
  2. npm run build:client 调用 scripts/build-client.mjs,把 src/client/index.js 打包成浏览器产物 dist/client.js。npm 包只包含服务端源码、浏览器产物、语言 JSON、DSH patch 和说明文档,不包含客户端源码、构建脚本和测试,所以发布前需要先构建。
  3. dsh plugin --profile web add . 装载 plugin bundle,并顺带应用清单里的 cordis.patch.yml。

装完之后还要做三件事:

  1. 在 Plugins 页面启用本插件 bundle。注意配置页面里的「启用飞书登录」默认关闭,它与 bundle 的总开关是两回事。
  2. 使用 DSH 启动时打印的原生 token URL 登录,然后打开 Plugins → 已安装插件 → 本插件的配置页面。
  3. 在飞书开放平台应用里限定可信人员的可用范围,并登记卡片上显示的精确回调地址:浏览器当前 origin 加 /auth/feishu/callback。

package.json 里的几个关键声明:

{
  "name": "dsh-feishu-oauth",
  "version": "0.2.2-rc.2",
  "description": "Feishu OAuth login for DeepSeek Harness using native session cookies",
  "license": "MIT",
  "engines": { "node": ">=24.9.0", "dsh": ">=0.2.1-rc.3" }
}

三、配置项与凭据处理

当前版本提供的配置字段只有三个:

字段 默认值 说明
enabled false 启用 OAuth 路由
appId 空 飞书 App ID
appSecret 空 原始 App Secret;读取时脱敏,表单留空表示保留现值

用法上的几个细节,值得在动手前知道:

  • 输入的是 App ID 和 App Secret 的原始值,不是环境变量名。
  • 首次配置必须填写 Secret;之后密码框留空就表示保留现有值。
  • 密码框始终以空白载入,保存成功后清空,界面不会告诉你 Secret 到底有没有配好。
  • 保存成功或出现配置冲突之后,需要重新载入表单再编辑。

安全方面有两个明确的提醒,README 用了加粗强调:

  • 脱敏和密码遮罩不等于加密。App Secret 以明文存储在本机 profile 配置里,需要保护配置文件及其备份。
  • 允许以 HTTP 访问 DSH,但浏览器提交设置时的 App Secret、原生登录凭据和会话流量均没有传输加密。飞书 provider 请求始终使用 HTTPS,这不保护浏览器到 DSH 的 HTTP 链路。README 建议使用原生 HTTPS,并说明不支持「HTTPS 在反向代理终止、DSH 仍监听 HTTP」的部署。访问地址还必须满足原生 Connection 信任规则,Host 保持不变,并与飞书登记的回调地址一致。

四、登录与恢复流程

走一遍完整路径:

  1. 匿名打开 /,看到登录卡片,点授权登录按钮。
  2. 跳转飞书授权端点,授权后回到 /auth/feishu/callback。
  3. 成功回调用零延迟 meta refresh 回同源 /,此时原生 Cookie 已写入,用户看到 DSH 界面,地址栏仍是 /。
  4. 后续请求由原生认证接管,插件不再介入。

失败或恢复路径:

  • 访问 /index.html 在匿名状态下可能拿到原生 401,应该回到 / 登录。
  • 带 ?token=... 的访问始终交给原生认证,这是恢复入口。
  • OAuth 关闭或凭据缺失时不注册登录路由,登录页不会出现。

相关配置变更时(开启、关闭、改凭据)插件会撤下 OAuth 路由、取消正在等待的登录请求,清理之后重新挂载,不影响原生入口。关闭或卸载插件也回到原生入口。

设置集成在捕获到激活或清理异常时会记录一条固定脱敏警告;README 同时提醒,并非所有缺失或错误配置都会产生警告,不要仅靠有没有警告来判断配置状态。

五、保护措施与已知限制

插件在内存里做了几道限制,但要正确解读它们:

  • state 单次使用、绑定浏览器,有效期 300 秒,最多保留 256 个待处理 state;最多同时进行 16 个 provider 交换流程。
  • 这些是单进程容量限制,不是按 IP 限流,也不是 DoS 防护。换成多进程部署时,这套限制不在全局生效。
  • state 只在进程内存中保存,进程退出或配置更新会使待登录失效;同一浏览器发起新登录会覆盖旧的临时绑定。
  • 绕过 Connection 的自定义 listener 不在保护范围内。

协议和传输上的措施:

  • 使用 S256 PKCE,provider 地址为固定 HTTPS,禁止跟随 provider 重定向,设置了请求超时和响应大小限制。
  • 临时 Cookie 是 HttpOnly/SameSite=Lax,HTTPS 下带 Secure。
  • 原生启动 token 不进入页面、不进入浏览器重定向、不发往外部 HTTP 请求、不写日志;插件也不读取签名密钥。
  • 错误只包含固定阶段 TOKEN、USERINFO 或 NATIVE_COOKIE,以及已收到的 HTTP 状态码和飞书数字错误码,不回显 provider 返回正文或凭据。

两个常见故障码的含义:

  • TOKEN/HTTP_400/CODE_20002 表示飞书拒绝应用凭据。
  • 只有 TOKEN 阶段出现、没有 HTTP 状态,表示未收到 token 接口响应头。

README 建议的排查方式:处理完问题后从首页重新登录,不要刷新已经消耗掉授权码的回调页。

六、飞书协议实现的落点

插件采用授权码 + PKCE,并保留 S256 challenge 与 verifier,不自动降级,也不重试已使用过的授权码。

  • 授权端点:https://accounts.feishu.cn/open-apis/authen/v1/authorize
  • 换取 token:https://open.feishu.cn/open-apis/authen/v2/oauth/token,按飞书文档中的 PKCE 配对说明,通过 JSON 请求体换取。
  • 飞书错误码 20049 表示 PKCE 校验失败。

有一点要留意:飞书 v2 token 接口被官方标为历史版本,文档提供了 v3 迁移说明。README 的说明是迁移 v3 之前需要验证授权端点的 PKCE 兼容性,升级飞书侧能力前要先确认新端点是否仍支持 PKCE。

七、开发与验证

插件以 npm 包形式发布,想本地改一遍再验证,可以按下面的顺序跑:

npm ci
npm run build:client
npm run check
npm test
npm pack --dry-run --ignore-scripts
  • npm run check 用 node --check 逐个检查服务端 ESM 源码、dist/client.js、客户端源码和构建脚本。
  • npm test 执行 node --test test/*.test.js,跑单元测试,以及可选的原生模块兼容测试。
  • npm pack --dry-run --ignore-scripts 用来核对 npm 包里实际包含哪些文件。

目录结构:

src/server/   插件入口、OAuth、登录页面及设置生命周期
src/client/   配置卡片与表单逻辑源码
dist/client.js 浏览器 bundle(生成并提交)
scripts/      客户端构建脚本
test/         单元测试及可选的原生模块兼容测试
locale/       对外导出的中英文 JSON

package.json 的 files 字段列出了发布内容:src/server、dist/client.js、locale、cordis.patch.yml、README、LICENSE、SECURITY.md、SETTINGS-INTEGRATION.md、ASSET-ATTRIBUTION.md。公开导出保持为 .、./client、./locale/*.json 和 ./package.json。CI 会在重建浏览器产物后检查差异。

客户端侧还有两点要留意:客户端依赖 @deepseek-ai/dsh-client-locale、@deepseek-ai/dsh-client-ui-settings、@deepseek-ai/dsh-client-ui-plugin-manager 三个包,由 manifest 的 client.inject 声明注入;插件对 DSH 的改动写在 cordis.patch.yml 里,升级 DSH 后需要检查 patch 是否仍然适用。README 明确说模拟测试和提取模块测试不等于真实浏览器或飞书授权往返验收,升级 DSH 后应该重新验证。

安全漏洞按 SECURITY.md 私密报告,不要在公开 issue 中提交凭据。

八、适合谁,以及动手前要检查什么

适合这类部署:DSH 跑在内网或团队环境,使用者是一批已知且可信的成员,都在同一个飞书组织里,你希望他们用已有的飞书账号登录,而不是每个人单独发账号密码。此时用飞书应用的可用范围做人员筛选,是这套方案的前提,也是唯一的权限边界。

不太适合这类场景:

  • 需要逐用户权限、审计或撤销能力。这套方案给所有登录者同一个 owner 权限,撤权只能通过飞书侧终止人员访问,已签发的 DSH Cookie 不会自动失效。
  • 需要跨多个租户或组织隔离成员。插件没有租户概念。
  • 对传输加密有合规要求,但只能提供浏览器到 DSH 的 HTTP 链路。README 建议使用原生 HTTPS,不支持反向代理终止 HTTPS、DSH 仍监听 HTTP 的部署。

动手前建议做这三件事:

  1. 读一遍源码,尤其是 src/server/oauth.js、src/server/settings.js 和 cordis.patch.yml,确认插件改了什么、有没有超出「登录入口替换」的范围。许可证是 MIT,许可证文本见仓库 LICENSE 文件。
  2. 确认 DSH 版本满足 >=0.2.1-rc.3,并实测所需的原生 API 存在。清单声明的下限不保证旧版本具备这些能力。
  3. 确认回调地址与飞书应用登记一致,并且网络出口能让浏览器访问到这个精确地址。

结语

dsh-feishu-oauth 的价值不在于做了一套复杂的账号系统,而是用最小的改动把 DSH 的登录入口接到飞书 OAuth 上,会话状态仍然交给原生机制管理,需要自己维护的东西少。它的代价也很明确:不提供按用户区分的权限、审计和撤销。理解这一点,才能判断它放进自己的部署里是否合适。

相关链接:

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜