# 后端收敛与防护设计(最小可落地方案) 说明:本文档针对当前仓库中前端直接写 Supabase(存在风险:前端使用 service_role key)问题,提供可执行的最小后台设计、接口与 DB 改造清单,供开发/运维快速落地。 --- ## 目标(Why) - 阻断前端持有 `service_role` 导致的越权写库风险。 - 将关键业务写入(接单/订单状态/资金/库存等)收敛到可信后端,保证原子性、幂等和审计。 - 在数据库侧加入最后一道防线(约束 + RLS + RPC)。 --- ## 概要执行步骤(What / High level) 1. 立刻移除前端的 `service_role`,前端只用 `anon`(阻断最大风险)。 2. 实现最小后台 API(订单管理、核心状态流转 rpc + HTTP 接口)。 3. 在数据库添加约束、RLS 策略与 RPC(把关键状态流转做成原子函数)。 4. 前端分阶段切换到新 API 并回归测试。 5. 补充审计、幂等与监控,逐步迁移其它敏感写入。 --- ## 详细步骤(可直接执行) ### A. 紧急措施(立即) - 编辑 `ak/config.uts`:注释或删除明文 `SUPA_KEY`(service_role),替为 anon 或从构建环境注入。 - 确认前端不再把 service_role 打包发布(CI/构建流水线更新)。 ### B. 最小后端 API(优先交付) 在 `server/` 下新增 `server/routes/delivery.js`(或 Fastify 插件)。 推荐接口(统一返回 `{ ok, code?, message?, data? }`): - POST /api/v1/delivery/accept-task - body: { task_id, action_id?: string } - auth: Bearer user_jwt - 后端流程:验证 token → 验证司机权限 → 调用 RPC `rpc_accept_task(uid, task_id, action_id)` → 返回 task 新状态 - POST /api/v1/delivery/update-status - body: { task_id, new_status, action_id, metadata? } - 后端流程:验证 → 调用 `rpc_update_delivery_status(...)`(RPC 内部做多表事务) - POST /api/v1/notifications/express/create - body: notification payload + message_id/action_id(幂等) - 后端流程:幂等检查 -> 插入 -> 触发 push(内部队列) 后端实现要点: - 鉴权:解析并验证前端 Supabase JWT,取 `uid` 作为操作人。 - 内部调用:后端使用环境变量 `SERVICE_ROLE_KEY` 或直接调用 RPC(推荐)执行受权写入。 - 返回格式统一并带错误码,便于前端处理与监控。 ### C. Postgres RPC(示例 SQL) 下面为 `rpc_accept_task` 草稿,供 DB 管理员在 Dev 环境调整与测试: ```sql -- rpc_accept_task: driver 领取任务(幂等 + 原子) CREATE OR REPLACE FUNCTION public.rpc_accept_task(p_driver_uuid uuid, p_task_uuid uuid, p_action_id text) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_task RECORD; BEGIN -- 幂等:插入到去重表,若已存在则返回已处理结果 INSERT INTO action_dedupe(action_id, created_at) VALUES (p_action_id, now()) ON CONFLICT (action_id) DO NOTHING; -- 执行原子更新 WITH u AS ( UPDATE ml_orders SET status = 2, updated_at = now() WHERE id = p_task_uuid AND status = 1 RETURNING * ) SELECT * INTO v_task FROM u LIMIT 1; IF NOT FOUND THEN RETURN jsonb_build_object('ok', false, 'message', 'task not available'); END IF; -- 写审计 INSERT INTO audit_logs(actor_id, action, target_table, target_id, payload, created_at) VALUES (p_driver_uuid, 'accept_task', 'ml_orders', p_task_uuid, row_to_json(v_task), now()); RETURN jsonb_build_object('ok', true, 'task', to_jsonb(v_task)); END; $$; ``` 注意: - 使用 `SECURITY DEFINER` 并确保函数拥有适当权限(仅 server/service_role 可调用)。 - `action_dedupe` 表需创建并设 unique(action_id)。 ### D. RLS 与 约束(示例) - 开启 RLS,例如对 `user_addresses`: ```sql ALTER TABLE user_addresses ENABLE ROW LEVEL SECURITY; CREATE POLICY user_owns_address ON user_addresses FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); ``` - 对关键表(如 `ml_orders`): - 禁止直接由匿名或普通前端更新关键列;只允许 `rpc` / service 调用通过函数更新。 - 索引/约束: - message_id 唯一:`CREATE UNIQUE INDEX ux_express_notifications_message_id ON express_notifications(message_id);` - 补全 NOT NULL / FK / CHECK(枚举字段限制等)。 ### E. 鉴权实现要点(后端) - 验证前端 JWT:解析/校验 Supabase JWT(可使用 Supabase 的用户 API 或直接 JWT 验证)。从 token 获取 `uid` 作为操作人。 - 后端自身使用环境变量 `SERVICE_ROLE_KEY` 调用 Supabase Admin API 或直接调用 Postgres RPC(建议后者更原子)。 - 不要在后端把 `service_role` 返回或写进前端文件。 ### F. 幂等与去重 - 对所有会被重复调用的外部动作(webhook、client retry)要求 `action_id`/`message_id`,并在 DB 层做 `ON CONFLICT DO NOTHING` 或在 RPC 先检查 `action_dedupe`。 ### G. 审计与监控 - 新建 `audit_logs(actor_id, action, target_table, target_id, payload, created_at)` 并在 RPC 中写入。 - 错误报警(Sentry 或日志轮询),并在关键接口记录 metrics(错误率、latency、冲突数)。 --- ## 迁移与上线路线(分阶段) 1. Dev:实现 RPC + API,更新 dev 配置把前端用 anon,完成单元测试与集成测试。 2. Stage:灰度发布后端 API,部分司机/少量流量切换到 API。监控 48h。 3. Prod:全面切换,删除前端中的直写调用(或通过 feature-flag 关闭)。 4. 回滚:保留旧直写代码并用 feature flag 随时回退;若回退需同时短期恢复前端原 key(仅极端应急,注意风险并在短时间内移除)。 --- ## 最小交付清单(可追踪任务) - [ ] 注释/移除前端 `service_role`:`ak/config.uts`(紧急) - [ ] 在 `server/` 新增 `POST /api/v1/delivery/accept-task`(鉴权 + 调用 RPC) - [ ] 在 DB 创建 `rpc_accept_task`、`action_dedupe`、`audit_logs` 表 - [ ] 对 `user_addresses`、`ml_orders` 等启用 RLS 策略(逐表) - [ ] 前端将接单/确认调用迁移到新 API(feature-flag)并回归测试 - [ ] 部署后监控并逐步扩大灰度 --- ## 风险与注意事项 - 风险:短期内若不移除 `service_role`,即便实现后台 API,也无法阻止攻击者通过已有 key 直接写库。优先级最高。 - 注意隐私/合规:审计日志要避免写入敏感明文(例如完整支付凭证)或加密存储。 - DB 权限:确保 RPC 使用受限角色执行并限制 RPC 所能做的动作。 --- ## 推荐下一步(我可以马上帮你做) - 选项 1:生成并提交 `ak/config.uts` 的 patch,将 `SUPA_KEY` 替换为示例 anon(阻断风险)。 - 选项 2:生成 `rpc_accept_task` 的完整 SQL + `server/routes/delivery.js` 的 Node 实现样例(包含鉴权中间件与调用示例)。 - 选项 3:列出仓库内所有前端写入点并按“必须走后端 / 可保留直写”分级清单(便于逐步迁移)。 请回复 `1`、`2` 或 `3` 选项,或告诉我需要调整的文档内容/格式。 --- ## 附录:前端不得直接连接数据库(简明实施指南) 结论:所有需要提升权限、跨表原子性、审计或幂等保证的写入必须走可信后端;仅在严格受限(RLS + 约束)且只影响用户自身资源时,前端可用 anon key 直写。 - 何时必须走后端:更新 `driver_id`、订单状态、资金/结算、库存变更、跨表事务、需要审计或幂等的操作、需使用 `service_role` 权限的写入。 - 何时可允许前端直写:仅用户自身数据(例如 `user_addresses`)且已启用 RLS 与完整约束,且不涉及跨表或审计要求。 最小后端职责(示例): - 验证前端 JWT(取 `uid`);做权限校验与幂等检查(`action_id`)。 - 调用 Postgres RPC(`SECURITY DEFINER`)或使用 `SERVICE_ROLE_KEY` 完成受权写入。 - 写入 `audit_logs` 并返回统一错误格式 `{ ok, code?, message?, data? }`。 紧急建议(复述以便执行): - 立即移除或注释前端的 `service_role`(已在工作区修改 `ak/config.uts`)。 - 在 CI 中阻止带有 `service_role` 的 key 进入前端配置或打包产物。 - 为关键流实现 RPC(如 `rpc_accept_task`)并暴露最小后端 API(如 `/api/v1/delivery/accept-task`),逐步将前端写入迁移到后端。 需要我把这个附录再整理为单独文件或把 `rpc_accept_task` SQL 与 `server/routes/delivery.js` 示例直接追加到本文件吗?