-- ============================================================ -- 1. STRIPE & FORTE: Remove homeowner direct SELECT, add RPC -- ============================================================ DROP POLICY IF EXISTS "Homeowners can read own association stripe mappings" ON public.stripe_account_mappings; DROP POLICY IF EXISTS "Users can view own association forte mappings" ON public.forte_account_mappings; -- RPC that returns only non-sensitive gateway fields CREATE OR REPLACE FUNCTION public.get_association_payment_gateways(p_association_id uuid) RETURNS jsonb LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$ SELECT jsonb_build_object( 'stripe', ( SELECT jsonb_build_object( 'id', id, 'association_id', association_id, 'stripe_account_id', stripe_account_id, 'stripe_public_key', stripe_public_key, 'is_active', is_active, 'label', label ) FROM public.stripe_account_mappings WHERE association_id = p_association_id AND is_active = true LIMIT 1 ), 'forte', ( SELECT jsonb_build_object( 'id', id, 'association_id', association_id, 'organization_id', organization_id, 'location_id', location_id, 'environment', environment, 'is_active', is_active, 'label', label, 'pass_processing_fee', pass_processing_fee, 'processing_fee_percent', processing_fee_percent, 'processing_fee_fixed_cents', processing_fee_fixed_cents ) FROM public.forte_account_mappings WHERE association_id = p_association_id AND is_active = true LIMIT 1 ) ); $$; -- ============================================================ -- 2. PROFILES: Scope to own profile + same association members -- ============================================================ DROP POLICY IF EXISTS "Users can view all profiles" ON public.profiles; CREATE POLICY "Users can view own and association profiles" ON public.profiles FOR SELECT TO authenticated USING ( user_id = auth.uid() OR has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR user_id IN ( SELECT o.user_id FROM public.owners o WHERE o.association_id IN (SELECT get_user_association_ids()) AND o.user_id IS NOT NULL ) OR user_id IN ( SELECT bm.user_id FROM public.board_members bm WHERE bm.association_id IN (SELECT get_user_association_ids()) AND bm.user_id IS NOT NULL ) ); -- ============================================================ -- 3. BANK ACCOUNTS: Add association scoping for staff -- ============================================================ DROP POLICY IF EXISTS "Staff full access on bank_accounts" ON public.bank_accounts; CREATE POLICY "Staff full access on own association bank_accounts" ON public.bank_accounts FOR ALL TO authenticated USING ( (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role)) AND association_id IN (SELECT get_user_association_ids()) ) WITH CHECK ( (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role)) AND association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- 4. BLANKET USING(true) POLICIES → Association-scoped -- ============================================================ -- Tables with direct association_id DROP POLICY IF EXISTS "Authenticated users can view annual meetings" ON public.annual_meetings; CREATE POLICY "Users can view own association annual meetings" ON public.annual_meetings FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view billable_expenses" ON public.billable_expenses; CREATE POLICY "Users can view own association billable expenses" ON public.billable_expenses FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view journal_entries" ON public.journal_entries; CREATE POLICY "Users can view own association journal entries" ON public.journal_entries FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view parking records" ON public.parking_records; CREATE POLICY "Users can view own association parking records" ON public.parking_records FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view projects" ON public.projects; CREATE POLICY "Users can view own association projects" ON public.projects FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view timeline events" ON public.unit_timeline_events; CREATE POLICY "Users can view own association timeline events" ON public.unit_timeline_events FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Anyone can verify proofs" ON public.document_validation_proofs; CREATE POLICY "Users can view own association proofs" ON public.document_validation_proofs FOR SELECT TO authenticated USING (association_id IN (SELECT get_user_association_ids())); DROP POLICY IF EXISTS "Authenticated users can view active board resources" ON public.board_resources; CREATE POLICY "Users can view own association board resources" ON public.board_resources FOR SELECT TO authenticated USING (is_active = true AND association_id IN (SELECT get_user_association_ids())); -- Tables scoped via parent join (application_id, bill_id, etc.) DROP POLICY IF EXISTS "Authenticated users can view arc_application_comments" ON public.arc_application_comments; CREATE POLICY "Users can view own association arc comments" ON public.arc_application_comments FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.arc_applications a WHERE a.id = arc_application_comments.application_id AND a.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can view arc_application_votes" ON public.arc_application_votes; CREATE POLICY "Users can view own association arc votes" ON public.arc_application_votes FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.arc_applications a WHERE a.id = arc_application_votes.application_id AND a.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can read bill comments" ON public.bill_comments; CREATE POLICY "Users can view own association bill comments" ON public.bill_comments FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.bills b WHERE b.id = bill_comments.bill_id AND b.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can view vote responses" ON public.board_vote_responses; CREATE POLICY "Users can view own association vote responses" ON public.board_vote_responses FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.board_votes bv WHERE bv.id = board_vote_responses.board_vote_id AND bv.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can read project comments" ON public.project_comments; CREATE POLICY "Users can view own association project comments" ON public.project_comments FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.projects p WHERE p.id = project_comments.project_id AND p.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can read project files" ON public.project_files; CREATE POLICY "Users can view own association project files" ON public.project_files FOR SELECT TO authenticated USING (EXISTS ( SELECT 1 FROM public.projects p WHERE p.id = project_files.project_id AND p.association_id IN (SELECT get_user_association_ids()) )); DROP POLICY IF EXISTS "Authenticated users can view status_update_comments" ON public.status_update_comments; CREATE POLICY "Staff can view status update comments" ON public.status_update_comments FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); DROP POLICY IF EXISTS "Authenticated users can view status_update_votes" ON public.status_update_votes; CREATE POLICY "Staff can view status update votes" ON public.status_update_votes FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); DROP POLICY IF EXISTS "Authenticated users can view entity_comments" ON public.entity_comments; CREATE POLICY "Staff can view entity comments" ON public.entity_comments FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); DROP POLICY IF EXISTS "Authenticated users can view entity_votes" ON public.entity_votes; CREATE POLICY "Staff can view entity votes" ON public.entity_votes FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); -- Company settings & role permissions: restrict to staff DROP POLICY IF EXISTS "Authenticated users can view company_settings" ON public.company_settings; CREATE POLICY "Staff can view company settings" ON public.company_settings FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); DROP POLICY IF EXISTS "Authenticated can read role_permissions" ON public.role_permissions; CREATE POLICY "Staff can read role permissions" ON public.role_permissions FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ); -- Shared links: restrict to creator or staff DROP POLICY IF EXISTS "Authenticated can read shared links" ON public.shared_links; CREATE POLICY "Users can read own or staff shared links" ON public.shared_links FOR SELECT TO authenticated USING ( created_by = auth.uid() OR has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) );