When to use it
The app stores new user data in Supabase, for example notes, favourites or log entries, and shows it on a screen.
Rules to keep
- The app ships the publishable key, so RLS is the only thing that keeps one user's rows away from another. Every new table enables RLS in the migration that creates it.
- Migrations are append-only: never edit one that has already been applied.
- Every new policy has a pgTAP test that proves allowed access and rejects the rest.
- Supabase is called only from a repository in
data, behind an interface indomain.
Steps
Create
supabase/migrations/<timestamp>_<name>.sqlwith a timestamp after the latest one, shaped likepush_devices:create table public.notes ( id uuid primary key default gen_random_uuid(), user_id uuid not null default auth.uid() references auth.users(id) on delete cascade, body text not null check (char_length(body) between 1 and 2000), created_at timestamptz not null default now() ); create index notes_user_id_idx on public.notes (user_id); alter table public.notes enable row level security; create policy "notes_select_own" on public.notes for select to authenticated using ((select auth.uid()) = user_id); create policy "notes_insert_own" on public.notes for insert to authenticated with check ((select auth.uid()) = user_id); create policy "notes_delete_own" on public.notes for delete to authenticated using ((select auth.uid()) = user_id); revoke all on public.notes from anon;Add only the operations the app needs: here users cannot edit a note.
If a write needs rules that a policy cannot express, such as moving ownership or checking other tables, revoke direct writes and expose a
security definerfunction withset search_path = ''and a check onauth.uid(), asregister_push_devicedoes.Add
supabase/tests/database/<name>_rls.test.sqlfollowingpush_devices_rls.test.sql: two users inauth.users,set local role authenticated,request.jwt.claim.subset to each user in turn. Check that a user sees and changes only their own rows, that writing a row for someone else fails with42501, and thatanongets nothing.Run
supabase db resetandsupabase test dblocally.In
lib/features/<feature>/domaindefine the model withfromJsonand anabstract interface classfor the repository, likeProfileRepository. Indata, implement it withSupabaseClient, likeSupabaseProfileRepository: without a session, throw instead of querying.In
application, expose the repository with aProviderthat readssupabaseClientProvider, likeprofileRepositoryProvider, and add the providers the screen watches. To build the screen, follow the playbook "Add a screen with a route and state".Add Dart tests for the model mapping and for the providers with a fake repository; the tests do not use the network.
Add the table, its owner and its rule to the table in
DATABASE.md.
Done when
- the verification commands pass;
supabase test dbpasses, including the new test;- the table has RLS enabled and only the policies the app needs;
DATABASE.mdlists the table.
