TL;DR
raw_user_meta_data obsahuje to, čo prehliadač odovzdal do supabase.auth.signUp(). V jednej produkčnej aplikácii z neho trigger handle_new_user čítal rolu nového používateľa, takže sa ktokoľvek mohol zaregistrovať s id roly Owner a prevziať účet. Oprava: rola pochádza z tabuľky, ktorú zapisuje iba server, vyplnenej skôr, než sa pozvánka odošle.
Stalo sa to v sledovaní času pre audítorskú firmu, aplikácii na Supabase, kde každé pravidlo prístupu žije v databáze. Chyba sedela v triggeri, ktorý vytvára profil pre každého nového používateľa.
Čo trigger robil?
Zjednodušene vyzeral takto:
create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = ''
as $$
declare
_role_id uuid := (new.raw_user_meta_data ->> 'role_id')::uuid;
begin
insert into public.profiles (id, full_name, role_id)
values (new.id, new.raw_user_meta_data ->> 'full_name', _role_id);
return new;
end;
$$;
Pozvánkový tok sem vkladal správne role_id, takže pri bežnom použití to fungovalo. Lenže raw_user_meta_data sú options.data registrácie a čo v nich bude, rozhoduje prehliadač:
await supabase.auth.signUp({
email: 'me@example.com',
password: '...',
options: { data: { role_id: '<owner role uuid>' } },
})
Kontrola oprávnení vracala pre vlastníkov true skôr, než sa pozrela na čokoľvek iné, takže toto jediné pole dávalo plnú kontrolu nad účtom. Samotný sprievodca Users od Supabase hovorí, že metadáta používateľa sa nesmú používať v politikách RLS ani v autorizačnej logike, pretože ich používateľ môže upraviť bez akejkoľvek kontroly.
Čo to opravilo?
Rola sa odteraz berie zo súkromnej tabuľky, na ktorú klient nesiahne:
create table private.pending_invites (
email text primary key,
role_id uuid,
invited_by uuid,
created_at timestamptz default now()
);
revoke all on table private.pending_invites from anon, authenticated;
Funkcia pozvánky beží na serveri a kontroluje oprávnenie pozývajúceho. Najprv zapíše čakajúcu pozvánku a až potom vytvorí odkaz. Trigger vyhľadá rolu podľa e-mailu a riadok zmaže. Meno sa stále berie z metadát, čo je v poriadku: sú to údaje na zobrazenie.
Na poradí záleží. Pozvánka vytvorí používateľa okamžite: sprievodca Users od Supabase uvádza, že pozvanie nového e-mailu vytvorí nepotvrdeného používateľa. Trigger sa spustí v tom okamihu, takže čakajúci riadok už musí existovať.
Ako skontrolovať vlastnú aplikáciu?
Otvorte svoju funkciu handle_new_user alebo akýkoľvek trigger na auth.users a vyhľadajte raw_user_meta_data. Meno a avatar odtiaľ sú v poriadku. Rola, firma, plán, tenant alebo čokoľvek iné, čo udeľuje prístup, musí pochádzať z tabuľky alebo z raw_app_meta_data, ktoré používateľ meniť nemôže.
Potom to vyskúšajte: zaregistrujte sa cez API s role_id alebo role: 'admin' v options.data. Nový účet nesmie dostať nič navyše.
Takáto kontrola je prvým krokom mojej metódy testovania RLS. Pri multi-tenant aplikáciách sa pozrite aj na čo RLS prehliadne u používateľa v dvoch org. Ďalšie kontroly, ktoré spúšťam pri každej aplikácii na Supabase, sú v mojom checkliste bezpečnosti aplikácie z Lovable. Prvé, na čo sa v dashboarde pozerám, je ktoré schémy Data API vystavuje. Kontrola zabezpečenia Supabase pokrýva roly, granty, politiky a nastavenia autentifikácie podľa 45 pravidiel OWASP Top 10:2025, nie to, čo trigger kopíruje z metadát pri registrácii, takže táto kontrola zostáva ručná.