d6eb992f71
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>
158 lines
7.1 KiB
SQL
158 lines
7.1 KiB
SQL
-- =============================================================================
|
|
-- 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')
|
|
))
|
|
);
|