From acc4b02a207f1200a52b382610227d0a6351e269 Mon Sep 17 00:00:00 2001 From: belviskhoremk Date: Wed, 12 Aug 2026 10:44:44 +0200 Subject: [PATCH] feat: add public-read, admin-write RLS policies for salon-images bucket Adds pgTAP tests and migration to create the salon-images storage bucket with RLS policies allowing public read access and admin-only write access. Tests verify anon users can read but not upload, and admin can upload. --- ...12084115_salon_images_storage_policies.sql | 17 +++++++++++++ supabase/tests/database/rls.test.sql | 24 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 supabase/migrations/20260812084115_salon_images_storage_policies.sql diff --git a/supabase/migrations/20260812084115_salon_images_storage_policies.sql b/supabase/migrations/20260812084115_salon_images_storage_policies.sql new file mode 100644 index 0000000..a36afd1 --- /dev/null +++ b/supabase/migrations/20260812084115_salon_images_storage_policies.sql @@ -0,0 +1,17 @@ +-- The salon-images bucket was created manually in the live project during +-- Phase 2A's Task 8 (private, no policies — reserved for this task). Local +-- dev stacks don't have it at all (it wasn't created via migration), so +-- this creates it if missing and makes it public either way: product/ +-- service photos are marketing images meant to be publicly visible once the +-- public site (Sub-project C) reads live data. +insert into storage.buckets (id, name, public) +values ('salon-images', 'salon-images', true) +on conflict (id) do update set public = true; + +create policy "salon_images_public_read" on storage.objects + for select using (bucket_id = 'salon-images'); + +create policy "salon_images_admin_write" on storage.objects + for all + using (bucket_id = 'salon-images' and (select auth.uid()) = '21c90224-e0b2-412e-a431-8e7f097c1cef'::uuid) + with check (bucket_id = 'salon-images' and (select auth.uid()) = '21c90224-e0b2-412e-a431-8e7f097c1cef'::uuid); diff --git a/supabase/tests/database/rls.test.sql b/supabase/tests/database/rls.test.sql index 6a5436d..9866149 100644 --- a/supabase/tests/database/rls.test.sql +++ b/supabase/tests/database/rls.test.sql @@ -1,5 +1,5 @@ begin; -select plan(12); +select plan(15); -- Seed reference data as service_role (bypasses RLS) set local role service_role; @@ -101,5 +101,27 @@ select lives_ok( 'admin can insert into services' ); +-- salon-images bucket: anyone can read, only admin can write +set local role anon; +set local request.jwt.claim.sub = '00000000-0000-0000-0000-000000000000'; +select lives_ok( + $$ select * from storage.objects where bucket_id = 'salon-images' $$, + 'anon can read salon-images objects' +); + +select throws_ok( + $$ insert into storage.objects (bucket_id, name) values ('salon-images', 'test.jpg') $$, + '42501', + null, + 'anon cannot upload to salon-images' +); + +set local role authenticated; +set local request.jwt.claim.sub = '21c90224-e0b2-412e-a431-8e7f097c1cef'; +select lives_ok( + $$ insert into storage.objects (bucket_id, name) values ('salon-images', 'admin-test.jpg') $$, + 'admin can upload to salon-images' +); + select * from finish(); rollback;