-- Migration: Secure User Roles from Self-Escalation -- Purpose: Prevent users from updating their own roles to 'admin' using RLS / Triggers, while granting full access to service_role. -- Create helper function to check admin role bypassing RLS (SECURITY DEFINER) CREATE OR REPLACE FUNCTION public.is_admin(user_id UUID) RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM public.users WHERE id = user_id AND role = 'admin' ); END; $$ LANGUAGE plpgsql SECURITY DEFINER; ALTER TABLE public.users ENABLE ROW LEVEL SECURITY; -- Grant privileges to PostgREST roles GRANT ALL ON TABLE public.users TO service_role; GRANT ALL ON TABLE public.users TO authenticated; GRANT SELECT ON TABLE public.users TO anon; GRANT ALL ON TABLE public.users TO postgres; -- Service role policy DROP POLICY IF EXISTS "service_role_all_users" ON public.users; CREATE POLICY "service_role_all_users" ON public.users FOR ALL TO service_role USING (true) WITH CHECK (true); -- Policy to allow users to view their own records DROP POLICY IF EXISTS select_own_user ON public.users; CREATE POLICY select_own_user ON public.users FOR SELECT TO authenticated USING (auth.uid() = id); -- Policy to allow admins to view all users DROP POLICY IF EXISTS select_all_users_for_admin ON public.users; CREATE POLICY select_all_users_for_admin ON public.users FOR SELECT TO authenticated USING (public.is_admin(auth.uid())); -- Policy to allow admins to update users DROP POLICY IF EXISTS update_users_for_admin ON public.users; CREATE POLICY update_users_for_admin ON public.users FOR UPDATE TO authenticated USING (public.is_admin(auth.uid())) WITH CHECK (public.is_admin(auth.uid())); -- Trigger to prevent any role updates to 'admin' from unauthorized users CREATE OR REPLACE FUNCTION check_user_role_escalation() RETURNS TRIGGER AS $$ BEGIN -- Allow changes to the role column if executed by administrative DB roles or service_role JWT IF (TG_OP = 'UPDATE' AND OLD.role IS DISTINCT FROM NEW.role) OR (TG_OP = 'INSERT') THEN IF current_setting('request.jwt.claim.role', true) <> 'service_role' AND current_setting('role', true) NOT IN ('service_role', 'supabase_admin', 'postgres') THEN -- Partners cannot upgrade themselves or others to admin IF NEW.role = 'admin' THEN RAISE EXCEPTION 'Unberechtigtes Rollen-Upgrade verweigert.'; END IF; END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; DROP TRIGGER IF EXISTS enforce_role_protection ON public.users; CREATE TRIGGER enforce_role_protection BEFORE INSERT OR UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION check_user_role_escalation();