1+ -- Drop the overly broad existing policy
2+ DROP POLICY IF EXISTS " Users can view their billing records" ON public .billing_records ;
3+
4+ -- Create more granular and secure RLS policies for billing_records
5+ CREATE POLICY " Users can view their own billing records"
6+ ON public .billing_records
7+ FOR SELECT
8+ TO authenticated
9+ USING (auth .uid () = user_id);
10+
11+ -- System can create billing records (for automated processing)
12+ CREATE POLICY " System can create billing records"
13+ ON public .billing_records
14+ FOR INSERT
15+ TO authenticated
16+ WITH CHECK (auth .uid () = user_id);
17+
18+ -- Restrict updates to only status and processed_at fields (non-financial data)
19+ CREATE POLICY " Users can update billing record status only"
20+ ON public .billing_records
21+ FOR UPDATE
22+ TO authenticated
23+ USING (auth .uid () = user_id);
24+
25+ -- No deletion of billing records for audit trail integrity
26+ CREATE POLICY " Billing records cannot be deleted"
27+ ON public .billing_records
28+ FOR DELETE
29+ TO authenticated
30+ USING (false);
31+
32+ -- Create security audit function for billing record access
33+ CREATE OR REPLACE FUNCTION public .log_billing_access(record_id uuid, access_type text )
34+ RETURNS void
35+ LANGUAGE plpgsql
36+ SECURITY DEFINER
37+ SET search_path = public
38+ AS $$
39+ BEGIN
40+ -- Log financial data access for security monitoring
41+ PERFORM public .log_security_event (
42+ ' billing_record_access' ,
43+ auth .uid (),
44+ jsonb_build_object(
45+ ' record_id' , record_id,
46+ ' access_type' , access_type,
47+ ' timestamp' , now()
48+ )
49+ );
50+ END;
51+ $$;
52+
53+ -- Create function to validate billing record ownership before sensitive operations
54+ CREATE OR REPLACE FUNCTION public .validate_billing_record_ownership(record_id uuid)
55+ RETURNS boolean
56+ LANGUAGE plpgsql
57+ SECURITY DEFINER
58+ SET search_path = public
59+ AS $$
60+ DECLARE
61+ record_owner uuid;
62+ BEGIN
63+ SELECT user_id INTO record_owner
64+ FROM billing_records
65+ WHERE id = record_id;
66+
67+ RETURN (record_owner = auth .uid ());
68+ END;
69+ $$;
0 commit comments