-- Create SECURITY DEFINER function to safely check MFA enrollment CREATE OR REPLACE FUNCTION public.has_mfa_enabled(_user_id uuid) RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER SET search_path = auth, public AS $$ SELECT EXISTS ( SELECT 1 FROM auth.mfa_factors WHERE user_id = _user_id AND status = 'verified' ); $$; GRANT EXECUTE ON FUNCTION public.has_mfa_enabled(uuid) TO authenticated; -- Drop all existing policies on user_roles DROP POLICY IF EXISTS "Users can view their own roles" ON public.user_roles; DROP POLICY IF EXISTS "Moderators can manage roles" ON public.user_roles; DROP POLICY IF EXISTS "Admins can assign moderator roles" ON public.user_roles; DROP POLICY IF EXISTS "Users can delete their own user role" ON public.user_roles; DROP POLICY IF EXISTS "Users can insert their own roles" ON public.user_roles; -- Recreate policies using has_mfa_enabled() function CREATE POLICY "Users can view their own roles" ON public.user_roles FOR SELECT TO authenticated USING (auth.uid() = user_id); CREATE POLICY "Moderators can manage roles" ON public.user_roles FOR ALL TO authenticated USING ( is_moderator(auth.uid()) AND (NOT has_mfa_enabled(auth.uid()) OR has_aal2()) ) WITH CHECK ( is_moderator(auth.uid()) AND (NOT has_mfa_enabled(auth.uid()) OR has_aal2()) ); CREATE POLICY "Users can delete their own user role" ON public.user_roles FOR DELETE TO authenticated USING (auth.uid() = user_id AND role = 'user'); GRANT SELECT ON public.user_roles TO authenticated;