Some checks failed
supabase-ci / validate (push) Has been cancelled
(cherry picked from commit facdfd21c9c0c17f09e1a3a5fbe2e2b253f76d82)
89 lines
3.6 KiB
PL/PgSQL
89 lines
3.6 KiB
PL/PgSQL
-- 76-cabinet-memberships-rls-definer.sql
|
|
-- Fix cabinet_memberships/file_cabinets/files recursive RLS.
|
|
--
|
|
-- The original cabinet membership policies joined file_cabinets directly, while
|
|
-- the file_cabinets/files membership policies joined cabinet_memberships
|
|
-- directly. Under an authenticated as-user SELECT this creates an RLS cycle and
|
|
-- PostgreSQL raises 42P17 (infinite recursion detected in policy for relation
|
|
-- "cabinet_memberships").
|
|
--
|
|
-- SECURITY DEFINER helpers evaluate the ownership/membership checks as the
|
|
-- function owner (bypassing the inner RLS checks) while still keying the result
|
|
-- to auth.uid(), matching the class-management helper pattern in 71.
|
|
--
|
|
-- The existing public tables are owned by supabase_admin; Supabase migrations
|
|
-- run as that table owner on dev/prod so the SECURITY DEFINER helpers are owned
|
|
-- by a role that bypasses the inner RLS checks.
|
|
|
|
create or replace function public.is_cabinet_owner(p_cabinet uuid)
|
|
returns boolean
|
|
language sql
|
|
stable
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
select exists (
|
|
select 1
|
|
from public.file_cabinets c
|
|
where c.id = p_cabinet
|
|
and c.user_id = auth.uid()
|
|
)
|
|
$$;
|
|
|
|
create or replace function public.is_cabinet_member(p_cabinet uuid)
|
|
returns boolean
|
|
language sql
|
|
stable
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
select exists (
|
|
select 1
|
|
from public.cabinet_memberships m
|
|
where m.cabinet_id = p_cabinet
|
|
and m.profile_id = auth.uid()
|
|
)
|
|
$$;
|
|
|
|
-- Keep function execution available to the roles used by RLS policies. The
|
|
-- functions disclose only a boolean about the caller's own auth.uid() state.
|
|
revoke all on function public.is_cabinet_owner(uuid) from public, anon;
|
|
revoke all on function public.is_cabinet_member(uuid) from public, anon;
|
|
grant execute on function public.is_cabinet_owner(uuid) to authenticated, service_role;
|
|
grant execute on function public.is_cabinet_member(uuid) to authenticated, service_role;
|
|
|
|
-- Re-declare cabinet_memberships policies without direct file_cabinets subqueries.
|
|
drop policy if exists cm_read_self_or_owner on public.cabinet_memberships;
|
|
create policy cm_read_self_or_owner on public.cabinet_memberships
|
|
for select to authenticated
|
|
using (profile_id = auth.uid() or public.is_cabinet_owner(cabinet_id));
|
|
|
|
drop policy if exists cm_insert_by_owner on public.cabinet_memberships;
|
|
create policy cm_insert_by_owner on public.cabinet_memberships
|
|
for insert to authenticated
|
|
with check (public.is_cabinet_owner(cabinet_id));
|
|
|
|
drop policy if exists cm_update_by_owner on public.cabinet_memberships;
|
|
create policy cm_update_by_owner on public.cabinet_memberships
|
|
for update to authenticated
|
|
using (public.is_cabinet_owner(cabinet_id))
|
|
with check (public.is_cabinet_owner(cabinet_id));
|
|
|
|
drop policy if exists cm_delete_by_owner on public.cabinet_memberships;
|
|
create policy cm_delete_by_owner on public.cabinet_memberships
|
|
for delete to authenticated
|
|
using (public.is_cabinet_owner(cabinet_id));
|
|
|
|
-- Re-declare membership-based cabinet/file read policies without direct
|
|
-- cabinet_memberships subqueries, so selecting cabinets/files does not recurse
|
|
-- back through cabinet_memberships RLS.
|
|
drop policy if exists "User can access cabinets via membership" on public.file_cabinets;
|
|
create policy "User can access cabinets via membership" on public.file_cabinets
|
|
for select to authenticated
|
|
using (public.is_cabinet_member(id));
|
|
|
|
drop policy if exists "User can access files via membership" on public.files;
|
|
create policy "User can access files via membership" on public.files
|
|
for select to authenticated
|
|
using (public.is_cabinet_member(cabinet_id));
|