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>
10 KiB
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#1–V#10), 5 corrigidas:
session.jslogger inconsistentetenantStore.ensureLoadedpollingnormalizeRoleduplicado → extraído prasrc/utils/roleNormalizer.jsconsole.erroremrouter/index.js.single()emfetchRole
Sessão 2 — agenda
- 11 verificações (V#11–V#21), 10 corrigidas:
useRecurrenceCRUD ganhou filtrotenant_id(alto)agenda.service.jsvazio deletadoagendaRepository↔useAgendaEventsconsolidados (composable virou wrapper fino, 181→67 linhas)AGENDA_EVENT_SELECTcentralizado emagendaSelects.js_tenantGuards.jscompartilhado- V#21 status
remarcar→remarcadopadronizado em 14 edições
Sessão 3 — pacientes
- 10 verificações (V#22–V#31), 6 corrigidas + 4 documentadas
- 5 arquivos obsoletos deletados (PatientsCadastroPage Bkp, preview, prontuário design 1/2/3)
tenant_idem todas queries de patients (alto)- 9
console.*migrados pra logger hydrateAssociationsparalelizada (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#15–A#29), 14 corrigidas
- Críticos:
- A#15 bucket
avatarspúblico → 5MB + mime whitelist + policies restritas - A#16 RPC v2 ignorava
active/expires/max_uses→ validação completa + incrementauses - A#17
notas_internasexposto ao paciente → removido do form e RPC - A#18
Math.random()pra token → RPCs server-side viagen_random_uuid() - A#19 intake sem
tenant_id→ RPC resolve viapatient_invitesoutenant_members
- A#15 bucket
- 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
PatientsExternalLinkPagedeletado,Landingpage-v1 - bkp.vuedeletado
Sessão 5 — SaaS (planos, preços, recursos)
- 10 verificações (V#33–V#42)
- 🔴 P0: A#30 — 7 tabelas SaaS com RLS OFF +
GRANT ALLpra anon. Migration...05_saas_rls_emergency_fixaplicou 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_planganhou bypass via session flag - RPC
set_tenant_feature_exceptionSECURITY DEFINER com regras assimétricas:enabled=false→ tenant_admin OU saas_admin (preferência)enabled=trueAND plano permite → tenant_admin OU saas_adminenabled=trueAND plano NÃO permite → só saas_admin + reason obrigatório
- Policy
tenant_features_write_saas_only
Frontend:
tenantFeaturesStore.isEnabledreescrito (B2): override negativo desliga, override positivo liga (exceção), sem override segue planosetForTenantchama RPC comreason- Tela nova
/saas/tenant-featurescom dialog de motivo obrigatório - JSDoc separação semântica:
entitlementsStore.has= "plano permite?" vstenantFeaturesStore.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_safebloqueia DELETE com subscriptions ativas - V#40 —
features.is_active(soft delete) + UI com filtro/Reativar - V#42 —
entitlementsStore.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:
- Honeypot — campo invisível
- Validação básica
- Rate limit por IP —
check_rate_limitRPC - Math captcha condicional — só ativa após N falhas (default 3)
- Modo paranoid global toggle
Implementação:
- Migrations
...06(4 tabelas) +...07(RPCs) - Edge function
submit-patient-intakereescrita (dual endpoint) - Componente
MathCaptchaChallenge.vuelazy - Tela
/saas/securitycom card explicativo (6 seções), KPIs 24h, toggles, sliders, dashboard de IPs
SaaS Twilio Config (UI editável)
- Migration
...08(singleton + RPCsget_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-configcom 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_tokenatomicamente 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
onAuthStateChangeconsolidado (session.js virou autoridade + APIonSessionEvent) - V#6
globalRoleCacheTTL 5min - V#10 Bloqueio SaaS via
meta.area/meta.saasAdminem vez depath.startsWith - V#8 RPC
get_patient_session_countssubstitui.limit(1000)arbitrário - V#9 router short-circuit
lastEnsureKeyem 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.enviarnã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 E2Edatabase-novo/tests/run.cjs— runner SQL integration tests (T#8)database-novo/backups/agora ignorado (regenerável viadb.cjs backup)src/components/security/MathCaptchaChallenge.vue— A#20 rev2src/views/pages/saas/SaasTenantFeaturesPage.vue— V#34src/views/pages/saas/SaasSecurityPage.vue— A#20 rev2 + card educativosrc/views/pages/saas/SaasTwilioConfigPage.vue— UI Twilio editávelsrc/utils/roleNormalizer.js— Sessão 1src/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