-- 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 $$;