fix(admin): remove redundant confirmations and repair code access
This commit is contained in:
+18
-17
@@ -3466,32 +3466,33 @@
|
||||
- 相关记录:BUG-177、BUG-198、BUG-199
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
## BUG-207 | 管理端业务操作重复要求邮箱验证码与手工原因
|
||||
## BUG-207 | 管理端业务操作重复要求邮箱验证码、手工原因与二次确认弹窗
|
||||
|
||||
- 状态:resolved(补充修复已完成,待提交与发布)
|
||||
- 首次发现:2026-08-16
|
||||
- 最近更新:2026-08-16
|
||||
- 影响面:管理端兑换码生成/编辑/撤销、管理员角色变更、账务与订阅调整、商品保存/发布、功能开关发布、易支付设置等写操作。
|
||||
- 用户现象:管理员已经登录后台并具备对应权限,执行批量生成兑换码等日常营销操作时仍需发送并等待邮箱验证码;首轮修复删除邮箱验证后,业务确认弹窗仍强制填写“操作原因”,日常营销操作仍被无必要的二次输入打断。
|
||||
- 根因:多个管理写入被统一接入 operation-level `requireHighRiskAdminMutation`,公共 `ReasonActionModal` 又内置权限级邮箱 OTP challenge/proof,导致账户登录安全与每次业务操作二次验证被错误叠加到所有管理业务。
|
||||
- 修复:所有管理业务写入统一改用 `requireAdminMutation`,继续强制管理员会话、细粒度权限与可信 Origin;删除 `/api/admin/reauth`、high-risk challenge/proof cookie 及相关 UI 参数。公共业务确认弹窗改为无需填写任何内容的 `ConfirmActionModal`,兑换码、管理员角色、订单/订阅调整、商品、功能开关、易支付与用户重置请求体不再要求客户端 `reason`;服务端按动作注入固定审计标识并继续传给原数据库 RPC 的非空 reason 参数。
|
||||
- 安全边界:保留 request ID、数据库权限检查、领域 RPC、append-only 审计、最后一位 Owner 保护;保留账户级 Better Auth TOTP MFA 和一次性恢复码;普通用户登录、注册与找回密码的邮箱 OTP 不受影响。
|
||||
- 验证:管理端权限、业务确认、兑换码/账务合同、账户级 MFA 等 5 个聚焦测试文件共 47/47 通过;`tsc --noEmit`、17 个目标源文件 ESLint 与 `git diff --check` 通过。首轮邮箱复核移除提交已发布到 staging,但“无需手工原因”的补充修复尚未发布。
|
||||
- 防复发:新增管理业务时只能在登录、权限、Origin、服务端审计标识和数据库审计边界内扩展;不得把邮箱 OTP 或手工原因输入放回公共业务确认弹窗。账户级 MFA 与普通用户身份验证必须保持独立。
|
||||
- 相关记录:BUG-155、BUG-156
|
||||
- 影响面:管理端兑换码生成/编辑/撤销、管理员角色变更、账务与订阅调整、商品保存/发布、功能开关发布、模型发布、易支付设置等写操作。
|
||||
- 用户现象:管理员已经登录后台并具备对应权限,执行批量生成兑换码等日常操作时仍需发送邮箱验证码;首轮删除邮箱验证后,又先填写“操作原因”,提交真实业务表单后还出现额外的“确定”弹窗,形成连续二次确认。
|
||||
- 根因:多个管理写入被统一接入 operation-level `requireHighRiskAdminMutation`,公共 `ReasonActionModal` 内置权限级邮箱 OTP challenge/proof;后续只把它替换成 `ConfirmActionModal`,仍保留了多余的操作级确认层,没有让真实的数据录入表单直接执行。
|
||||
- 修复:所有管理业务写入统一使用 `requireAdminMutation`,继续强制管理员会话、对应权限与可信 Origin;删除 `/api/admin/reauth`、high-risk challenge/proof cookie、`ReasonActionModal`、`ConfirmActionModal`、相关 `Popconfirm` / `Modal.confirm` 与 pending-confirm 状态。真实的数据录入 Modal 继续保留,但点击表单的“生成 / 保存 / 发布”等主按钮即直接执行;无额外参数的撤销、重试与开关动作由原按钮直接执行。客户端不再提交手工 `reason`,服务端按动作注入固定审计标识并继续传给数据库 RPC 的非空审计字段。
|
||||
- 安全边界:保留 request ID、数据库 actor 身份校验、领域 RPC、细粒度权限、可信 Origin、append-only 审计和最后一位 Owner 保护;保留账户级 Better Auth TOTP MFA 与一次性恢复码;普通用户登录、注册和找回密码的邮箱 OTP 不受影响。
|
||||
- 验证:全局源码扫描确认管理业务中不存在 `ConfirmActionModal`、`Popconfirm`、`Modal.confirm`、`reason-action-modal` 或“操作原因”;管理端权限、业务直提交流程、兑换码/账务合同、账户级 MFA 等 6 个聚焦测试文件共 52/52 通过;`tsc --noEmit`、改动 TS/TSX 文件 ESLint 与 `git diff --check` 通过。
|
||||
- 防复发:新增管理业务时只能在登录、权限、Origin、服务端审计标识和数据库审计边界内扩展;不得把邮箱 OTP、手工原因或通用二次确认弹窗放回日常管理操作。只有真实的数据录入/选择表单可以使用 Modal,账户级 MFA 与普通用户身份验证必须保持独立。
|
||||
- 相关记录:BUG-155、BUG-156、BUG-209
|
||||
- 修复版本:本地未提交候选(首轮邮箱复核移除:`4f6cf5782d3871a28e531a4dff9bcc6a2633ce09`)
|
||||
|
||||
## BUG-208 | self-hosted 管理端生成兑换码返回通用 500
|
||||
## BUG-209 | self-hosted 管理端生成兑换码先返回通用 500,随后合法管理员被权限链拒绝
|
||||
|
||||
- 状态:resolved(本地修复,待提交与发布)
|
||||
- 状态:resolved(本地修复,待提交、迁移与发布)
|
||||
- 首次发现:2026-08-16
|
||||
- 最近更新:2026-08-16
|
||||
- 影响面:self-hosted runtime 的 `POST /api/admin/codes` 批量生成兑换码;列表读取、普通用户兑换与 production 未在本次诊断中验证。
|
||||
- 用户现象:具备权限的管理员提交合法点数、数量、到期时间和备注后,接口返回 `500 {"error":"后台服务暂时不可用"}`,无法生成兑换码。
|
||||
- 根因:`runCodeRpc()` 将 JavaScript 对象数组直接作为 `$5::jsonb` 参数交给 `node-postgres`。实际 `pg` serializer 会把该值编码成 PostgreSQL array 文本(例如 `{"{\\"codeHash\\":...}"}`),而不是 JSON 数组;RPC 的 `jsonb` 入参因此无法按预期解析,底层错误又被通用管理错误映射收敛为 500。staging 容器原始 SQLSTATE 未取得,因此不记录未经验证的具体错误码。
|
||||
- 修复:调用 `public.admin_create_redemption_codes` 前显式执行 `JSON.stringify(input.p_codes)`;RPC 签名、权限、request ID、明文码仅单次返回和 append-only 审计保持不变。同时由服务端注入 `admin_console_create_redemption_codes` 审计标识,不再依赖客户端原因。
|
||||
- 验证:本地使用项目实际 `pg` serializer 对比确认:对象数组会被编码为 PostgreSQL array 文本,显式序列化后保持合法 JSON 数组文本;新增源码合同锁定 `JSON.stringify(input.p_codes)`、服务端审计标识和无客户端 reason,相关 5 个聚焦测试文件共 47/47 通过;`tsc --noEmit`、目标 ESLint 与 `git diff --check` 通过。
|
||||
- 防复发:self-hosted `pg` 的 `jsonb` 参数必须显式 JSON 序列化;数据库/源码合同测试需要锁定 `JSON.stringify(input.p_codes)`,不能只依赖 Supabase RPC 测试覆盖参数编码。
|
||||
- 影响面:self-hosted runtime 的兑换码列表、批量生成、编辑与撤销;普通用户兑换和 production 未在本次修复中验证。
|
||||
- 用户现象:第一次提交合法点数、数量、到期时间和备注时,`POST /api/admin/codes` 返回 `500 {"error":"后台服务暂时不可用"}`;修正参数序列化后,已登录且能进入后台的管理员再次提交无 `reason` 请求,返回 `{"error":"无权执行此操作"}`。
|
||||
- 根因:存在两个独立问题。第一,`runCodeRpc()` 将 JavaScript 对象数组直接作为 `$5::jsonb` 参数交给 `node-postgres`,被编码成 PostgreSQL array 文本而不是 JSON 数组。第二,兑换码页面、Refine access-control、API 与 PostgreSQL wrapper 使用了不一致且过窄的 `billing.adjustments.write`;部分合法后台角色只有所有管理员共有的 `admin.access`,因此请求在 UI、API 或数据库任一层都可能被拒绝。
|
||||
- 修复:调用 `public.admin_create_redemption_codes` 前显式执行 `JSON.stringify(input.p_codes)`。兑换码列表、创建、编辑、撤销的 UI 可写判断、Refine 资源读写权限、GET/POST/PATCH/DELETE 路由及三个 PostgreSQL wrapper 全部统一为 `admin.access`。新增向前迁移重建 wrapper 与审计 trigger,把兑换码审计行的 `permission_used` 统一写成 `admin.access`;保留服务端固定审计标识、原 RPC 签名、request ID、明文码仅单次返回和数据库 actor 身份校验。
|
||||
- 验证:本地使用项目实际 `pg` serializer 对比确认显式序列化后保持合法 JSON 数组文本;源码合同锁定兑换码 UI、Refine、四个 API 方法、三个数据库 wrapper 与审计权限一致,且不再依赖客户端 `reason` 或操作确认弹窗。相关 6 个聚焦测试文件共 52/52 通过;`tsc --noEmit`、改动 TS/TSX 文件 ESLint 与 `git diff --check` 通过。
|
||||
- 发布要求:必须先把 `20260816010000_admin_redemption_admin_access.sql` 应用到 staging 数据库,再部署同一精确 SHA;只部署应用代码仍会被旧 PostgreSQL wrapper 按 `billing.adjustments.write` 拒绝。
|
||||
- 防复发:self-hosted `pg` 的 `jsonb` 参数必须显式 JSON 序列化;一个管理资源的列表、UI access-control、API guard、数据库 permission check 与审计 `permission_used` 必须使用同一权限语义,不能只改前端或 API。
|
||||
- 相关记录:BUG-155、BUG-207
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
|
||||
Reference in New Issue
Block a user