From 28f2285a0eaa0c196bab414220da1e34a4091596 Mon Sep 17 00:00:00 2001 From: DanielS Date: Sat, 4 Jul 2026 17:36:51 +0200 Subject: [PATCH] security: fix private storage buckets, api access, and admin RLS bypasses --- shop/lib/supabase/proxy.ts | 2 +- ...704174000_secure_storage_and_admin_rls.sql | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 shop/supabase/migrations/20260704174000_secure_storage_and_admin_rls.sql diff --git a/shop/lib/supabase/proxy.ts b/shop/lib/supabase/proxy.ts index 577e23a..50f87d3 100644 --- a/shop/lib/supabase/proxy.ts +++ b/shop/lib/supabase/proxy.ts @@ -67,7 +67,7 @@ export async function updateSession(request: NextRequest) { } // Admin protection - if (request.nextUrl.pathname.startsWith("/admin")) { + if (request.nextUrl.pathname.startsWith("/admin") || request.nextUrl.pathname.startsWith("/api/admin")) { if (!user || !user.sub) { const url = request.nextUrl.clone(); url.pathname = "/auth/login"; diff --git a/shop/supabase/migrations/20260704174000_secure_storage_and_admin_rls.sql b/shop/supabase/migrations/20260704174000_secure_storage_and_admin_rls.sql new file mode 100644 index 0000000..f8980a2 --- /dev/null +++ b/shop/supabase/migrations/20260704174000_secure_storage_and_admin_rls.sql @@ -0,0 +1,63 @@ +-- Migration: Secure invoices bucket and allow admin access in RLS policies + +-- 1. Secure storage bucket and set public to false +UPDATE storage.buckets SET public = false WHERE id = 'invoices'; + +-- 2. Update storage policy for invoice access to check authenticated company assignment or admin +DROP POLICY IF EXISTS "Public Access" ON storage.objects; +CREATE POLICY "Secure Invoice Access" ON storage.objects FOR SELECT USING ( + bucket_id = 'invoices' + AND ( + -- Admins can read all invoices + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR + -- Partner users can only read invoices belonging to their company + -- Filename format: ab_[order_id].pdf + (SELECT company_id FROM public.users WHERE id = auth.uid()) = ( + SELECT company_id FROM public.orders + WHERE id = CAST(substring(name from 'ab_(.+)\.pdf') AS UUID) + ) + ) +); + +-- 3. Recreate end_customers RLS policies to allow admin bypass +DROP POLICY IF EXISTS "Partner sehen nur eigene Endkunden" ON public.end_customers; +CREATE POLICY "Partner sehen nur eigene Endkunden" ON public.end_customers + FOR ALL + USING ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + WITH CHECK ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ); + +-- 4. Recreate licenses SELECT RLS policy to allow admin bypass +DROP POLICY IF EXISTS "Partner sehen Lizenzen eigener Kunden" ON public.licenses; +CREATE POLICY "Partner sehen Lizenzen eigener Kunden" ON public.licenses + FOR SELECT + USING ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR EXISTS ( + SELECT 1 FROM public.end_customers c + WHERE c.id = licenses.end_customer_id + AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + ); + +-- 5. Recreate licenses INSERT RLS policy to allow admin bypass +DROP POLICY IF EXISTS "Partner erstellen Lizenzen eigener Kunden" ON public.licenses; +CREATE POLICY "Partner erstellen Lizenzen eigener Kunden" ON public.licenses + FOR INSERT + WITH CHECK ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR EXISTS ( + SELECT 1 FROM public.end_customers c + WHERE c.id = licenses.end_customer_id + AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + ); + +-- Reload Schema Cache +NOTIFY pgrst, 'reload schema';