From 41bfd7032292ed1da0601f0d413db85af2d9c774 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Wed, 29 Jul 2026 22:36:06 -0700 Subject: [PATCH] feat(db): add ai_chat_log so the ask endpoint is actually rate limited `ask` has never been deployed, and the CI step in this branch will publish it. Its limiter counts rows in ai_chat_log to cap requests at 8/min and 120/day per IP, but no migration ever created that table, and the limiter fails open when the count cannot be read. Shipping `ask` without this would put an unauthenticated endpoint with Access-Control-Allow-Origin: * in front of paid model APIs with no limit at all. Applied to the live database as well. --- supabase/migrations/20260730_ai_chat_log.sql | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 supabase/migrations/20260730_ai_chat_log.sql diff --git a/supabase/migrations/20260730_ai_chat_log.sql b/supabase/migrations/20260730_ai_chat_log.sql new file mode 100644 index 0000000..025bb71 --- /dev/null +++ b/supabase/migrations/20260730_ai_chat_log.sql @@ -0,0 +1,28 @@ +-- Rate-limit ledger for the public `ask` edge function (news.pex.mom assistant). +-- +-- `ask` is unauthenticated and sends `Access-Control-Allow-Origin: *`, so this +-- table is its only throttle: 8 requests per IP per minute, 120 per day. The +-- limiter counts rows here and fails open when the count cannot be read, which +-- means shipping `ask` without this table would leave a public endpoint calling +-- paid model APIs with no limit at all. + +create table if not exists public.ai_chat_log ( + id bigserial primary key, + ip text not null, + created_at timestamptz not null default now() +); + +-- The limiter always filters on both columns together (ip = ? and created_at >= ?). +create index if not exists ai_chat_log_ip_created_at_idx + on public.ai_chat_log (ip, created_at desc); + +-- Only the service role touches this table; the function reaches it through +-- PostgREST with the service key, which bypasses RLS. Enabling RLS without any +-- policy therefore keeps the behaviour intact while denying anon and +-- authenticated clients, who have no reason to read a table of IP addresses. +alter table public.ai_chat_log enable row level security; + +revoke all on public.ai_chat_log from anon, authenticated; + +comment on table public.ai_chat_log is + 'Rate-limit ledger for the public ask endpoint. Holds IP addresses; prune rows older than the 24h window the limiter uses.';