Alle PlaybooksNext.js + Supabase

Eine Tabelle mit RLS hinzufügen

Neue Daten eines Nutzers in Supabase mit Row Level Security speichern, damit jeder Nutzer nur seine eigenen Zeilen liest und ändert.

Wann du es brauchst

Das Produkt braucht eigene Daten, die einem Nutzer gehören, zum Beispiel Notizen, Projekte oder gespeicherte Einträge.

Regeln

  • Migrationen sind append-only: Lege eine neue Datei in supabase/migrations/ an und ändere nie eine bereits angewendete.
  • RLS ist die Autorisierungsgrenze. Jede neue Tabelle in public aktiviert Row Level Security in derselben Migration, die sie anlegt.
  • Der Eigentümer kommt aus der Sitzung (auth.uid()), nie aus dem Request-Body.
  • Servercode nutzt den Client pro Request aus src/lib/supabase/server.ts, der als angemeldeter Nutzer arbeitet. SUPABASE_SERVICE_ROLE_KEY umgeht RLS: Verwende ihn nicht für Nutzerdaten und importiere ihn nie in Client-Code.

Schritte

  1. Lege supabase/migrations/<timestamp>_<name>.sql mit einem Zeitstempel nach der letzten Migration an. Für eine Tabelle notes:

    create table public.notes (
      id uuid primary key default gen_random_uuid(),
      owner_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_owner_id_idx on public.notes (owner_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()) = owner_id);
    create policy "notes_insert_own" on public.notes for insert
      to authenticated with check ((select auth.uid()) = owner_id);
    create policy "notes_update_own" on public.notes for update
      to authenticated using ((select auth.uid()) = owner_id)
      with check ((select auth.uid()) = owner_id);
    create policy "notes_delete_own" on public.notes for delete
      to authenticated using ((select auth.uid()) = owner_id);
    
    revoke all on public.notes from anon;
    
  2. Schreibe nur die Policies, die das Produkt braucht: Eine Tabelle, die Nutzer lesen, aber nie ändern, hat nur eine select-Policy. Schreibe (select auth.uid()) wie in profiles, damit Postgres den Ausdruck einmal pro Abfrage statt einmal pro Zeile auswertet.

  3. Wenn Nutzer bestimmte Spalten nicht ändern dürfen, folge profiles: revoke update auf die Tabelle, dann grant update (<spalten>) nur auf die änderbaren.

  4. Lies und schreibe aus einer Server Component oder einem Route Handler mit createClient() aus src/lib/supabase/server.ts. Rufe wie in src/app/api/me/route.ts zuerst auth.getUser() auf und antworte ohne Nutzer mit 401. Filtere nicht nach einer owner_id aus dem Request: RLS begrenzt die Zeilen bereits.

  5. Wende die Migration an (supabase db push oder der SQL-Editor des Projekts) und prüfe die Isolation mit zwei Konten: Nutzer A legt eine Zeile an, Nutzer B darf sie weder sehen noch ändern noch löschen, und eine anonyme Anfrage erhält keine Zeilen.

  6. Lege die Validierung der Eingaben in ein reines Modul unter src/lib/ und teste es in test/ mit Vitest; die Tests nutzen kein Netzwerk.

  7. Beschreibe die Tabelle und ihre Regeln in DATABASE.md.

Fertig, wenn

  • die Prüfbefehle erfolgreich sind;
  • die neue Tabelle RLS aktiviert hat und eine Policy pro erlaubter Operation;
  • die Prüfung mit zwei Konten aus Schritt 5 erfolgreich war;
  • DATABASE.md die Tabelle beschreibt.