Files
agenciapsilmno/database-novo/migrations/20260419000015_communication_security_hardening.sql
T
Leonardo d6eb992f71 Sessoes 6cont-10: hardening em 6 areas + scan completo do SaaS
Continuacao de 7c20b51. Esta etapa fechou TODA revisao senior do SaaS
(15 areas auditadas) + refator parcial de pacientes.

Ver commit.md para descricao completa por sessao.

# Estado final do projeto
- A# auditoria abertos: 1 (A#31 Deploy real)
- V# verificacoes abertos: 14 (todos medios/baixos adiados com plano)
- Criticos: 0
- Altos: 0
- Vitest: 208/208 (era 192, +16 nos novos composables)
- SQL integration: 33/33
- E2E (Playwright): 5/5
- Areas auditadas: 15

# Highlights
- Documentos 100% fechado (V#50/51/52: portal-paciente policy + content_sha256 + 4 cron jobs retention)
- Tenants V#1 P0: tenant_invites com RLS off + 0 policies (mesmo padrao A#30)
- Calendario 100% fechado: feriados WITH CHECK
- Addons V#1 P0 (dinheiro): addon_transactions WITH CHECK saas_admin
- Central SaaS V#1: faq write so saas_admin (era tenant_admin)
- Servicos/Prontuarios 100% fechado: services/medicos/insurance_plans + cascades
- Pacientes V#9: 2 composables novos (useCep, usePatientSupportContacts) + repo estendido + script extraido (template intocado, fica para quando houver E2E)

# 8 migrations novas neste commit
- 20260419000011_documents_portal_patient_policy.sql
- 20260419000012_documents_content_hash.sql
- 20260419000013_cron_retention_jobs.sql
- 20260419000014_financial_security_hardening.sql
- 20260419000015_communication_security_hardening.sql
- 20260419000016_tenants_calendario_hardening.sql
- 20260419000017_addons_central_saas_hardening.sql
- 20260419000018_servicos_prontuarios_hardening.sql

Total acumulado: 18 migrations (Sessoes 1-10).

# A#31 reformulado pra proxima sessao
"Deploy real" muda escopo: como nao ha cloud Supabase nem secrets reais
ainda (MVP), proxima sessao vira "Preparacao completa pra deploy" (DEPLOY.md,
validar migrations num container limpo, audit edge functions, listar env vars,
script db.cjs deploy-check).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 22:00:06 -03:00

128 lines
6.0 KiB
SQL

-- =============================================================================
-- Migration: 20260419000015_communication_security_hardening
-- Sessão 6 — revisão Comunicação. Resolve V#1-V#5 (2 críticos + 3 altos).
-- V#6-V#10 adiados (médios/baixos com plano completo no DB).
--
-- 🔴 V#1+V#2 são bugs P0: policies usavam (tenant_id = auth.uid()) — comparação
-- de UUID de tenant com UUID de user. Tabelas inacessíveis na prática.
-- =============================================================================
-- ─────────────────────────────────────────────────────────────────────────
-- V#1: email_layout_config — fix BUG do tenant_id = auth.uid()
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "tenant owns email layout config" ON public.email_layout_config;
DROP POLICY IF EXISTS "email_layout_config: tenant_admin all" ON public.email_layout_config;
CREATE POLICY "email_layout_config: tenant_admin all" ON public.email_layout_config
FOR ALL 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')
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#2: email_templates_tenant — MESMO bug
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "tenant manages own overrides" ON public.email_templates_tenant;
DROP POLICY IF EXISTS "email_templates_tenant: tenant_admin all" ON public.email_templates_tenant;
CREATE POLICY "email_templates_tenant: tenant_admin all" ON public.email_templates_tenant
FOR ALL 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')
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#3: notification_logs — SELECT pra tenant_member
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "notif_logs_tenant_member" ON public.notification_logs;
CREATE POLICY "notif_logs_tenant_member" ON public.notification_logs
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'
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#4: notification_queue — SELECT pra tenant_member
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "notif_queue_tenant_member" ON public.notification_queue;
CREATE POLICY "notif_queue_tenant_member" ON public.notification_queue
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'
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#5: notification_channels — SELECT pra tenant_member; INSERT tenant_admin; UPDATE/DELETE owner
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "notification_channels_owner" ON public.notification_channels;
DROP POLICY IF EXISTS "notif_channels_select" ON public.notification_channels;
DROP POLICY IF EXISTS "notif_channels_insert" ON public.notification_channels;
DROP POLICY IF EXISTS "notif_channels_modify" ON public.notification_channels;
CREATE POLICY "notif_channels_select" ON public.notification_channels
FOR SELECT TO authenticated
USING (
deleted_at IS NULL
AND (
public.is_saas_admin()
OR owner_id = auth.uid()
OR tenant_id IN (
SELECT tm.tenant_id FROM public.tenant_members tm
WHERE tm.user_id = auth.uid() AND tm.status = 'active'
)
)
);
CREATE POLICY "notif_channels_insert" ON public.notification_channels
FOR INSERT TO authenticated
WITH CHECK (
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'
)
);
CREATE POLICY "notif_channels_modify" ON public.notification_channels
FOR UPDATE TO authenticated
USING (owner_id = auth.uid() OR public.is_saas_admin())
WITH CHECK (owner_id = auth.uid() OR public.is_saas_admin());
CREATE POLICY "notif_channels_delete" ON public.notification_channels
FOR DELETE TO authenticated
USING (owner_id = auth.uid() OR public.is_saas_admin());