feat: harden install and resumable OAuth login
CI / test (macos-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s

This commit is contained in:
nac
2026-08-28 07:01:20 +00:00
parent 6b5b3a413a
commit 9f3d3ad5fc
19 changed files with 1721 additions and 472 deletions
+3 -3
View File
@@ -11,8 +11,8 @@ MCP client → TypeScript tool boundary → CRM domain service → NocoBaseClien
- `src/index.ts` 是单一可执行入口;`src/cli.ts` 管理 setup、doctor、客户端配置和 stdio 生命周期。
- `src/nocobase-endpoints.ts` 集中固定 API、OAuth issuer、发现地址和 resource identifier。
- `src/oauth.ts` 实现 OAuth 发现、动态公共客户端注册、设备授权、浏览器启动、轮询和刷新;不读取浏览器 Cookie,也不监听本机端口。
- `src/auth-service.ts` 把设备授权包装成 MCP 可调用的开始/状态会话;先返回安全的网页链接,再在后台验证和保存授权结果。
- `src/credentials.ts` 管理用户级私有凭证文件与进程内缓存。默认保存 OAuth 访问/刷新令牌,兼容旧版个人 Key;任何凭据都不进入 argv、环境变量、CRM 配置或 MCP 配置。
- `src/auth-service.ts` 把设备授权包装成 MCP 可调用的开始/状态会话;先返回安全的网页链接,再跨 stdio 进程恢复、验证和保存授权结果。
- `src/authorization-store.ts` 管理短期待授权文件、跨进程独占锁、崩溃恢复和锁所有权;`src/credentials.ts` 管理用户级私有凭证及进程内缓存;两者共用 `src/private-json.ts` 的私有目录检查和原子 JSON 写入。默认保存 OAuth 访问/刷新令牌,兼容旧版个人 Key;任何凭据都不进入 argv、环境变量、CRM 配置或 MCP 配置。
- `src/nocobase-client.ts` 是唯一远程适配器,直接实现 NocoBase resource action 协议、data-modeling 读取、项目附件关系和 ACL 检查。
- `src/crm-service.ts` 负责 CRM 查询、解析、分页、聚合和可读输出;`src/crm-helpers.ts` 与 `src/filters.ts` 保存纯函数。
- `src/project-service.ts` 只允许固定项目字段的 create/update;`src/tools.ts` 是稳定 MCP 契约和错误/原始数据策略边界。
@@ -24,7 +24,7 @@ MCP client → TypeScript tool boundary → CRM domain service → NocoBaseClien
- 生产运行时只连接固定 HTTPS API `https://justconn.top/api`,不接受参数、配置或凭证文件覆盖。
- OAuth issuer、协议端点和浏览器授权 URL 必须属于固定的 `https://justconn.top` 来源。
- Device Authorization 请求 `api offline_access`;密码和浏览器会话不会离开 NocoBase。
- MCP 可以在无配置、无凭据状态下准备默认配置并启动;只有业务请求需要身份。`nocobase_login` 返回网页链接和确认码,后台完成的 Token 不经过 Agent。
- MCP 可以在无配置、无凭据状态下准备默认配置并启动;只有业务请求需要身份。`nocobase_login` 返回应交给发起用户当前私聊的 OAuth Device Flow 网页链接、确认码和机器可读交付边界;链接不是 Bearer 凭据,无界面服务器不需要远程桌面。内部设备码只进入短期 `0600` 文件;`nocobase_login_status` 可在新进程中继续兑换,完成的 Token 不经过 Agent。
- Authorization 只允许在同源重定向中保留,跨源重定向在发送凭证前失败。
- Token 会从 HTTP 错误中替换为 `<redacted>`。
- stdio 是唯一默认传输,不创建后台服务或监听端口。
+7 -5
View File
@@ -10,17 +10,17 @@
项目不保存共享业务账号,也不需要多人共用的远程 MCP 服务。个人 Authorize Key 仅作为显式备用认证方式。
## 最新 Commit 交付
## 最新 Commit 安装与更新
默认安装不等待 Release。Agent 在每次安装开始时读取 Gitea `main` 的最新完整 Commit SHA,然后把这次任务固定在该 SHA:
默认安装和更新都不等待 Release。用户可以每次发送同一个 INSTALL 链接;Agent 先检查现有 MCP 条目、安装入口和 `install-receipt.json`,再读取 Gitea `main` 的最新完整 Commit SHA,并把这次任务固定在该 SHA。需要首次安装、更新或修复时:
- 从带 SHA 的不可变 URL 重新读取 `INSTALL.md`;
- 下载同一 SHA 的源码归档;
- 在临时目录执行锁定依赖安装、检查、测试、bundle 和 npm pack;
- 将本地生成的运行 tarball 安装到用户级隔离前缀;
- 首次安装到用户级隔离前缀,更新则沿用原前缀并保留配置、凭据和读写模式;
- 报告完整 Commit SHA 和本地 tarball SHA-256。
解析 SHA 后,即使 `main` 在安装期间更新,也不混入新文件;下一次安装才采用更新后的提交。任何检查或构建失败都停止,不回退旧 Release。正式 Release 可以继续用于归档和人工回滚,但不是默认安装来源。
若有效安装记录已经是同一最新 SHA,并且二进制、帮助和 MCP 入口均完整,Agent 直接执行配置核对与 `crm_health`,不重复下载、构建或要求重新登录。解析 SHA 后,即使 `main` 在任务期间更新,也不混入新文件;下一次安装或更新才采用更新后的提交。任何检查或构建失败都停止,不回退旧 Release。正式 Release 可以继续用于归档和人工回滚,但不是默认安装来源。
Gitea 仓库访问受限时,用户或 Agent 必须具有源码和 Commit 归档读取权限。受限仓库链接本身不能成为任意用户可访问的一键安装链接,也不得通过聊天传递访问令牌。
@@ -48,7 +48,9 @@ Gitea 仓库访问受限时,用户或 Agent 必须具有源码和 Commit 归
## 更新与回滚
更新时,Agent 重新解析 `main` 的最新 Commit,在新临时目录完成全部检查和构建,再对同一隔离前缀运行 `npm install --prefix ... --omit=dev <locally-built-tarball>`。安装前备份 MCP 客户端配置和非秘密 CRM 配置,并记录更新前后的完整 Commit SHA。
更新时,Agent 重新解析 `main` 的最新 Commit;SHA 变化或现有入口不完整时,在新临时目录完成全部检查和构建,再对同一隔离前缀运行 `npm install --prefix ... --omit=dev <locally-built-tarball>`。更新前备份 MCP 客户端配置和非秘密 CRM 配置,并记录更新前后的完整 Commit SHA;沿用现有 `--enable-writes` 状态,除非用户明确要求改变。
MCP 重载后先调用 `crm_health`。已有 OAuth 登录仍有效时直接完成更新;只有明确返回认证失败时才启动浏览器授权。成功后原子写入不含秘密的 `install-receipt.json`,供下次准确判断是否已经是同一 Commit。
回滚是唯一允许选择旧提交的安装操作:使用上次记录的完整 Commit SHA 重建并安装该快照,不通过移动分支名猜测版本。凭证文件与 CRM 配置位于包目录之外,更新和回滚不应复制、覆盖或删除个人 OAuth 凭据。
+4 -3
View File
@@ -16,7 +16,7 @@ nocobase-crm-mcp client-config [--config <path>] [--enable-writes] [--json]
- `doctor`:验证凭据和 CRM 映射是否可用。
- `client-config`:准备默认 CRM 配置并输出可合并到 MCP 客户端的配置片段;不要求登录,也不包含任何 Token 或 Key。
- `--enable-writes`:显式增加受控项目写入工具;默认不启用。
- `--no-browser`:不自动启动浏览器,只显示 NocoBase 授权链接和确认码。
- `--no-browser`:不自动启动浏览器,只输出可交给发起用户的 NocoBase 授权链接和确认码;搭配 `--json` 时还会在 stderr 输出 `authorization_required` 结构化事件。
- `--manual-key`:仅在 OAuth 不可用时,通过安全 TTY 隐藏输入个人 Authorize Key。
## 默认工具
@@ -33,7 +33,7 @@ nocobase-crm-mcp client-config [--config <path>] [--enable-writes] [--json]
查询结果默认返回经过整理的紧凑字段,而不是完整 NocoBase 原始记录。
`nocobase_login` 会启动用户确认的 OAuth 授权,并在成功后写入本机私有凭据文件,因此不会被标记为只读;它不会修改任何 CRM 业务数据。该工具只返回可交给用户的 `login_url`、`user_code`、有效期和状态,不返回 Token 或设备码。
`nocobase_login` 会启动用户确认的 OAuth 授权,并在成功后写入本机私有凭据文件,因此不会被标记为只读;它不会修改任何 CRM 业务数据。该工具返回 `login_url`、标准别名 `verification_uri_complete`、`user_code`、有效期、状态和机器可读交付边界。链接与确认码应原样发送给发起用户的当前一对一私聊,不返回 Token 或内部设备码,也不需要服务器远程桌面;不得发送到群聊、公开频道或第三方。
## 可选工具
@@ -69,13 +69,14 @@ OAuth Token 和 Authorize Key 都不支持也不允许通过环境变量传入
## OAuth 登录
推荐在 MCP 注册后调用 `nocobase_login`,把返回的链接和确认码发给用户,再用 `nocobase_login_status` 查询结果。桌面环境会尝试自动打开浏览器;远程或无界面环境使用相同链接,不需要回调端口。已有凭据被 NocoBase 拒绝时,使用 `nocobase_login({"force": true})` 重新授权。CLI 的 `setup --no-browser` 保留为客户端无法在登录前加载连接器时的备用入口。
推荐在 MCP 注册后调用 `nocobase_login`,把返回的链接和确认码发给发起用户的当前私聊,再用 `nocobase_login_status` 查询结果。它们属于 OAuth Device Flow 的短期用户验证信息,不是密码、Key、Bearer Token 或内部 `device_code`。待授权状态以短期私有文件保存,因此桌面、远程或无界面环境都不需要回调端口或服务器远程桌面,stdio 进程重启后也能恢复同一个授权。已有凭据被 NocoBase 拒绝时,使用 `nocobase_login({"force": true})` 重新授权。CLI 的 `setup --no-browser` 保留为客户端无法在登录前加载连接器时的持续等待备用入口;运行期间不得关闭该 CLI 进程。
- 发现地址:`https://justconn.top/api/.well-known/openid-configuration`
- 授权模式:OAuth 2.1 Device Authorization
- 客户端类型:动态注册的 native public client,不保存 client secret
- 请求权限:`api offline_access`
- 浏览器只访问 `https://justconn.top`;程序不读取 Cookie 或浏览器存储
- 待授权设备码只保存于同一用户的短期私有文件,成功、取消或过期后立即删除
- 访问令牌临近过期时自动刷新;并发请求只触发一次刷新
- OAuth 端点、浏览器 URL 和 issuer 都必须匹配固定的 `justconn.top` 来源