7.1 KiB
7.1 KiB
后端收敛与防护设计(最小可落地方案)
说明:本文档针对当前仓库中前端直接写 Supabase(存在风险:前端使用 service_role key)问题,提供可执行的最小后台设计、接口与 DB 改造清单,供开发/运维快速落地。
目标(Why)
- 阻断前端持有
service_role导致的越权写库风险。 - 将关键业务写入(接单/订单状态/资金/库存等)收敛到可信后端,保证原子性、幂等和审计。
- 在数据库侧加入最后一道防线(约束 + RLS + RPC)。
概要执行步骤(What / High level)
- 立刻移除前端的
service_role,前端只用anon(阻断最大风险)。 - 实现最小后台 API(先做配送接单/状态流转 rpc + HTTP 接口)。
- 在数据库添加约束、RLS 策略与 RPC(把关键状态流转做成原子函数)。
- 前端分阶段切换到新 API 并回归测试。
- 补充审计、幂等与监控,逐步迁移其它敏感写入。
详细步骤(可直接执行)
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 环境调整与测试:
-- 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;
-- 执行原子更新:仅当 task 可被领取(status=1 且 driver_id IS NULL)
WITH u AS (
UPDATE ml_delivery_tasks
SET driver_id = p_driver_uuid, status = 2, updated_at = now()
WHERE id = p_task_uuid AND status = 1 AND driver_id IS NULL
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_delivery_tasks', 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:
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_delivery_tasks、ml_orders):- 禁止直接由匿名或普通前端更新关键列(例如 driver_id、status);只允许
rpc/ service 调用通过函数更新。
- 禁止直接由匿名或普通前端更新关键列(例如 driver_id、status);只允许
- 索引/约束:
- message_id 唯一:
CREATE UNIQUE INDEX ux_express_notifications_message_id ON express_notifications(message_id); - 补全 NOT NULL / FK / CHECK(枚举字段限制等)。
- message_id 唯一:
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、冲突数)。
迁移与上线路线(分阶段)
- Dev:实现 RPC + API,更新 dev 配置把前端用 anon,完成单元测试与集成测试。
- Stage:灰度发布后端 API,部分司机/少量流量切换到 API。监控 48h。
- Prod:全面切换,删除前端中的直写调用(或通过 feature-flag 关闭)。
- 回滚:保留旧直写代码并用 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_delivery_tasks、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 选项,或告诉我需要调整的文档内容/格式。