From 540b506f91ac90fa07ef7a4e34e535bdd59b759b Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 18:48:22 +0200 Subject: [PATCH] feat(P3.T1): devices.revoked_at + revoke_device RPC + realtime publication Co-Authored-By: Claude Sonnet 4.6 --- .../20260516000005_device_revocation.sql | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 supabase/migrations/20260516000005_device_revocation.sql diff --git a/supabase/migrations/20260516000005_device_revocation.sql b/supabase/migrations/20260516000005_device_revocation.sql new file mode 100644 index 0000000..cf58c04 --- /dev/null +++ b/supabase/migrations/20260516000005_device_revocation.sql @@ -0,0 +1,67 @@ +-- Phase 3: per-device revocation. Adds a revoked_at flag, an RPC that lets +-- the device's owner flip it, and publishes the devices table to realtime +-- so every signed-in install can react to its own row being revoked. + +alter table public.devices + add column if not exists revoked_at timestamptz null; + +create index if not exists devices_user_revoked_idx + on public.devices(user_id, revoked_at); + +-- RLS: existing policy on devices already gates by user_id; an UPDATE done +-- via the RPC runs with SECURITY DEFINER so we don't widen the RLS surface. + +create or replace function public.revoke_device(p_device_id uuid) +returns void +language plpgsql +security definer +set search_path = public +as $$ +declare + v_owner uuid; +begin + if auth.uid() is null then + raise exception 'not authenticated' using errcode = '28000'; + end if; + + select user_id into v_owner + from public.devices + where id = p_device_id; + + if v_owner is null then + raise exception 'device not found' using errcode = 'P0002'; + end if; + + if v_owner <> auth.uid() then + raise exception 'not authorized' using errcode = '42501'; + end if; + + update public.devices + set revoked_at = now() + where id = p_device_id + and revoked_at is null; +end; +$$; + +revoke all on function public.revoke_device(uuid) from public; +grant execute on function public.revoke_device(uuid) to authenticated; + +-- Replica identity full so UPDATE events deliver the full new row (including +-- revoked_at) to subscribers; default REPLICA IDENTITY DEFAULT only sends +-- the primary key columns, which would force a refetch on every event. +alter table public.devices replica identity full; + +-- Add devices to the supabase_realtime publication if not already present. +do $$ +begin + if not exists ( + select 1 + from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'devices' + ) then + execute 'alter publication supabase_realtime add table public.devices'; + end if; +end +$$;