-- ============================================================================= -- Migration: 20260419000016_tenants_calendario_hardening -- Sessão 7 — Tenants + Calendário scan (corrige críticos + altos + WITH CHECKs). -- -- Resolve: -- • Tenants V#1 (P0) — tenant_invites RLS off + 0 policies -- • Tenants V#2 — profiles_insert_own sem WITH CHECK -- • Tenants V#3 — support_sessions_saas_insert sem WITH CHECK -- • Tenants V#6 — user_settings_insert_own sem WITH CHECK -- • Calendário V#1 — feriados_insert + feriados_saas_insert sem WITH CHECK -- -- Auditoria prévia: tenant_invites tem 0 rows (seguro habilitar RLS sem -- migração de dados). -- ============================================================================= -- ───────────────────────────────────────────────────────────────────────── -- Tenants V#1 (P0): tenant_invites -- ----------------------------------------------------------------------------- ALTER TABLE public.tenant_invites ENABLE ROW LEVEL SECURITY; REVOKE ALL ON public.tenant_invites FROM anon, authenticated; GRANT SELECT, INSERT, UPDATE, DELETE ON public.tenant_invites TO authenticated; -- SELECT: tenant_admin/admin/owner do tenant + saas_admin DROP POLICY IF EXISTS tenant_invites_select ON public.tenant_invites; CREATE POLICY tenant_invites_select ON public.tenant_invites FOR SELECT TO authenticated USING ( public.is_saas_admin() OR tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') ) ); -- INSERT: só tenant_admin do tenant_id, e invited_by deve ser o caller DROP POLICY IF EXISTS tenant_invites_insert ON public.tenant_invites; CREATE POLICY tenant_invites_insert ON public.tenant_invites FOR INSERT TO authenticated WITH CHECK ( invited_by = auth.uid() AND tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') ) ); -- UPDATE: só revogação por tenant_admin do tenant. Aceitar é via RPC tenant_accept_invite (SECURITY DEFINER). DROP POLICY IF EXISTS tenant_invites_update ON public.tenant_invites; CREATE POLICY tenant_invites_update ON public.tenant_invites FOR UPDATE TO authenticated USING ( public.is_saas_admin() OR tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') ) ) WITH CHECK ( public.is_saas_admin() OR tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') ) ); -- DELETE: tenant_admin OR saas_admin DROP POLICY IF EXISTS tenant_invites_delete ON public.tenant_invites; CREATE POLICY tenant_invites_delete ON public.tenant_invites FOR DELETE TO authenticated USING ( public.is_saas_admin() OR tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') ) ); COMMENT ON TABLE public.tenant_invites IS 'Convites pra entrar em tenant. Aceitar deve ser via RPC tenant_accept_invite (SECURITY DEFINER). Criar/revogar via UI por tenant_admin.'; -- ───────────────────────────────────────────────────────────────────────── -- Tenants V#2: profiles INSERT WITH CHECK -- ----------------------------------------------------------------------------- DROP POLICY IF EXISTS profiles_insert_own ON public.profiles; CREATE POLICY profiles_insert_own ON public.profiles FOR INSERT TO authenticated WITH CHECK (id = auth.uid()); -- ───────────────────────────────────────────────────────────────────────── -- Tenants V#3: support_sessions INSERT WITH CHECK -- (admin_id deve ser o caller E o caller deve ser saas_admin) -- ----------------------------------------------------------------------------- DROP POLICY IF EXISTS support_sessions_saas_insert ON public.support_sessions; CREATE POLICY support_sessions_saas_insert ON public.support_sessions FOR INSERT TO authenticated WITH CHECK ( admin_id = auth.uid() AND EXISTS (SELECT 1 FROM public.saas_admins sa WHERE sa.user_id = auth.uid()) ); -- ───────────────────────────────────────────────────────────────────────── -- Tenants V#6: user_settings INSERT WITH CHECK -- ----------------------------------------------------------------------------- DROP POLICY IF EXISTS user_settings_insert_own ON public.user_settings; CREATE POLICY user_settings_insert_own ON public.user_settings FOR INSERT TO authenticated WITH CHECK (user_id = auth.uid()); -- ───────────────────────────────────────────────────────────────────────── -- Calendário V#1: feriados INSERT WITH CHECK (tenant + global) -- ----------------------------------------------------------------------------- DROP POLICY IF EXISTS feriados_insert ON public.feriados; CREATE POLICY feriados_insert ON public.feriados FOR INSERT TO authenticated WITH CHECK ( tenant_id IS NOT NULL AND owner_id = auth.uid() AND tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' ) ); DROP POLICY IF EXISTS feriados_saas_insert ON public.feriados; CREATE POLICY feriados_saas_insert ON public.feriados FOR INSERT TO authenticated WITH CHECK ( tenant_id IS NULL AND EXISTS (SELECT 1 FROM public.saas_admins sa WHERE sa.user_id = auth.uid()) ); -- ───────────────────────────────────────────────────────────────────────── -- Calendário V#2: feriados DELETE — adicionar tenant_admin -- ----------------------------------------------------------------------------- DROP POLICY IF EXISTS feriados_delete ON public.feriados; CREATE POLICY feriados_delete ON public.feriados FOR DELETE TO authenticated USING ( owner_id = auth.uid() OR (tenant_id IS NOT NULL AND tenant_id IN ( SELECT tm.tenant_id FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.status = 'active' AND tm.role IN ('tenant_admin','admin','owner') )) );