Files
hackerspace.sh/scripts/006_comprehensive_rls_fix.sql
T
2026-03-11 04:44:48 +00:00

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;