[feature] 外部只读 token 可启停 + 接入配置包一键复制 (A-20260902-021 v1.3) #43

Closed
opened 2026-09-10 18:32:27 +08:00 by tanzk · 2 comments
Owner

背景

用户 2026-09-10 反馈外部只读 token(workbuddy 日报/简报拉取)使用体验有三处痛点:

  1. 明文只显示一次:生成后关闭弹窗就再也拿不到,用户认为「似乎没必要」——但这是 A-20260902-021 LOCKED 的安全底线(库中仅存 SHA-256 hash,明文不可逆),架构不变,改为「不再依赖回看明文」的使用方式。
  2. 吊销后不能再用:现有 revoke 是终态软删(enabled=false),误操作只能重新建 token 并重新分发。缺「重新启用」。
  3. 配置分散:调用方(WorkBuddy)需要 token + 接口地址 + 提示词 + 规范说明四样东西,现在只能人工四处拼凑。

影响范围

  • 后端:backend/app/api/v1/external.py(token 管理端点)、backend/app/services/external_service.py
  • 前端:web/src/views/system/ExternalAccess.vue、web/src/api/externalAccess.js
  • 不涉及表结构变更(复用既有 enabled 字段),无 Alembic migration
  • 不影响既有外部调用(纯增量端点 + 前端 UI)

根因判断

非缺陷,是功能缺口:token 生命周期只设计了「生成 → 吊销」两个终态,缺少「暂停/恢复」中间态;且未提供面向调用方的「开箱配置」物料。

处理动作 / 设计要求

  • 新增 POST /api/v1/external/tokens/{id}/enable:把 enabled 置回 true(幂等),清空 revoked_by 语义化留痕(保留历史记录,仅恢复状态)
  • 前端 token 列表:已吊销行增加「恢复」按钮(与「吊销」互斥展示)
  • 新增 GET /api/v1/external/tokens/{id}/setup-package:返回该 token 的接入配置包(Markdown 文本),包含:接口前缀、鉴权头格式、scope 对应端点清单、限频说明、调用示例 curl、提示词模板
  • 前端新增「复制配置包」按钮(列表行 + 生成成功弹窗各一处),一键复制整包
  • 提示词/规范文本来源:优先读 docs/standards/外部只读数据接入规范.md 的结构化摘要(代码常量,不写死在 Vue),按 scope 动态裁剪章节
  • 生成成功弹窗去掉「明文仅显示一次」的强制感措辞,改为「已复制配置包」为主操作

验收标准 / 关闭条件

  1. 吊销后的 token 可通过「恢复」重新启用,且恢复后能正常调用 /api/v1/external/brief(返回 200)
  2. enable 端点对已启用 token 幂等(重复调用返回 200 且状态不变)
  3. 配置包复制内容含:接口地址 + Authorization: Bearer <token> + 至少 1 个 curl 示例 + 提示词模板 + 规范要点,且随 scope 变化
  4. 新增测试覆盖:enable 幂等 / enable 对不存在 id 返回 404 / 配置包按 scope 裁剪
  5. ruff check app/ tests/ 与 pytest 全绿;前端 eslint + vitest 全绿
  6. 审查报告无 CRITICAL

关联

  • 架构:docs/arch/A-20260902-021-外部工具只读开放平台.md(LOCKED v1.0,本单为其 v1.3 增量)
  • 规范:docs/standards/外部只读数据接入规范.md(v1.1)
  • 前置:工单 #11(token 责任人)、#25(brief 任意区间)
  • 设计文档:docs/arch/A-20260910-003-外部接入体验与配置包.md
## 背景 用户 2026-09-10 反馈外部只读 token(workbuddy 日报/简报拉取)使用体验有三处痛点: 1. **明文只显示一次**:生成后关闭弹窗就再也拿不到,用户认为「似乎没必要」——但这是 A-20260902-021 LOCKED 的安全底线(库中仅存 SHA-256 hash,明文不可逆),**架构不变**,改为「不再依赖回看明文」的使用方式。 2. **吊销后不能再用**:现有 `revoke` 是终态软删(`enabled=false`),误操作只能重新建 token 并重新分发。缺「重新启用」。 3. **配置分散**:调用方(WorkBuddy)需要 token + 接口地址 + 提示词 + 规范说明四样东西,现在只能人工四处拼凑。 ## 影响范围 - 后端:`backend/app/api/v1/external.py`(token 管理端点)、`backend/app/services/external_service.py` - 前端:`web/src/views/system/ExternalAccess.vue`、`web/src/api/externalAccess.js` - **不涉及表结构变更**(复用既有 `enabled` 字段),无 Alembic migration - 不影响既有外部调用(纯增量端点 + 前端 UI) ## 根因判断 非缺陷,是**功能缺口**:token 生命周期只设计了「生成 → 吊销」两个终态,缺少「暂停/恢复」中间态;且未提供面向调用方的「开箱配置」物料。 ## 处理动作 / 设计要求 - [ ] 新增 `POST /api/v1/external/tokens/{id}/enable`:把 `enabled` 置回 true(幂等),清空 `revoked_by` 语义化留痕(保留历史记录,仅恢复状态) - [ ] 前端 token 列表:已吊销行增加「恢复」按钮(与「吊销」互斥展示) - [ ] 新增 `GET /api/v1/external/tokens/{id}/setup-package`:返回该 token 的**接入配置包**(Markdown 文本),包含:接口前缀、鉴权头格式、scope 对应端点清单、限频说明、调用示例 curl、**提示词模板** - [ ] 前端新增「复制配置包」按钮(列表行 + 生成成功弹窗各一处),一键复制整包 - [ ] 提示词/规范文本来源:优先读 `docs/standards/外部只读数据接入规范.md` 的结构化摘要(**代码常量,不写死在 Vue**),按 scope 动态裁剪章节 - [ ] 生成成功弹窗去掉「明文仅显示一次」的强制感措辞,改为「已复制配置包」为主操作 ## 验收标准 / 关闭条件 1. 吊销后的 token 可通过「恢复」重新启用,且恢复后能正常调用 `/api/v1/external/brief`(返回 200) 2. `enable` 端点对已启用 token 幂等(重复调用返回 200 且状态不变) 3. 配置包复制内容含:接口地址 + `Authorization: Bearer <token>` + 至少 1 个 curl 示例 + 提示词模板 + 规范要点,且随 scope 变化 4. 新增测试覆盖:`enable` 幂等 / `enable` 对不存在 id 返回 404 / 配置包按 scope 裁剪 5. `ruff check app/ tests/` 与 `pytest` 全绿;前端 `eslint` + `vitest` 全绿 6. 审查报告无 CRITICAL ## 关联 - 架构:`docs/arch/A-20260902-021-外部工具只读开放平台.md`(LOCKED v1.0,本单为其 v1.3 增量) - 规范:`docs/standards/外部只读数据接入规范.md`(v1.1) - 前置:工单 #11(token 责任人)、#25(brief 任意区间) - 设计文档:`docs/arch/A-20260910-003-外部接入体验与配置包.md`
tanzk added the feature label 2026-09-10 18:32:38 +08:00
Author
Owner

实现完成 (2026-09-10)

提交: fc6bc10 (#43 #44 #45 #46 一次性落地)

验收证据

项 结果
ruff All checks passed
pytest 682 passed
eslint 0 errors
vitest 46 passed
npm run build OK

审查报告

docs/reports/20260910-接入中心与导航重组-审查报告.md — 0 CRITICAL,建议通过终审。

审查中实测发现并修复

  • HIGH: MenuTree 漏合并 node.children → 菜单整块不渲染(单测全绿但线上白屏)。已补渲染层测试 MenuTree.spec.js。
  • MEDIUM: 服务层 db.commit() 绕过测试事务回滚 → 二次运行必失败。已改唯一标识 + 自洁。
  • LOW: vitest 缺 @ 别名 / jsdom 30 无 localStorage。已补 setup。

遗留跟进(建议另开工单)

  • integration_profiles 模板版本历史
  • 多 worker 部署时登录会话需外置 Redis
## 实现完成 (2026-09-10) **提交**: `fc6bc10` (#43 #44 #45 #46 一次性落地) ### 验收证据 | 项 | 结果 | |---|---| | ruff | All checks passed | | pytest | **682 passed** | | eslint | 0 errors | | vitest | **46 passed** | | npm run build | OK | ### 审查报告 `docs/reports/20260910-接入中心与导航重组-审查报告.md` — **0 CRITICAL**,建议通过终审。 ### 审查中实测发现并修复 - **HIGH**: MenuTree 漏合并 `node.children` → 菜单整块不渲染(单测全绿但线上白屏)。已补渲染层测试 `MenuTree.spec.js`。 - MEDIUM: 服务层 `db.commit()` 绕过测试事务回滚 → 二次运行必失败。已改唯一标识 + 自洁。 - LOW: vitest 缺 `@` 别名 / jsdom 30 无 localStorage。已补 setup。 ### 遗留跟进(建议另开工单) - integration_profiles 模板版本历史 - 多 worker 部署时登录会话需外置 Redis
Author
Owner

关闭凭证核验 (2026-09-10)

按 docs/standards/issue-workflow.md §6.2「凭证可追溯」逐条核验本次交付:

关闭条件 核验方式 结果
吊销后可恢复且能正常调用 test_enable_restores_revoked_token ✅
enable 幂等 test_enable_is_idempotent ✅
配置包含地址+鉴权+示例+提示词,随 scope 变化 test_setup_package_scoped_endpoints + 实跑渲染 ✅
配置包不含 token 明文 test_setup_package_has_no_plain_token ✅
接入中心可从菜单进入、4 Tab 可加载 浏览器实测 ✅
模型配置响应不含 api_key 字段 test_ai_model_config_no_api_key_leak ✅
提示词预览只读(无写方法) test_ai_prompt_preview_has_no_write_method(405) ✅
登录态校验失败不覆盖旧 state test_confirm_http_does_not_overwrite_on_failed_verify ✅
无 Xvfb 降级 501 + 一键命令 test_login_start_returns_501_when_no_display ✅
系统管理 3 层分组、一级菜单收敛 浏览器实测 DOM + menu.spec.js ✅
零 path 变更 menu.spec.js 断言菜单 path 均在 router ✅
权限配对(manager 读通过/写 403) test_nav_permission_pairing.py 10 用例 ✅
全量回归 ruff 全过 / pytest 682 passed / eslint 0 err / vitest 46 passed / build ✓ ✅
审查报告无 CRITICAL docs/reports/20260910-接入中心与导航重组-审查报告.md ✅

提交: fc6bc10 结论: 关闭条件全部满足,关闭本单。

## 关闭凭证核验 (2026-09-10) 按 `docs/standards/issue-workflow.md` §6.2「凭证可追溯」逐条核验本次交付: | 关闭条件 | 核验方式 | 结果 | |---|---|---| | 吊销后可恢复且能正常调用 | `test_enable_restores_revoked_token` | ✅ | | enable 幂等 | `test_enable_is_idempotent` | ✅ | | 配置包含地址+鉴权+示例+提示词,随 scope 变化 | `test_setup_package_scoped_endpoints` + 实跑渲染 | ✅ | | **配置包不含 token 明文** | `test_setup_package_has_no_plain_token` | ✅ | | 接入中心可从菜单进入、4 Tab 可加载 | 浏览器实测 | ✅ | | **模型配置响应不含 api_key 字段** | `test_ai_model_config_no_api_key_leak` | ✅ | | 提示词预览只读(无写方法) | `test_ai_prompt_preview_has_no_write_method`(405) | ✅ | | 登录态校验失败不覆盖旧 state | `test_confirm_http_does_not_overwrite_on_failed_verify` | ✅ | | 无 Xvfb 降级 501 + 一键命令 | `test_login_start_returns_501_when_no_display` | ✅ | | 系统管理 3 层分组、一级菜单收敛 | 浏览器实测 DOM + `menu.spec.js` | ✅ | | 零 path 变更 | `menu.spec.js` 断言菜单 path 均在 router | ✅ | | 权限配对(manager 读通过/写 403) | `test_nav_permission_pairing.py` 10 用例 | ✅ | | 全量回归 | ruff 全过 / pytest 682 passed / eslint 0 err / vitest 46 passed / build ✓ | ✅ | | 审查报告无 CRITICAL | `docs/reports/20260910-接入中心与导航重组-审查报告.md` | ✅ | **提交**: `fc6bc10` **结论**: 关闭条件全部满足,关闭本单。
tanzk closed this issue 2026-09-10 19:02:56 +08:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tanzk/csctalon#43