Files
agenciapsilmno/commit.md
T
Leonardo 7c20b518d4 Sessoes 1-6 acumuladas: hardening B2, defesa em camadas, +192 testes
Repositorio estava ha ~5 sessoes sem commit. Consolida tudo desde d088a89.

Ver commit.md na raiz para descricao completa por sessao.

# Numeros
- A# auditoria abertos: 0/30
- V# verificacoes abertos: 5/52 (todos adiados com plano)
- T# testes escritos: 10/10
- Vitest: 192/192
- SQL integration: 33/33
- E2E (Playwright, novo): 5/5
- Migrations: 17 (10 novas Sessao 6)
- Areas auditadas: 7 (+documentos com 10 V#)

# Highlights Sessao 6 (hoje)
- V#34/V#41 Opcao B2: tenant_features com plano + override (RPC SECURITY DEFINER, tela /saas/tenant-features)
- A#20 rev2 self-hosted: defesa em 5 camadas (honeypot + rate limit + math captcha condicional + paranoid mode + dashboard /saas/security)
- Documentos hardening (V#43-V#49): tenant scoping em storage policies (vazamento entre clinicas eliminado), RPC validate_share_token, signatures policy granular
- SaaS Twilio Config (/saas/twilio-config): UI editavel para SID/webhook/cotacao; AUTH_TOKEN permanece em env var
- T#9 + T#10: useAgendaEvents.spec.js + Playwright E2E (descobriu bug no front que foi corrigido)

# Sessoes anteriores (1-5) consolidadas
- Sessao 1: auth/router/session, normalizeRole extraido
- Sessao 2: agenda - composables/services consolidados
- Sessao 3: pacientes - tenant_id em todas queries
- Sessao 4: security review pagina publica - 14/15 vulnerabilidades corrigidas
- Sessao 5: SaaS - P0 (A#30: 7 tabelas com RLS off corrigidas)

# .gitignore ajustado
- supabase/* + !supabase/functions/ (mantem 10 edge functions, ignora .temp/migrations gerados pelo CLI)
- database-novo/backups/ (regeneravel via db.cjs backup)
- test-results/ + playwright-report/
- .claude/settings.local.json (config local com senha de dev removida do tracking)

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

10 KiB
Raw Blame History

Sessões 1-6 acumuladas — hardening, defesa em profundidade, +192 testes

Repositório estava sem commit há ~5 sessões de trabalho intenso. Este commit consolida tudo que foi feito desde o último marco (d088a89).

A# auditoria: 30 itens registrados, 0 abertos. V# verificações: 52 registradas (10 novas em Documentos), 5 abertas (todas adiadas com plano). T# testes: 10/10 escritas. 192 vitest + 33 SQL + 5 E2E passando.


Sessão 1 — auth/router/session

  • A#7 resolvido (window.__guardsBound)
  • 10 verificações registradas (V#1V#10), 5 corrigidas:
    • session.js logger inconsistente
    • tenantStore.ensureLoaded polling
    • normalizeRole duplicado → extraído pra src/utils/roleNormalizer.js
    • console.error em router/index.js
    • .single() em fetchRole

Sessão 2 — agenda

  • 11 verificações (V#11V#21), 10 corrigidas:
    • useRecurrence CRUD ganhou filtro tenant_id (alto)
    • agenda.service.js vazio deletado
    • agendaRepositoryuseAgendaEvents consolidados (composable virou wrapper fino, 181→67 linhas)
    • AGENDA_EVENT_SELECT centralizado em agendaSelects.js
    • _tenantGuards.js compartilhado
    • V#21 status remarcarremarcado padronizado em 14 edições

Sessão 3 — pacientes

  • 10 verificações (V#22V#31), 6 corrigidas + 4 documentadas
  • 5 arquivos obsoletos deletados (PatientsCadastroPage Bkp, preview, prontuário design 1/2/3)
  • tenant_id em todas queries de patients (alto)
  • 9 console.* migrados pra logger
  • hydrateAssociations paralelizada (5 round-trips → 2)
  • .maybeSingle() onde precisava

Sessão 4 — security review (página pública de cadastro)

  • V#31 virou security review completa: 15 vulnerabilidades (A#15A#29), 14 corrigidas
  • Críticos:
    • A#15 bucket avatars público → 5MB + mime whitelist + policies restritas
    • A#16 RPC v2 ignorava active/expires/max_uses → validação completa + incrementa uses
    • A#17 notas_internas exposto ao paciente → removido do form e RPC
    • A#18 Math.random() pra token → RPCs server-side via gen_random_uuid()
    • A#19 intake sem tenant_id → RPC resolve via patient_invites ou tenant_members
  • Médios: log patient_invite_attempts (A#24), política LGPD (A#25), botão mock só em DEV (A#26), length caps server-side (A#27)
  • Baixos: duplicado PatientsExternalLinkPage deletado, Landingpage-v1 - bkp.vue deletado

Sessão 5 — SaaS (planos, preços, recursos)

  • 10 verificações (V#33V#42)
  • 🔴 P0: A#30 — 7 tabelas SaaS com RLS OFF + GRANT ALL pra anon. Migration ...05_saas_rls_emergency_fix aplicou REVOKE + ENABLE RLS + 9 policies corretas
  • 109/109 testes passando

Sessão 6 (HOJE, 2026-04-19) — bloco principal

V#34 + V#41 — Opção B2 (plano + override + exceção comercial)

Resolve tenantFeaturesStore.isEnabled que retornava true por default (qualquer feature aparecia ativa pra qualquer tenant) E a dupla-fonte com entitlementsStore.

Backend (migration ...01):

  • Trigger tenant_features_guard_with_plan ganhou bypass via session flag
  • RPC set_tenant_feature_exception SECURITY DEFINER com regras assimétricas:
    • enabled=false → tenant_admin OU saas_admin (preferência)
    • enabled=true AND plano permite → tenant_admin OU saas_admin
    • enabled=true AND plano NÃO permite → só saas_admin + reason obrigatório
  • Policy tenant_features_write_saas_only

Frontend:

  • tenantFeaturesStore.isEnabled reescrito (B2): override negativo desliga, override positivo liga (exceção), sem override segue plano
  • setForTenant chama RPC com reason
  • Tela nova /saas/tenant-features com dialog de motivo obrigatório
  • JSDoc separação semântica: entitlementsStore.has = "plano permite?" vs tenantFeaturesStore.isEnabled = "ativo agora?"
  • 17 testes em tenantFeaturesStore.spec.js

Pendentes Sessão 5 fechados

  • V#35 — 17→11 policies (consolidadas plans/features/plan_features/subscriptions) + COMMENT ON POLICY
  • V#36 — RPC delete_plan_safe bloqueia DELETE com subscriptions ativas
  • V#40features.is_active (soft delete) + UI com filtro/Reativar
  • V#42entitlementsStore.loadFor* no catch não marca como carregado + logError

Testes T#5/T#7/T#8

  • T#5 tenantStore.spec.js — 15 testes (singleflight, regressão V#5, erros, setActiveTenant, reset, getters)
  • T#7 validators.spec.js — 38 testes (sanitização do intake)
  • T#8 database-novo/tests/run.cjs — runner Node + docker exec, 33 cenários SQL

A#20 (CAPTCHA) — rev2 self-hosted

Decisão: descartado Cloudflare Turnstile / hCaptcha em favor de defesa em camadas self-hosted. Razões: zero LGPD, zero provider, zero fricção pro paciente legítimo (UX importa em paciente vulnerável buscando atendimento).

5 camadas:

  1. Honeypot — campo invisível
  2. Validação básica
  3. Rate limit por IPcheck_rate_limit RPC
  4. Math captcha condicional — só ativa após N falhas (default 3)
  5. Modo paranoid global toggle

Implementação:

  • Migrations ...06 (4 tabelas) + ...07 (RPCs)
  • Edge function submit-patient-intake reescrita (dual endpoint)
  • Componente MathCaptchaChallenge.vue lazy
  • Tela /saas/security com card explicativo (6 seções), KPIs 24h, toggles, sliders, dashboard de IPs

SaaS Twilio Config (UI editável)

  • Migration ...08 (singleton + RPCs get_twilio_config/update_twilio_config)
  • AUTH_TOKEN permanece em env var (único secret); SID/webhook/rate/margem migram pra DB
  • Edge function lê do banco com fallback pra env (back-compat)
  • Tela /saas/twilio-config com card + status do AUTH_TOKEN
  • Bug fix: friendlyErrorMessage() traduz "Edge Function returned a non-2xx status code"

Revisão sênior em Documentos/prontuários

10 V# novas registradas, 7 corrigidas, 3 adiadas.

Críticos:

  • 🔴 V#43/V#44 vazamento entre clínicas via storage policies — corrigido com tenant scoping no path (storage.foldername(name))[1]::uuid IN tenant_members
  • 🔴 V#45 documents policy pobre (só owner_id = auth.uid()) — separada em SELECT/INSERT/UPDATE/DELETE com tenant scoping

Altos:

  • 🟠 V#46 share_links sem incremento de usos — RPC validate_share_token atomicamente valida + incrementa + loga
  • 🟠 V#47 signatures policy ALL — separada (UPDATE só pra signatário)

Médios:

  • 🟡 V#48 access_logs WITH CHECK
  • 🟡 V#49 templates WITH CHECK

B-block (V# avulsos)

  • V#2 Listener onAuthStateChange consolidado (session.js virou autoridade + API onSessionEvent)
  • V#6 globalRoleCache TTL 5min
  • V#10 Bloqueio SaaS via meta.area/meta.saasAdmin em vez de path.startsWith
  • V#8 RPC get_patient_session_counts substitui .limit(1000) arbitrário
  • V#9 router short-circuit lastEnsureKey em ensureMenuBuilt
  • V#17 25 console.* eliminados em src/views/pages/saas/
  • V#18 TTL real em tenantFeaturesStore

T#9 + T#10

  • T#9 useAgendaEvents.spec.js — 13 testes do wrapper
  • T#10 Playwright + Chromium instalados; 5 specs E2E em e2e/patient-intake.spec.js
  • Bug fix achado pelo E2E: CadastroPacienteExterno.enviar não extraía body do erro 403 — corrigido

📦 Migrations consolidadas (todas as sessões)

20260417000001_dev_tables                          (Sessão pré-1: tabelas dev)
20260417000002_dev_tables_ordem
20260418000001_dev_verificacoes                    (Sessão 1)
20260418000002_patient_intake_security_hardening   (Sessão 4)
20260418000003_patient_invite_attempts_log         (Sessão 4)
20260418000004_dev_tests                           (Sessão 1)
20260418000005_saas_rls_emergency_fix              (Sessão 5 — P0)
20260419000001_tenant_features_b2_governance       (Sessão 6 — V#34/V#41)
20260419000002_features_is_active                  (Sessão 6 — V#40)
20260419000003_delete_plan_safe                    (Sessão 6 — V#36)
20260419000004_consolidate_policies                (Sessão 6 — V#35)
20260419000005_restrict_intake_rpc                 (Sessão 6 — A#20)
20260419000006_layered_bot_defense                 (Sessão 6 — A#20 rev2)
20260419000007_bot_defense_rpcs                    (Sessão 6 — A#20 rev2)
20260419000008_saas_twilio_config                  (Sessão 6)
20260419000009_patient_session_counts_rpc          (Sessão 6 — V#8)
20260419000010_documents_security_hardening        (Sessão 6 — V#43-V#49)

🆕 Pastas/arquivos novos importantes

  • e2e/ — specs Playwright (T#10)
  • playwright.config.js — config E2E
  • database-novo/tests/run.cjs — runner SQL integration tests (T#8)
  • database-novo/backups/ agora ignorado (regenerável via db.cjs backup)
  • src/components/security/MathCaptchaChallenge.vue — A#20 rev2
  • src/views/pages/saas/SaasTenantFeaturesPage.vue — V#34
  • src/views/pages/saas/SaasSecurityPage.vue — A#20 rev2 + card educativo
  • src/views/pages/saas/SaasTwilioConfigPage.vue — UI Twilio editável
  • src/utils/roleNormalizer.js — Sessão 1
  • src/features/agenda/services/_tenantGuards.js + agendaSelects.js — Sessão 2
  • 6 specs novas em __tests__/ (vitest)
  • supabase/functions/submit-patient-intake/ — edge function reescrita A#20 rev2

🛠️ .gitignore ajustado neste commit

  • supabase/* + !supabase/functions/ (mantém edge functions, ignora .temp//migrations//etc gerados pelo CLI)
  • database-novo/backups/ (backups regeneráveis)
  • test-results/, playwright-report/ (outputs Playwright)
  • .claude/settings.local.json (config local do harness)

📊 Números finais

Métrica Início Fim
A# abertos 30 (a registrar) 0
V# abertos 52 (a registrar) 5 (adiados)
T# escritas 0/10 10/10
Vitest 192/192
SQL integration 33/33
E2E (Playwright) 5/5
Migrations 0 17
Telas SaaS novas 3
Edge functions reescritas 1 (submit-patient-intake)

⚠️ Adiados (próximas sessões — plano completo no DB)

  • V#3 + V#9 pacientes — refatoração de composables/services (PatientsCadastroPage 1985 linhas). Sessão dedicada de 1-2h
  • V#50/V#51/V#52 documentos — portal-paciente policy, hash SHA-256, retention cron
  • Áreas não auditadas: financeiro, comunicação
  • Deploy real: cloud Supabase + secrets + edge functions