Files
agenciapsilmno/database-novo/migrations/20260419000018_servicos_prontuarios_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

224 lines
9.4 KiB
SQL

-- =============================================================================
-- Migration: 20260419000018_servicos_prontuarios_hardening
-- Sessão 9 — Serviços/Prontuários scan.
--
-- Resolve:
-- • Serviços V#1+V#2 (CRÍTICOS): silos por owner em services/medicos/insurance_plans
-- • Serviços V#3+V#4 (ALTOS): cascade silos em commitment_services/insurance_plan_services
-- • Serviços V#5: WITH CHECK ausente em commitment_time_logs/determined_*
--
-- Padrão validado em 5 áreas anteriores (Documentos/Financeiro/Comunicação/etc):
-- SELECT tenant_member, INSERT/UPDATE/DELETE owner+saas, com WITH CHECK explícito.
-- =============================================================================
-- ─────────────────────────────────────────────────────────────────────────
-- V#1 services — split em 4 policies
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "services: owner full access" ON public.services;
DROP POLICY IF EXISTS "services: select" ON public.services;
DROP POLICY IF EXISTS "services: insert" ON public.services;
DROP POLICY IF EXISTS "services: update" ON public.services;
DROP POLICY IF EXISTS "services: delete" ON public.services;
CREATE POLICY "services: select" ON public.services
FOR SELECT TO authenticated
USING (
owner_id = auth.uid()
OR 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'
)
);
CREATE POLICY "services: insert" ON public.services
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 "services: update" ON public.services
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 "services: delete" ON public.services
FOR DELETE TO authenticated
USING (owner_id = auth.uid() OR public.is_saas_admin());
-- ─────────────────────────────────────────────────────────────────────────
-- V#2 medicos — mesmo padrão
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "medicos: owner full access" ON public.medicos;
DROP POLICY IF EXISTS "medicos: select" ON public.medicos;
DROP POLICY IF EXISTS "medicos: insert" ON public.medicos;
DROP POLICY IF EXISTS "medicos: update" ON public.medicos;
DROP POLICY IF EXISTS "medicos: delete" ON public.medicos;
CREATE POLICY "medicos: select" ON public.medicos
FOR SELECT TO authenticated
USING (
owner_id = auth.uid()
OR 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'
)
);
CREATE POLICY "medicos: insert" ON public.medicos
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 "medicos: update" ON public.medicos
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 "medicos: delete" ON public.medicos
FOR DELETE TO authenticated
USING (owner_id = auth.uid() OR public.is_saas_admin());
-- ─────────────────────────────────────────────────────────────────────────
-- V#1 (parte 2) insurance_plans — mesmo padrão
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "insurance_plans: owner full access" ON public.insurance_plans;
DROP POLICY IF EXISTS "insurance_plans: select" ON public.insurance_plans;
DROP POLICY IF EXISTS "insurance_plans: insert" ON public.insurance_plans;
DROP POLICY IF EXISTS "insurance_plans: update" ON public.insurance_plans;
DROP POLICY IF EXISTS "insurance_plans: delete" ON public.insurance_plans;
CREATE POLICY "insurance_plans: select" ON public.insurance_plans
FOR SELECT TO authenticated
USING (
owner_id = auth.uid()
OR 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'
)
);
CREATE POLICY "insurance_plans: insert" ON public.insurance_plans
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 "insurance_plans: update" ON public.insurance_plans
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 "insurance_plans: delete" ON public.insurance_plans
FOR DELETE TO authenticated
USING (owner_id = auth.uid() OR public.is_saas_admin());
-- ─────────────────────────────────────────────────────────────────────────
-- V#3 commitment_services — cascade via JOIN com services.tenant_id
-- (tabela N:N sem tenant_id próprio; herda do services pai)
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "commitment_services: owner full access" ON public.commitment_services;
DROP POLICY IF EXISTS "commitment_services: tenant_member" ON public.commitment_services;
CREATE POLICY "commitment_services: tenant_member" ON public.commitment_services
FOR ALL TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.services s
WHERE s.id = commitment_services.service_id
AND (
s.owner_id = auth.uid()
OR public.is_saas_admin()
OR s.tenant_id IN (
SELECT tm.tenant_id FROM public.tenant_members tm
WHERE tm.user_id = auth.uid() AND tm.status = 'active'
)
)
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.services s
WHERE s.id = commitment_services.service_id
AND (s.owner_id = auth.uid() OR public.is_saas_admin())
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#4 insurance_plan_services — cascade via JOIN com insurance_plans
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS "insurance_plan_services_owner" ON public.insurance_plan_services;
DROP POLICY IF EXISTS "insurance_plan_services: tenant_member" ON public.insurance_plan_services;
CREATE POLICY "insurance_plan_services: tenant_member" ON public.insurance_plan_services
FOR ALL TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.insurance_plans ip
WHERE ip.id = insurance_plan_services.insurance_plan_id
AND (
ip.owner_id = auth.uid()
OR public.is_saas_admin()
OR ip.tenant_id IN (
SELECT tm.tenant_id FROM public.tenant_members tm
WHERE tm.user_id = auth.uid() AND tm.status = 'active'
)
)
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.insurance_plans ip
WHERE ip.id = insurance_plan_services.insurance_plan_id
AND (ip.owner_id = auth.uid() OR public.is_saas_admin())
)
);
-- ─────────────────────────────────────────────────────────────────────────
-- V#5 — adicionar WITH CHECK em INSERT das 3 tabelas que não tinham
-- -----------------------------------------------------------------------------
DROP POLICY IF EXISTS ctl_insert_for_active_member ON public.commitment_time_logs;
CREATE POLICY ctl_insert_for_active_member ON public.commitment_time_logs
FOR INSERT TO authenticated
WITH CHECK (
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 dcf_insert_for_active_member ON public.determined_commitment_fields;
CREATE POLICY dcf_insert_for_active_member ON public.determined_commitment_fields
FOR INSERT TO authenticated
WITH CHECK (
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 dc_insert_for_active_member ON public.determined_commitments;
CREATE POLICY dc_insert_for_active_member ON public.determined_commitments
FOR INSERT TO authenticated
WITH CHECK (
tenant_id IN (
SELECT tm.tenant_id FROM public.tenant_members tm
WHERE tm.user_id = auth.uid() AND tm.status = 'active'
)
);