BEGIN; DROP POLICY IF EXISTS "devices_delete_own" ON public.devices; CREATE OR REPLACE FUNCTION public.protect_device_security_fields() RETURNS trigger LANGUAGE plpgsql SET search_path = '' AS $$ BEGIN IF current_user NOT IN ('postgres', 'service_role', 'supabase_admin') AND ( NEW.user_id IS DISTINCT FROM OLD.user_id OR NEW.installation_id IS DISTINCT FROM OLD.installation_id OR NEW.platform IS DISTINCT FROM OLD.platform OR NEW.revoked_at IS DISTINCT FROM OLD.revoked_at OR NEW.push_token IS DISTINCT FROM OLD.push_token ) THEN RAISE EXCEPTION 'device_security_fields_are_server_managed' USING ERRCODE = '42501'; END IF; IF OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NULL THEN RAISE EXCEPTION 'device_revocation_is_permanent' USING ERRCODE = '42501'; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS reject_direct_device_push_token_update ON public.devices; DROP FUNCTION IF EXISTS public.reject_direct_device_push_token(); CREATE TRIGGER protect_device_security_fields_update BEFORE UPDATE ON public.devices FOR EACH ROW EXECUTE FUNCTION public.protect_device_security_fields(); CREATE OR REPLACE FUNCTION public.revoke_device(target_device_id uuid) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ DECLARE current_user_id uuid := auth.uid(); target_device public.devices; BEGIN IF current_user_id IS NULL THEN RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501'; END IF; IF target_device_id IS NULL THEN RAISE EXCEPTION 'invalid_device' USING ERRCODE = '22023'; END IF; PERFORM pg_advisory_xact_lock(hashtextextended(target_device_id::text, 73052)); SELECT * INTO target_device FROM public.devices WHERE id = target_device_id AND user_id = current_user_id FOR UPDATE; IF target_device.id IS NULL THEN RAISE EXCEPTION 'device_not_found' USING ERRCODE = 'P0002'; END IF; IF target_device.revoked_at IS NULL THEN UPDATE public.devices SET revoked_at = now(), push_token = NULL WHERE id = target_device.id RETURNING * INTO target_device; END IF; DELETE FROM public.push_tokens WHERE device_id = target_device.id AND user_id = current_user_id; RETURN to_jsonb(target_device); END; $$; CREATE OR REPLACE FUNCTION public.unregister_current_device( current_installation_id uuid ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ DECLARE current_user_id uuid := auth.uid(); target_device_id uuid; BEGIN IF current_user_id IS NULL THEN RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501'; END IF; IF current_installation_id IS NULL THEN RAISE EXCEPTION 'invalid_device' USING ERRCODE = '22023'; END IF; SELECT id INTO target_device_id FROM public.devices WHERE user_id = current_user_id AND installation_id = current_installation_id AND revoked_at IS NULL FOR UPDATE; IF target_device_id IS NULL THEN RETURN jsonb_build_object('removed', false); END IF; DELETE FROM public.push_tokens WHERE device_id = target_device_id AND user_id = current_user_id; DELETE FROM public.devices WHERE id = target_device_id AND user_id = current_user_id AND revoked_at IS NULL; RETURN jsonb_build_object('removed', true, 'device_id', target_device_id); END; $$; CREATE OR REPLACE FUNCTION public.purge_revoked_device( target_device_id uuid ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ DECLARE current_user_id uuid := auth.uid(); jwt_issued_at timestamptz; removed_count integer; BEGIN IF current_user_id IS NULL THEN RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501'; END IF; BEGIN jwt_issued_at := to_timestamp((auth.jwt()->>'iat')::double precision); EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION 'recent_authentication_required' USING ERRCODE = '42501'; END; IF jwt_issued_at IS NULL OR jwt_issued_at < now() - interval '10 minutes' THEN RAISE EXCEPTION 'recent_authentication_required' USING ERRCODE = '42501'; END IF; DELETE FROM public.devices WHERE id = target_device_id AND user_id = current_user_id AND revoked_at IS NOT NULL; GET DIAGNOSTICS removed_count = ROW_COUNT; IF removed_count = 0 THEN RAISE EXCEPTION 'revoked_device_not_found' USING ERRCODE = 'P0002'; END IF; RETURN jsonb_build_object('removed', true, 'device_id', target_device_id); END; $$; REVOKE ALL ON FUNCTION public.revoke_device(uuid) FROM PUBLIC, anon; REVOKE ALL ON FUNCTION public.unregister_current_device(uuid) FROM PUBLIC, anon; REVOKE ALL ON FUNCTION public.purge_revoked_device(uuid) FROM PUBLIC, anon; GRANT EXECUTE ON FUNCTION public.revoke_device(uuid) TO authenticated; GRANT EXECUTE ON FUNCTION public.unregister_current_device(uuid) TO authenticated; GRANT EXECUTE ON FUNCTION public.purge_revoked_device(uuid) TO authenticated; COMMIT;