补充方案

This commit is contained in:
not-like-juvenile
2026-03-12 10:36:51 +08:00
parent 9cc6dcc2a6
commit 4acbb8ced5
7 changed files with 290 additions and 18 deletions

View File

@@ -0,0 +1,85 @@
-- =====================================================================================
-- RPC: notify-worker safe recipients lookup (RLS-safe)
--
-- 背景:
-- - public.ml_orders 已开启 RLSPostgREST 在未携带可解码 JWT 时auth.uid() 为 NULL
-- 直接 SELECT 会被策略过滤为 0 行,导致 notify-worker 报 “order not found for waybill”。
-- - 在一些自托管场景中Authorization: Bearer <service_role JWT> 可能因 JWT_SECRET 不一致被 PostgREST 拒绝401 PGRST301
--
-- 方案:
-- - 提供 SECURITY DEFINER 的 RPC只返回订单的收件人映射user_id / merchant_id
-- - 通过请求头 x-notify-worker-token 做显式鉴权(避免把表全局 SELECT 放开)。
--
-- 使用:
-- - notify-worker 调用 POST /rest/v1/rpc/notify_get_order_recipients
-- 并携带 header: x-notify-worker-token: <NOTIFY_WORKER_RPC_TOKEN>
-- =====================================================================================
BEGIN;
CREATE OR REPLACE FUNCTION public.notify_get_order_recipients(
p_order_id UUID DEFAULT NULL,
p_order_no TEXT DEFAULT NULL
)
RETURNS TABLE (
id UUID,
order_no VARCHAR,
user_id UUID,
merchant_id UUID
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $func$
DECLARE
headers_json JSON;
token TEXT;
expected_token TEXT;
BEGIN
-- 1) 读取请求头 tokenPostgREST 会把 headers 放入 GUC request.headers
expected_token := current_setting('app.notify_worker_token', true);
headers_json := NULLIF(current_setting('request.headers', true), '')::json;
IF headers_json IS NOT NULL THEN
token := headers_json->>'x-notify-worker-token';
END IF;
IF expected_token IS NULL OR expected_token = '' THEN
RAISE EXCEPTION 'server misconfigured: app.notify_worker_token is not set';
END IF;
IF token IS NULL OR token <> expected_token THEN
RAISE EXCEPTION 'permission denied: invalid x-notify-worker-token';
END IF;
-- 2) 参数校验
IF (p_order_id IS NULL OR p_order_id::text = '') AND (p_order_no IS NULL OR btrim(p_order_no) = '') THEN
RAISE EXCEPTION 'p_order_id or p_order_no must be provided';
END IF;
-- 3) 返回映射SECURITY DEFINER 可绕过 RLS只返回最小必要字段
RETURN QUERY
SELECT o.id, o.order_no, o.user_id, o.merchant_id
FROM public.ml_orders o
WHERE (p_order_id IS NOT NULL AND o.id = p_order_id)
OR (p_order_no IS NOT NULL AND o.order_no = p_order_no)
LIMIT 1;
END;
$func$;
-- 默认收紧:撤销 PUBLIC按需授予 anon/authenticated/service_role 执行权限。
REVOKE ALL ON FUNCTION public.notify_get_order_recipients(UUID, TEXT) FROM PUBLIC;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
GRANT EXECUTE ON FUNCTION public.notify_get_order_recipients(UUID, TEXT) TO anon;
END IF;
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
GRANT EXECUTE ON FUNCTION public.notify_get_order_recipients(UUID, TEXT) TO authenticated;
END IF;
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
GRANT EXECUTE ON FUNCTION public.notify_get_order_recipients(UUID, TEXT) TO service_role;
END IF;
END $$;
COMMIT;

View File

@@ -10,6 +10,7 @@
- `SUPA_USE_BEARER`(可选):是否附加 `Authorization: Bearer <SUPA_KEY>`,默认 `false`
- 在一些自托管 Supabase/Kongkey-auth环境中**只需要** `apikey`;如果误加 Bearer 且 key 不是 JWT可能出现 `PGRST301`"None of the keys was able to decode the JWT")。
- `WEBHOOK_SECRET`(可选):与第三方共享的 HMAC-SHA256 secret用于校验 `X-Signature`(签名为 hex
- `WEBHOOK_REJECT_INVALID_SIGNATURE`(可选):若为 `true`,且配置了 `WEBHOOK_SECRET`,则验签失败会直接返回 HTTP 401默认不拒绝只记录
- `WEBHOOK_PORT`(可选):接收器监听端口,默认 `7201`(推荐用这个,便于与 push-server 共享同一份 `server/config.json`
- `PORT`(可选):接收器监听端口(兼容旧用法;若共享 `server/config.json` 且其中 `PORT=7301`,会导致端口冲突)
@@ -85,6 +86,8 @@ curl -i -X POST http://localhost:7201/webhook/express/status \
-d "$BODY"
```
> 重要:签名计算必须使用**原始请求体文本**raw body。接收器也会使用 raw body 进行验签;不要用 JSON 对象 stringify 后的字符串替代。
健康检查:
- `GET http://localhost:7201/health`(端口以 `PORT` 为准)
@@ -125,12 +128,15 @@ Stop-Process -Id <PID>
验证写入(查看 Supabase
```bash
# 示例:列最近 5 条原始回文
curl -s -H "apikey: $SUPA_KEY" -H "Authorization: Bearer $SUPA_KEY" \
curl -s -H "apikey: $SUPA_KEY" -H "Accept: application/json" \
"$SUPA_URL/rest/v1/platform_express_event_raw?select=*&order=received_at.desc&limit=5" | jq .
# 查看最近轨迹事件
curl -s -H "apikey: $SUPA_KEY" -H "Authorization: Bearer $SUPA_KEY" \
curl -s -H "apikey: $SUPA_KEY" -H "Accept: application/json" \
"$SUPA_URL/rest/v1/platform_express_tracking_events?select=*&order=created_at.desc&limit=5" | jq .
# 如果你的环境已确认 Bearer 可用(不会触发 PGRST301也可以额外加上
# -H "Authorization: Bearer $SUPA_KEY"
```
与仓库中 Mock 实现的关系:
@@ -146,6 +152,6 @@ curl -s -H "apikey: $SUPA_KEY" -H "Authorization: Bearer $SUPA_KEY" \
- 若需要我加重放防护或返回 4xx/5xx 更精确的逻辑,也可继续实现。
文件位置:
- [Webhook 接收器](pages/mall/delivery/server/webhook-receiver.js)
- [Webhook 接收器](pages/mall/delivery/webhook-server/webhook-receiver.js)
作者:自动生成(可手动调整)