-- Fix RLS Policies for Employees Table (V5 - Architectural Fix) -- This version moves user context to the 'users' table to break recursion. -- 1. Extend public.users table (Idempotent) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='role_name') THEN ALTER TABLE public.users ADD COLUMN role_name TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='company_id') THEN ALTER TABLE public.users ADD COLUMN company_id UUID; END IF; END $$; -- 2. Create Sync Function CREATE OR REPLACE FUNCTION public.sync_user_security_context() RETURNS TRIGGER AS $$ BEGIN -- Update the users table when an employee is linked to a user_id IF NEW.user_id IS NOT NULL THEN UPDATE public.users SET role_name = (SELECT name FROM public.roles WHERE id = NEW.role_id), company_id = NEW.company_id WHERE id = NEW.user_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 3. Create Sync Trigger DROP TRIGGER IF EXISTS on_employee_security_change ON public.employees; CREATE TRIGGER on_employee_security_change AFTER INSERT OR UPDATE OF role_id, company_id, user_id ON public.employees FOR EACH ROW EXECUTE FUNCTION public.sync_user_security_context(); -- 4. Initial Sync (Seed existing users) UPDATE public.users u SET role_name = r.name, company_id = e.company_id FROM public.employees e JOIN public.roles r ON e.role_id = r.id WHERE e.user_id = u.id; -- 5. Helper Function (Bypass RLS) -- This one is now super safe because it only checks 'users' table CREATE OR REPLACE FUNCTION public.is_authorized_to_manage(target_company_id UUID) RETURNS BOOLEAN AS $$ BEGIN -- Allow if table is empty (initial setup) IF NOT EXISTS (SELECT 1 FROM public.employees) THEN RETURN TRUE; END IF; -- Check users table for cached role info -- Since we are querying 'users' from an 'employees' policy, there is NO RECURSION. RETURN EXISTS ( SELECT 1 FROM public.users WHERE id = auth.uid() AND (role_name = 'admin' OR (role_name = 'manager' AND company_id = target_company_id)) ); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 6. Redefine Employees Policies DROP POLICY IF EXISTS "Employees unified select" ON public.employees; DROP POLICY IF EXISTS "Employees unified insert" ON public.employees; DROP POLICY IF EXISTS "Employees unified update" ON public.employees; DROP POLICY IF EXISTS "Employees unified delete" ON public.employees; DROP POLICY IF EXISTS "View employees policy" ON public.employees; DROP POLICY IF EXISTS "Manage employees policy" ON public.employees; DROP POLICY IF EXISTS "Initial setup allowance" ON public.employees; -- SELECT: Colleagues can see each other, Admins see all CREATE POLICY "Employees select policy" ON public.employees FOR SELECT TO authenticated USING ( user_id = auth.uid() -- Can see self OR EXISTS ( SELECT 1 FROM public.users u WHERE u.id = auth.uid() AND (u.role_name = 'admin' OR u.company_id = employees.company_id) ) ); -- INSERT/UPDATE/DELETE CREATE POLICY "Employees management policy" ON public.employees FOR ALL TO authenticated USING ( public.is_authorized_to_manage(company_id) ) WITH CHECK ( public.is_authorized_to_manage(company_id) );