The Sabbels Handmade database implements PostgreSQL Row Level Security (RLS) on all tables to enforce fine-grained access control. Policies differentiate between anonymous users, authenticated clients, and administrators.
All tables in the public schema have RLS enabled. Without appropriate policies, users cannot access any data.
Utility function to check if a user has admin privileges.
create or replace function public.is_admin(uid uuid)returns booleanlanguage sqlstablesecurity definerset search_path = public as $$ select exists ( select 1 from public.users u where u.id = uid and u.role = 'admin' );$$;grant execute on function public.is_admin(uuid) to anon, authenticated;
Security Definer: Executes with owner privileges to read user roles
Stable: Allows query optimization (result doesn’t change within transaction)
Granted to: Both anon and authenticated roles can execute
create policy "Users: select own or admin" on public.users for select to authenticated using ( id = auth.uid() or public.is_admin(auth.uid()) );create policy "Users: delete admin only" on public.users for delete to authenticated using ( public.is_admin(auth.uid()) );
create policy "Colors: public read" on public.colors for select to anon, authenticated using (true);create policy "Colors: admin write" on public.colors for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "Items: public read" on public.items for select to anon, authenticated using (true);create policy "Items: admin write" on public.items for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
The public read policy allows browsing all items regardless of status. Filter by status = 'active' in your application queries to show only published products to customers.
create policy "ItemColors: public read" on public.item_colors for select to anon, authenticated using (true);create policy "ItemColors: admin write" on public.item_colors for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "ItemImages: public read" on public.item_images for select to anon, authenticated using (true);create policy "ItemImages: admin write" on public.item_images for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "Variants: public read" on public.item_variants for select to anon, authenticated using (true);create policy "Variants: admin write" on public.item_variants for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "Ratings: public read" on public.item_ratings for select to anon, authenticated using (true);create policy "Ratings: user manage own" on public.item_ratings for all to authenticated using ( auth.uid() = user_id ) with check ( auth.uid() = user_id );
Users can only manage their own reviews. The unique constraint (item_id, user_id) ensures one review per user per item.
create policy "Orders: read own or admin" on public.orders for select to authenticated using ( user_id = auth.uid() or public.is_admin(auth.uid()) );create policy "Orders: create own or admin" on public.orders for insert to authenticated with check ( user_id = auth.uid() or public.is_admin(auth.uid()) );create policy "Orders: admin update" on public.orders for update to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "OrderItems: read own via order" on public.order_items for select to authenticated using ( exists ( select 1 from public.orders o where o.id = order_items.order_id and (o.user_id = auth.uid() or public.is_admin(auth.uid())) ) );create policy "OrderItems: create own via order" on public.order_items for insert to authenticated with check ( exists ( select 1 from public.orders o where o.id = order_id and (o.user_id = auth.uid() or public.is_admin(auth.uid())) ) );
Access is controlled via the parent orders table using subquery checks. Users can only see/add items to orders they own.
create policy "Payments: admin read" on public.payments for select to authenticated using ( public.is_admin(auth.uid()) );create policy "Payments: admin write" on public.payments for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
Payment records are admin-only. Regular users cannot view payment details, even for their own orders. This protects sensitive financial data.
create policy "StripeEvents: admin read" on public.stripe_events for select to authenticated using ( public.is_admin(auth.uid()) );create policy "StripeEvents: admin write" on public.stripe_events for all to authenticated using ( public.is_admin(auth.uid()) ) with check ( public.is_admin(auth.uid()) );
create policy "customer_photos: public read visible" on public.customer_photos for select to anon, authenticated using ( is_visible = true );create policy "customer_photos: admin read all" on public.customer_photos for select to authenticated using ( public.is_admin(auth.uid()) );create policy "customer_photos: admin write" on public.customer_photos for insert to authenticated with check ( public.is_admin(auth.uid()) );
The public can only see photos where is_visible = true. Admins have two separate SELECT policies: one for public photos and one for all photos.
create policy "Storage: read product-images" on storage.objects for select to anon, authenticated using ( bucket_id = 'product-images' );create policy "Storage: insert product-images (admin)" on storage.objects for insert to authenticated with check ( bucket_id = 'product-images' and public.is_admin(auth.uid()) );create policy "Storage: update product-images (admin)" on storage.objects for update to authenticated using ( bucket_id = 'product-images' and public.is_admin(auth.uid()) ) with check ( bucket_id = 'product-images' and public.is_admin(auth.uid()) );create policy "Storage: delete product-images (admin)" on storage.objects for delete to authenticated using ( bucket_id = 'product-images' and public.is_admin(auth.uid()) );
To test policies, use SET ROLE and SET request.jwt.claims:
-- Test as anonymous userSET ROLE anon;SELECT * FROM items; -- Should workSELECT * FROM payments; -- Should return empty-- Test as authenticated clientSET ROLE authenticated;SET request.jwt.claim.sub TO '<user-uuid>';SELECT * FROM orders WHERE user_id = auth.uid(); -- Should see own orders-- Test as adminSET request.jwt.claim.sub TO '<admin-uuid>';SELECT * FROM payments; -- Should see all payments
Always use parameterized queries and validate input server-side. RLS prevents unauthorized data access but doesn’t protect against SQL injection or business logic errors.