517 lines
22 KiB
PL/PgSQL
517 lines
22 KiB
PL/PgSQL
-- =====================================================
|
|
-- COMPREHENSIVE RLS FIX
|
|
-- Fixes infinite recursion + enum cast issues
|
|
-- =====================================================
|
|
|
|
-- Helper: returns the space_ids the user belongs to (security definer avoids recursion)
|
|
create or replace function public.get_user_space_ids(uid uuid)
|
|
returns setof uuid
|
|
language sql
|
|
security definer
|
|
stable
|
|
set search_path = public
|
|
as $$
|
|
select space_id from public.space_members where user_id = uid
|
|
$$;
|
|
|
|
-- Helper: checks if user has one of the given roles in a space
|
|
-- NOTE: role column is a member_role enum, so we cast to text for comparison
|
|
create or replace function public.user_has_role_in_space(uid uuid, sid uuid, allowed_roles text[])
|
|
returns boolean
|
|
language sql
|
|
security definer
|
|
stable
|
|
set search_path = public
|
|
as $$
|
|
select exists (
|
|
select 1 from public.space_members
|
|
where user_id = uid
|
|
and space_id = sid
|
|
and role::text = any(allowed_roles)
|
|
)
|
|
$$;
|
|
|
|
-- Grant execute permissions
|
|
grant execute on function public.get_user_space_ids(uuid) to authenticated, service_role;
|
|
grant execute on function public.user_has_role_in_space(uuid, uuid, text[]) to authenticated, service_role;
|
|
|
|
-- =====================================================
|
|
-- SPACES
|
|
-- =====================================================
|
|
drop policy if exists "spaces_select_members" on public.spaces;
|
|
drop policy if exists "spaces_insert_authenticated" on public.spaces;
|
|
drop policy if exists "spaces_update_admins" on public.spaces;
|
|
drop policy if exists "spaces_delete_admins" on public.spaces;
|
|
|
|
create policy "spaces_select_members" on public.spaces
|
|
for select using (id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "spaces_insert_authenticated" on public.spaces
|
|
for insert with check (auth.uid() is not null);
|
|
|
|
create policy "spaces_update_admins" on public.spaces
|
|
for update using (public.user_has_role_in_space(auth.uid(), id, array['admin']));
|
|
|
|
create policy "spaces_delete_admins" on public.spaces
|
|
for delete using (public.user_has_role_in_space(auth.uid(), id, array['admin']));
|
|
|
|
-- =====================================================
|
|
-- SPACE MEMBERS
|
|
-- =====================================================
|
|
drop policy if exists "space_members_select_own" on public.space_members;
|
|
drop policy if exists "space_members_select_same_space" on public.space_members;
|
|
drop policy if exists "space_members_select_own_space" on public.space_members;
|
|
drop policy if exists "space_members_insert_authenticated" on public.space_members;
|
|
drop policy if exists "space_members_update_admins" on public.space_members;
|
|
drop policy if exists "space_members_delete_admins" on public.space_members;
|
|
|
|
create policy "space_members_select_own" on public.space_members
|
|
for select using (user_id = auth.uid());
|
|
|
|
create policy "space_members_select_same_space" on public.space_members
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
-- Any authenticated user can insert a row where user_id = themselves
|
|
create policy "space_members_insert_authenticated" on public.space_members
|
|
for insert with check (auth.uid() is not null and user_id = auth.uid());
|
|
|
|
-- Users can update their own profile; admins can update anyone in their space
|
|
create policy "space_members_update_admins" on public.space_members
|
|
for update using (
|
|
user_id = auth.uid()
|
|
or public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
create policy "space_members_delete_admins" on public.space_members
|
|
for delete using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- TASKS
|
|
-- =====================================================
|
|
drop policy if exists "tasks_select_members" on public.tasks;
|
|
drop policy if exists "tasks_insert_members" on public.tasks;
|
|
drop policy if exists "tasks_update_members" on public.tasks;
|
|
drop policy if exists "tasks_delete_members" on public.tasks;
|
|
|
|
create policy "tasks_select_members" on public.tasks
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "tasks_insert_members" on public.tasks
|
|
for insert with check (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "tasks_update_members" on public.tasks
|
|
for update using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "tasks_delete_members" on public.tasks
|
|
for delete using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
-- =====================================================
|
|
-- PROJECTS
|
|
-- =====================================================
|
|
drop policy if exists "projects_select_members" on public.projects;
|
|
drop policy if exists "projects_insert_members" on public.projects;
|
|
drop policy if exists "projects_update_members" on public.projects;
|
|
drop policy if exists "projects_delete_members" on public.projects;
|
|
|
|
create policy "projects_select_members" on public.projects
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "projects_insert_members" on public.projects
|
|
for insert with check (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "projects_update_members" on public.projects
|
|
for update using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "projects_delete_members" on public.projects
|
|
for delete using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
-- =====================================================
|
|
-- KNOWLEDGE BASE
|
|
-- =====================================================
|
|
drop policy if exists "kb_select_members" on public.knowledge_base;
|
|
drop policy if exists "kb_insert_members" on public.knowledge_base;
|
|
drop policy if exists "kb_update_members" on public.knowledge_base;
|
|
drop policy if exists "kb_delete_board" on public.knowledge_base;
|
|
|
|
create policy "kb_select_members" on public.knowledge_base
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "kb_insert_members" on public.knowledge_base
|
|
for insert with check (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "kb_update_members" on public.knowledge_base
|
|
for update using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "kb_delete_board" on public.knowledge_base
|
|
for delete using (public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board']));
|
|
|
|
-- =====================================================
|
|
-- SECRETS
|
|
-- =====================================================
|
|
drop policy if exists "secrets_select_admins" on public.secrets;
|
|
drop policy if exists "secrets_modify_admins" on public.secrets;
|
|
|
|
create policy "secrets_select_admins" on public.secrets
|
|
for select using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board', 'treasurer'])
|
|
);
|
|
|
|
create policy "secrets_modify_admins" on public.secrets
|
|
for all using (public.user_has_role_in_space(auth.uid(), space_id, array['admin']));
|
|
|
|
-- =====================================================
|
|
-- AREA LEADS
|
|
-- =====================================================
|
|
drop policy if exists "area_leads_select_members" on public.area_leads;
|
|
drop policy if exists "area_leads_modify_board" on public.area_leads;
|
|
|
|
create policy "area_leads_select_members" on public.area_leads
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "area_leads_modify_board" on public.area_leads
|
|
for all using (public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board']));
|
|
|
|
-- =====================================================
|
|
-- CONTACTS
|
|
-- =====================================================
|
|
drop policy if exists "contacts_select_members" on public.contacts;
|
|
drop policy if exists "contacts_modify_members" on public.contacts;
|
|
|
|
create policy "contacts_select_members" on public.contacts
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "contacts_modify_members" on public.contacts
|
|
for all using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
-- =====================================================
|
|
-- PAYMENTS
|
|
-- =====================================================
|
|
drop policy if exists "payments_select_treasurer" on public.payments;
|
|
drop policy if exists "payments_modify_treasurer" on public.payments;
|
|
|
|
create policy "payments_select_treasurer" on public.payments
|
|
for select using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board', 'treasurer'])
|
|
);
|
|
|
|
create policy "payments_modify_treasurer" on public.payments
|
|
for all using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'treasurer'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- COMMS CHANNELS
|
|
-- =====================================================
|
|
drop policy if exists "channels_select_members" on public.comms_channels;
|
|
drop policy if exists "channels_insert_board" on public.comms_channels;
|
|
drop policy if exists "channels_update_board" on public.comms_channels;
|
|
drop policy if exists "channels_delete_board" on public.comms_channels;
|
|
|
|
create policy "channels_select_members" on public.comms_channels
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "channels_insert_board" on public.comms_channels
|
|
for insert with check (public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board']));
|
|
|
|
create policy "channels_update_board" on public.comms_channels
|
|
for update using (public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board']));
|
|
|
|
create policy "channels_delete_board" on public.comms_channels
|
|
for delete using (public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board']));
|
|
|
|
-- =====================================================
|
|
-- COMMS MESSAGES
|
|
-- =====================================================
|
|
drop policy if exists "messages_select_members" on public.comms_messages;
|
|
drop policy if exists "messages_insert_members" on public.comms_messages;
|
|
|
|
create policy "messages_select_members" on public.comms_messages
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "messages_insert_members" on public.comms_messages
|
|
for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
and user_id = auth.uid()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- INTEGRATIONS
|
|
-- =====================================================
|
|
drop policy if exists "integrations_select_admins" on public.integrations;
|
|
drop policy if exists "integrations_modify_admins" on public.integrations;
|
|
|
|
create policy "integrations_select_admins" on public.integrations
|
|
for select using (public.user_has_role_in_space(auth.uid(), space_id, array['admin']));
|
|
|
|
create policy "integrations_modify_admins" on public.integrations
|
|
for all using (public.user_has_role_in_space(auth.uid(), space_id, array['admin']));
|
|
|
|
-- =====================================================
|
|
-- ACTIVITY LOG
|
|
-- =====================================================
|
|
drop policy if exists "activity_select_members" on public.activity_log;
|
|
drop policy if exists "activity_insert_members" on public.activity_log;
|
|
|
|
create policy "activity_select_members" on public.activity_log
|
|
for select using (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
create policy "activity_insert_members" on public.activity_log
|
|
for insert with check (space_id in (select public.get_user_space_ids(auth.uid())));
|
|
|
|
|
|
-- =====================================================
|
|
-- SPACES
|
|
-- =====================================================
|
|
drop policy if exists "spaces_select_members" on public.spaces;
|
|
drop policy if exists "spaces_insert_authenticated" on public.spaces;
|
|
drop policy if exists "spaces_update_admins" on public.spaces;
|
|
|
|
create policy "spaces_select_members" on public.spaces for select using (
|
|
id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "spaces_insert_authenticated" on public.spaces for insert
|
|
with check (auth.uid() is not null);
|
|
|
|
create policy "spaces_update_admins" on public.spaces for update using (
|
|
public.user_has_role_in_space(auth.uid(), id, array['admin'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- SPACE MEMBERS
|
|
-- =====================================================
|
|
drop policy if exists "space_members_select_own" on public.space_members;
|
|
drop policy if exists "space_members_select_same_space" on public.space_members;
|
|
drop policy if exists "space_members_select_own_space" on public.space_members;
|
|
drop policy if exists "space_members_insert_authenticated" on public.space_members;
|
|
drop policy if exists "space_members_update_admins" on public.space_members;
|
|
drop policy if exists "space_members_delete_admins" on public.space_members;
|
|
|
|
-- SELECT: Users can see their own record
|
|
create policy "space_members_select_own" on public.space_members for select using (
|
|
user_id = auth.uid()
|
|
);
|
|
|
|
-- SELECT: Users can see all members in spaces they belong to
|
|
create policy "space_members_select_same_space" on public.space_members for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
-- INSERT: Authenticated users can add themselves to a space
|
|
create policy "space_members_insert_authenticated" on public.space_members for insert
|
|
with check (auth.uid() is not null and user_id = auth.uid());
|
|
|
|
-- UPDATE: Admins can update any member, users can update themselves
|
|
create policy "space_members_update_admins" on public.space_members for update using (
|
|
user_id = auth.uid()
|
|
or public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
-- DELETE: Only admins can remove members
|
|
create policy "space_members_delete_admins" on public.space_members for delete using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- TASKS
|
|
-- =====================================================
|
|
drop policy if exists "tasks_select_members" on public.tasks;
|
|
drop policy if exists "tasks_insert_members" on public.tasks;
|
|
drop policy if exists "tasks_update_members" on public.tasks;
|
|
drop policy if exists "tasks_delete_members" on public.tasks;
|
|
|
|
create policy "tasks_select_members" on public.tasks for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "tasks_insert_members" on public.tasks for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "tasks_update_members" on public.tasks for update using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "tasks_delete_members" on public.tasks for delete using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
-- =====================================================
|
|
-- PROJECTS
|
|
-- =====================================================
|
|
drop policy if exists "projects_select_members" on public.projects;
|
|
drop policy if exists "projects_insert_members" on public.projects;
|
|
drop policy if exists "projects_update_members" on public.projects;
|
|
drop policy if exists "projects_delete_members" on public.projects;
|
|
|
|
create policy "projects_select_members" on public.projects for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "projects_insert_members" on public.projects for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "projects_update_members" on public.projects for update using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "projects_delete_members" on public.projects for delete using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
-- =====================================================
|
|
-- KNOWLEDGE BASE
|
|
-- =====================================================
|
|
drop policy if exists "kb_select_members" on public.knowledge_base;
|
|
drop policy if exists "kb_insert_members" on public.knowledge_base;
|
|
drop policy if exists "kb_update_members" on public.knowledge_base;
|
|
drop policy if exists "kb_delete_board" on public.knowledge_base;
|
|
|
|
create policy "kb_select_members" on public.knowledge_base for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "kb_insert_members" on public.knowledge_base for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "kb_update_members" on public.knowledge_base for update using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "kb_delete_board" on public.knowledge_base for delete using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- SECRETS
|
|
-- =====================================================
|
|
drop policy if exists "secrets_select_admins" on public.secrets;
|
|
drop policy if exists "secrets_modify_admins" on public.secrets;
|
|
|
|
create policy "secrets_select_admins" on public.secrets for select using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board', 'treasurer'])
|
|
);
|
|
|
|
create policy "secrets_modify_admins" on public.secrets for all using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- AREA LEADS
|
|
-- =====================================================
|
|
drop policy if exists "area_leads_select_members" on public.area_leads;
|
|
drop policy if exists "area_leads_modify_board" on public.area_leads;
|
|
|
|
create policy "area_leads_select_members" on public.area_leads for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "area_leads_modify_board" on public.area_leads for all using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- CONTACTS
|
|
-- =====================================================
|
|
drop policy if exists "contacts_select_members" on public.contacts;
|
|
drop policy if exists "contacts_modify_members" on public.contacts;
|
|
|
|
create policy "contacts_select_members" on public.contacts for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "contacts_modify_members" on public.contacts for all using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
-- =====================================================
|
|
-- PAYMENTS
|
|
-- =====================================================
|
|
drop policy if exists "payments_select_treasurer" on public.payments;
|
|
drop policy if exists "payments_modify_treasurer" on public.payments;
|
|
|
|
create policy "payments_select_treasurer" on public.payments for select using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board', 'treasurer'])
|
|
);
|
|
|
|
create policy "payments_modify_treasurer" on public.payments for all using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'treasurer'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- COMMS CHANNELS
|
|
-- =====================================================
|
|
drop policy if exists "channels_select_members" on public.comms_channels;
|
|
drop policy if exists "channels_insert_board" on public.comms_channels;
|
|
drop policy if exists "channels_update_board" on public.comms_channels;
|
|
drop policy if exists "channels_delete_board" on public.comms_channels;
|
|
|
|
create policy "channels_select_members" on public.comms_channels for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "channels_insert_board" on public.comms_channels for insert with check (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board'])
|
|
);
|
|
|
|
create policy "channels_update_board" on public.comms_channels for update using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board'])
|
|
);
|
|
|
|
create policy "channels_delete_board" on public.comms_channels for delete using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin', 'board'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- COMMS MESSAGES
|
|
-- =====================================================
|
|
drop policy if exists "messages_select_members" on public.comms_messages;
|
|
drop policy if exists "messages_insert_members" on public.comms_messages;
|
|
|
|
create policy "messages_select_members" on public.comms_messages for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "messages_insert_members" on public.comms_messages for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
and user_id = auth.uid()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- INTEGRATIONS
|
|
-- =====================================================
|
|
drop policy if exists "integrations_select_admins" on public.integrations;
|
|
drop policy if exists "integrations_modify_admins" on public.integrations;
|
|
|
|
create policy "integrations_select_admins" on public.integrations for select using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
create policy "integrations_modify_admins" on public.integrations for all using (
|
|
public.user_has_role_in_space(auth.uid(), space_id, array['admin'])
|
|
);
|
|
|
|
-- =====================================================
|
|
-- ACTIVITY LOG
|
|
-- =====================================================
|
|
drop policy if exists "activity_select_members" on public.activity_log;
|
|
drop policy if exists "activity_insert_members" on public.activity_log;
|
|
|
|
create policy "activity_select_members" on public.activity_log for select using (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
create policy "activity_insert_members" on public.activity_log for insert with check (
|
|
space_id in (select public.get_user_space_ids(auth.uid()))
|
|
);
|
|
|
|
-- =====================================================
|
|
-- GRANT EXECUTE on helper functions to authenticated users
|
|
-- =====================================================
|
|
grant execute on function public.get_user_space_ids(uuid) to authenticated;
|
|
grant execute on function public.user_has_role_in_space(uuid, uuid, text[]) to authenticated;
|