Replace the blanket select/insert/update/delete grant to anon/authenticated/ service_role with privilege-scoped grants: anon/authenticated get select-only on the public-read tables plus insert-only on bookings/inquiries; full CRUD is reserved for authenticated (narrowed to the admin UID by RLS) and service_role. Table grants are now a real second layer of defense instead of mirroring RLS. Updates two pgTAP assertions whose blocking mechanism moved from RLS-filtered-empty to grant-level permission-denied, and adds a new assertion proving anon cannot UPDATE bookings. Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
64 lines
2.3 KiB
PL/PgSQL
64 lines
2.3 KiB
PL/PgSQL
-- Base table grants for the API roles. The Supabase CLI's current default
|
|
-- (auto_expose_new_tables unset) no longer auto-grants table privileges to
|
|
-- anon/authenticated/service_role, so without these GRANTs the RLS policies
|
|
-- from the initial schema migration are unreachable (every request fails at
|
|
-- the privilege check before RLS is even evaluated). This is the documented
|
|
-- "explicit grants + RLS" pattern Supabase recommends going forward.
|
|
--
|
|
-- Unlike the previous blanket grant, these privileges are scoped to match
|
|
-- what each role's RLS policies actually permit, so table-level GRANTs are a
|
|
-- real second layer of defense rather than a no-op rubber stamp on top of
|
|
-- RLS: anon/authenticated can only select the public-read tables and insert
|
|
-- into bookings/inquiries; only authenticated (narrowed further by RLS's
|
|
-- admin-only policies to the single admin UID) gets full read/write on
|
|
-- everything. service_role still needs an explicit full grant here: table
|
|
-- privileges and RLS are two independent checks in Postgres — service_role's
|
|
-- BYPASSRLS role attribute only skips the RLS policy check, it does not
|
|
-- substitute for the base GRANT privilege check, so without this it cannot
|
|
-- touch these tables at all (e.g. the test suite's service_role seed step).
|
|
grant select on
|
|
public.products,
|
|
public.services,
|
|
public.schedule_template,
|
|
public.schedule_exceptions,
|
|
public.booked_slots
|
|
to anon, authenticated;
|
|
|
|
grant insert on
|
|
public.bookings,
|
|
public.inquiries
|
|
to anon, authenticated;
|
|
|
|
grant select, insert, update, delete on
|
|
public.products,
|
|
public.services,
|
|
public.schedule_template,
|
|
public.schedule_exceptions,
|
|
public.bookings,
|
|
public.booked_slots,
|
|
public.inquiries
|
|
to authenticated, service_role;
|
|
|
|
-- Keeps the public, PII-free booked_slots table in sync whenever a booking
|
|
-- is created. security definer + explicit search_path per Supabase's
|
|
-- documented safe pattern for definer functions.
|
|
|
|
create or replace function public.sync_booked_slot()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
begin
|
|
insert into public.booked_slots (date, time)
|
|
values (new.date, new.time)
|
|
on conflict do nothing;
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
create trigger booking_created_sync_slot
|
|
after insert on public.bookings
|
|
for each row
|
|
execute function public.sync_booked_slot();
|