feat: 客户端 API Key 可上传 Agent APK,并可单独授权设置当前版本 #384

Open
opened 2026-10-12 18:03:24 +08:00 by ila · 2 comments
Owner

基本信息

  • 类型:功能(服务端 + Web 小改:客户端 API Key 可上传 Agent APK 版本、可单独授权设置当前版本)。涉及权限与安全,高风险。
  • 状态:已建单,开始实施。用户:「授权你建单,再按惯例交给 Sonnet 实施,最后部署到上线.」(含合并与发布;设为当前 0.9.70 不在本次授权内)。Gitea MCP 不可用,回退 REST。
  • 来源:2026-10-12 用户:「把最新版本尝试通过apikey和接口发布到线上」。Claude Code 核对发现客户端 API Key 网关不含 Agent 版本接口,给出 A(Admin 页面手动上传)/B(扩展 API Key 权限,新建工单)/C(直接写服务器,不建议);用户选「B」。
  • 关联:#237(客户端密钥与可编辑模块授权)、Agent APK 版本管理(Business-Rules「采购规则与 Agent 版本管理」)。

已核验事实(origin/main 3495de9,2026-10-12)

  • Agent 版本接口只在 Admin:GET/POST /api/admin/v1/agent-app-releases、PUT /api/admin/v1/agent-app-releases/current、GET /:releaseId/download(server/app/goauto/apprelease/router.go),要求 JWT 且 RequireRoleKey("admin");权限矩阵中均为非采购员(access/purchaser.go:24–27),归属模块 devices(access/modules.go:142)。
  • Upload(apprelease/handler.go:54):multipart file + releaseNotes,MaxBytesReader(MaxAPKBytes+1MB),仅 .apk,创建人取 user.GetUserId(c)(JWT);服务端解析 Manifest 的 versionCode/versionName,保存 SHA-256、大小,校验 versionCode 唯一。
  • SetCurrent(:82):JSON {requestId, releaseId},幂等。设为当前后 Agent 启动/手动检查即提示更新(安装仍由 Android 系统人工确认,设备有活动任务时禁止)。
  • 客户端 API 网关(clientapi/routes.go Inventory):路径前缀 /api/client/v1,每个端点声明模块与能力(read/write/delete/动作名);devices 模块当前只有 GET /devices(read)。网关请求体上限 16MB(clientapi/gateway.go:83),每次请求写 client_api_key_audit;key.Allows(module, capability) 按密钥授权判断;操作人取密钥的 AuthorizedBy。
  • Web 客户端密钥页动作标签为全局表(web/src/views/goauto/client-keys/,actionLabels 已有 activate: '设置当前规则')。

目标

经授权的客户端 API Key 可以:①查看与上传 Agent APK 版本;②(单独授权)把某个已上传版本设为当前。现有密钥不自动获得这些能力。

非目标

  • 不开放 APK 下载给客户端 API(避免外部拉取安装包)。
  • 不改 Agent 端更新逻辑、不改 Admin 原有上传/设为当前流程与权限、不新增角色或菜单。
  • 不自动设为当前;不对采购机做任何操作。

方案

  1. 能力定义(devices 模块下两个新动作,需在密钥上显式勾选):
    • release_upload「上传 Agent 版本」:GET /api/client/v1/agent-app-releases(列表)、POST /api/client/v1/agent-app-releases(上传)。
    • release_activate「设置当前 Agent 版本」:PUT /api/client/v1/agent-app-releases/current。
    • 不使用已有的 activate(其标签是「设置当前规则」,会混淆),也不挂在 read/write 上,避免已有 devices 读写密钥被动获得权限。
  2. 处理器复用:clientapi/routes.go 注册上述端点复用 apprelease.Handler 的 List/Upload/SetCurrent;Upload 的创建人改为统一取操作人:JWT 用户或客户端密钥 AuthorizedBy(与采购 operatorID 同口径),不改变 Admin 行为。
  3. 体积:网关 16MB 上限需不小于 MaxAPKBytes+1MB;若 MaxAPKBytes 大于 15MB,则仅对上传端点放宽到 APK 上限(不全局放宽)。
  4. 审计:沿用网关审计;上传结果中的 releaseId、versionCode、SHA-256 可在 Admin 版本列表查看。不记录 APK 内容。
  5. Web:客户端密钥页 actionLabels 新增 release_upload: '上传 Agent 版本'、release_activate: '设置当前 Agent 版本';设为当前的勾选旁加风险提示「设为当前后,所有设备会提示更新」。
  6. 契约文档:Admin/客户端 API 文档补充两个动作与三个端点。

设计证据

现有客户端密钥授权页沿用模块/动作勾选控件,只新增两个动作标签与一行风险提示;无新页面、无布局变化,不需要原型。

子项目影响、依赖与并行

  • Server:clientapi/routes.go(及 Catalog 自动生成动作)、apprelease/handler.go(创建人取值)、可能 clientapi/gateway.go(上传端点体积)及测试。
  • Web:views/goauto/client-keys/ 标签与提示。
  • 无数据库迁移(动作以字符串保存在密钥授权中,需实施时核实;如需迁移则停下重新确认)。Android 无。
  • 无依赖,可并行。

验收标准

  • 单元测试:未授权密钥调用三个端点均被拒(含仅有 devices read/write 的密钥);仅授 release_upload 可列表/上传、不可设为当前;授 release_activate 可设为当前;上传创建人为密钥授权人;Admin 原流程不变。
  • 单元测试:上传体积边界(≤ APK 上限通过、超限拒绝,其他客户端端点仍为 16MB);非 .apk 拒绝;重复 versionCode 拒绝。
  • Web:两个动作在密钥授权页可见、可勾选,标签与风险提示正确;构建通过。
  • 发布(Server + Web)需用户授权;发布后由用户在 Admin 为指定密钥勾选 release_upload,用该密钥上传 0.9.70(83)验证;设为当前另行确认。

风险与回退

  • 安全:新增的是写入能力,密钥泄露者可上传 APK;设为当前会让所有设备提示更新。因此两个动作分开授权,默认不授予,审计留痕;APK 仍需设备端人工确认安装,且签名不一致无法覆盖安装。
  • 回退:撤销密钥动作授权或回滚代码;无数据变化。

文档影响

  • 更新 Wiki Architecture-and-Code-Map(客户端 API 新端点)与 Business-Rules-and-Glossary(客户端密钥可授权的 Agent 版本动作、分开授权与风险);如客户端 API 契约有专页一并更新。
## 基本信息 - 类型:功能(服务端 + Web 小改:客户端 API Key 可上传 Agent APK 版本、可单独授权设置当前版本)。**涉及权限与安全,高风险**。 - 状态:**已建单,开始实施**。用户:「授权你建单,再按惯例交给 Sonnet 实施,最后部署到上线.」(含合并与发布;设为当前 0.9.70 不在本次授权内)。Gitea MCP 不可用,回退 REST。 - 来源:2026-10-12 用户:「把最新版本尝试通过apikey和接口发布到线上」。Claude Code 核对发现客户端 API Key 网关不含 Agent 版本接口,给出 A(Admin 页面手动上传)/B(扩展 API Key 权限,新建工单)/C(直接写服务器,不建议);用户选「B」。 - 关联:#237(客户端密钥与可编辑模块授权)、Agent APK 版本管理(Business-Rules「采购规则与 Agent 版本管理」)。 ## 已核验事实(`origin/main` `3495de9`,2026-10-12) - Agent 版本接口只在 Admin:`GET/POST /api/admin/v1/agent-app-releases`、`PUT /api/admin/v1/agent-app-releases/current`、`GET /:releaseId/download`(`server/app/goauto/apprelease/router.go`),要求 JWT 且 `RequireRoleKey("admin")`;权限矩阵中均为非采购员(`access/purchaser.go:24–27`),归属模块 `devices`(`access/modules.go:142`)。 - `Upload`(`apprelease/handler.go:54`):multipart `file` + `releaseNotes`,`MaxBytesReader(MaxAPKBytes+1MB)`,仅 `.apk`,创建人取 `user.GetUserId(c)`(JWT);服务端解析 Manifest 的 versionCode/versionName,保存 SHA-256、大小,校验 versionCode 唯一。 - `SetCurrent`(`:82`):JSON `{requestId, releaseId}`,幂等。设为当前后 Agent 启动/手动检查即提示更新(安装仍由 Android 系统人工确认,设备有活动任务时禁止)。 - 客户端 API 网关(`clientapi/routes.go` `Inventory`):路径前缀 `/api/client/v1`,每个端点声明模块与能力(`read`/`write`/`delete`/动作名);`devices` 模块当前只有 `GET /devices`(read)。网关请求体上限 16MB(`clientapi/gateway.go:83`),每次请求写 `client_api_key_audit`;`key.Allows(module, capability)` 按密钥授权判断;操作人取密钥的 `AuthorizedBy`。 - Web 客户端密钥页动作标签为全局表(`web/src/views/goauto/client-keys/`,`actionLabels` 已有 `activate: '设置当前规则'`)。 ## 目标 经授权的客户端 API Key 可以:①查看与上传 Agent APK 版本;②(单独授权)把某个已上传版本设为当前。现有密钥不自动获得这些能力。 ## 非目标 - 不开放 APK 下载给客户端 API(避免外部拉取安装包)。 - 不改 Agent 端更新逻辑、不改 Admin 原有上传/设为当前流程与权限、不新增角色或菜单。 - 不自动设为当前;不对采购机做任何操作。 ## 方案 1. **能力定义**(`devices` 模块下两个新动作,需在密钥上显式勾选): - `release_upload`「上传 Agent 版本」:`GET /api/client/v1/agent-app-releases`(列表)、`POST /api/client/v1/agent-app-releases`(上传)。 - `release_activate`「设置当前 Agent 版本」:`PUT /api/client/v1/agent-app-releases/current`。 - 不使用已有的 `activate`(其标签是「设置当前规则」,会混淆),也不挂在 `read`/`write` 上,避免已有 devices 读写密钥被动获得权限。 2. **处理器复用**:`clientapi/routes.go` 注册上述端点复用 `apprelease.Handler` 的 `List`/`Upload`/`SetCurrent`;`Upload` 的创建人改为统一取操作人:JWT 用户或客户端密钥 `AuthorizedBy`(与采购 `operatorID` 同口径),不改变 Admin 行为。 3. **体积**:网关 16MB 上限需不小于 `MaxAPKBytes+1MB`;若 `MaxAPKBytes` 大于 15MB,则仅对上传端点放宽到 APK 上限(不全局放宽)。 4. **审计**:沿用网关审计;上传结果中的 releaseId、versionCode、SHA-256 可在 Admin 版本列表查看。不记录 APK 内容。 5. **Web**:客户端密钥页 `actionLabels` 新增 `release_upload: '上传 Agent 版本'`、`release_activate: '设置当前 Agent 版本'`;设为当前的勾选旁加风险提示「设为当前后,所有设备会提示更新」。 6. **契约文档**:Admin/客户端 API 文档补充两个动作与三个端点。 ## 设计证据 现有客户端密钥授权页沿用模块/动作勾选控件,只新增两个动作标签与一行风险提示;无新页面、无布局变化,不需要原型。 ## 子项目影响、依赖与并行 - Server:`clientapi/routes.go`(及 Catalog 自动生成动作)、`apprelease/handler.go`(创建人取值)、可能 `clientapi/gateway.go`(上传端点体积)及测试。 - Web:`views/goauto/client-keys/` 标签与提示。 - 无数据库迁移(动作以字符串保存在密钥授权中,需实施时核实;如需迁移则停下重新确认)。Android 无。 - 无依赖,可并行。 ## 验收标准 - [ ] 单元测试:未授权密钥调用三个端点均被拒(含仅有 devices read/write 的密钥);仅授 `release_upload` 可列表/上传、不可设为当前;授 `release_activate` 可设为当前;上传创建人为密钥授权人;Admin 原流程不变。 - [ ] 单元测试:上传体积边界(≤ APK 上限通过、超限拒绝,其他客户端端点仍为 16MB);非 .apk 拒绝;重复 versionCode 拒绝。 - [ ] Web:两个动作在密钥授权页可见、可勾选,标签与风险提示正确;构建通过。 - [ ] 发布(Server + Web)需用户授权;发布后由用户在 Admin 为指定密钥勾选 `release_upload`,用该密钥上传 0.9.70(83)验证;**设为当前另行确认**。 ## 风险与回退 - 安全:新增的是写入能力,密钥泄露者可上传 APK;设为当前会让所有设备提示更新。因此两个动作分开授权,默认不授予,审计留痕;APK 仍需设备端人工确认安装,且签名不一致无法覆盖安装。 - 回退:撤销密钥动作授权或回滚代码;无数据变化。 ## 文档影响 - 更新 Wiki Architecture-and-Code-Map(客户端 API 新端点)与 Business-Rules-and-Glossary(客户端密钥可授权的 Agent 版本动作、分开授权与风险);如客户端 API 契约有专页一并更新。
Author
Owner

开始实施(2026-10-12,Claude Code)

用户:「授权你建单,再按惯例交给 Sonnet 实施,最后部署到上线.」

  • worktree D:/OPC/goauto-worktrees/issue-384,分支 feat/384-client-key-agent-release,基线 origin/main 3495de9。
  • Sonnet 子 Agent 实施,Claude Code 审查复跑后合并、发布;发布沿用用户要求:线上有采集/采购执行时等待空闲再重启。
  • 设为当前 0.9.70、给密钥勾选新动作、装采购机不在本次授权内。Gitea MCP 不可用,回退 REST。
## 开始实施(2026-10-12,Claude Code) 用户:「授权你建单,再按惯例交给 Sonnet 实施,最后部署到上线.」 - worktree `D:/OPC/goauto-worktrees/issue-384`,分支 `feat/384-client-key-agent-release`,基线 `origin/main` `3495de9`。 - Sonnet 子 Agent 实施,Claude Code 审查复跑后合并、发布;发布沿用用户要求:线上有采集/采购执行时等待空闲再重启。 - 设为当前 0.9.70、给密钥勾选新动作、装采购机不在本次授权内。Gitea MCP 不可用,回退 REST。
Author
Owner

实现、合并与发布(2026-10-12,Claude Code)

  • 提交:19484f0(服务端:devices 模块新增 release_upload/release_activate,三个客户端端点复用 apprelease;上传创建人取密钥 AuthorizedBy;仅上传端点体积放宽到 MaxAPKBytes+1MB)、6107c23(Web 标签与风险提示)。无迁移(授权存 GrantsJSON,动作来自内存目录)。
  • 复核:clientapi/apprelease/clientkey/access 测试通过,jest client-keys 9/9,build 通过。合并 main 07800cf(--no-ff),已推送。
  • 发布:/home/goauto/releases/20261012-07800cf-384,server SHA256 58f4a0a4…2131(进程一致),index 67c5fcee…098a;config 一致。发布时有 1 个采购执行中、32 个排队,服务器脚本等待「执行中=0 且 10 秒后复查=0」后于 18:34:38 切换并重启,active。
  • 验收:首页/登录新 index,10 项 JS/CSS 200,验证码 200,未登录 Admin 接口 401;/api/client/v1/agent-app-releases GET/PUT 无密钥 401;下载路由 404(未开放)。日志无 panic/fatal/1146/1054。Nginx vhost client_max_body_size 100m,APK 超过 100MB 会被 Nginx 拒绝。
  • 回滚:current 切回 /home/goauto/releases/20261012-15bab9c-381-382-383 并重启。
  • 待办:Wiki(Architecture/Business-Rules/Deployment)未更新;用户在 Admin 给密钥勾选 release_upload 并告知密钥所在环境变量后上传 0.9.70;设为当前另行确认。
## 实现、合并与发布(2026-10-12,Claude Code) - 提交:`19484f0`(服务端:devices 模块新增 `release_upload`/`release_activate`,三个客户端端点复用 apprelease;上传创建人取密钥 AuthorizedBy;仅上传端点体积放宽到 MaxAPKBytes+1MB)、`6107c23`(Web 标签与风险提示)。无迁移(授权存 GrantsJSON,动作来自内存目录)。 - 复核:clientapi/apprelease/clientkey/access 测试通过,jest client-keys 9/9,build 通过。合并 main `07800cf`(--no-ff),已推送。 - 发布:`/home/goauto/releases/20261012-07800cf-384`,server SHA256 `58f4a0a4…2131`(进程一致),index `67c5fcee…098a`;config 一致。发布时有 1 个采购执行中、32 个排队,服务器脚本等待「执行中=0 且 10 秒后复查=0」后于 18:34:38 切换并重启,active。 - 验收:首页/登录新 index,10 项 JS/CSS 200,验证码 200,未登录 Admin 接口 401;`/api/client/v1/agent-app-releases` GET/PUT 无密钥 401;下载路由 404(未开放)。日志无 panic/fatal/1146/1054。Nginx vhost `client_max_body_size 100m`,APK 超过 100MB 会被 Nginx 拒绝。 - 回滚:current 切回 `/home/goauto/releases/20261012-15bab9c-381-382-383` 并重启。 - 待办:Wiki(Architecture/Business-Rules/Deployment)未更新;用户在 Admin 给密钥勾选 release_upload 并告知密钥所在环境变量后上传 0.9.70;设为当前另行确认。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#384