7c20b518d4
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>
9.0 KiB
9.0 KiB
HANDOFF — 2026-04-19 (Sessão 6)
Documento de continuidade. Quando você voltar, comece lendo esta página. Todo o trabalho está registrado no banco (/saas/desenvolvimento → Verificações, Auditoria, Testes) — este arquivo é só o mapa.
📊 Estado atual
| Tipo | Aberto | Notas |
|---|---|---|
| A# auditoria | 0 | ✅ todas as 30 resolvidas |
| V# verificações | 9 | auth(3), pacientes(3), saas(2), router(1) |
| T# testes | 2 a escrever | T#9 useAgendaEvents wrapper, T#10 E2E |
| Áreas não auditadas | 3 | financeiro, comunicação, documentos/prontuários |
| Migrations não commitadas | 8 | Sessão 6 (ver lista abaixo) |
| Vitest | 179/179 | 8 suites |
| SQL integration tests | 33/33 | database-novo/tests/run.cjs |
✅ Sessão 6 (hoje, 2026-04-19) — resumo
Bloco 1 — V#34 + V#41 (Opção B2: plano + override + exceção comercial)
- Migration
20260419000001_tenant_features_b2_governance.sql:- Trigger
tenant_features_guard_with_planganhou bypass via session flag (current_setting('app.allow_feature_exception')) - Nova RPC
set_tenant_feature_exception(tenant_id, feature_key, enabled, reason)SECURITY DEFINER com regras assimétricas:enabled=false→ tenant_admin OU saas_admin (preferência do cliente)enabled=trueAND plano permite → tenant_admin OU saas_adminenabled=trueAND plano NÃO permite → só saas_admin + reason obrigatório (exceção comercial)
- Policy
tenant_features_write_saas_only— writes diretos só saas_admin
- Trigger
- Store
tenantFeaturesStore.isEnabledreescrito (B2): override negativo desliga, override positivo liga, sem override segue plano - Store
setForTenantagora chama RPC comreasonopcional - UI nova
/saas/tenant-features(selector de tenant, catálogo, dialog comreasonobrigatório p/ exceção, log de mudanças) - JSDoc documentando separação semântica (
entitlementsStore.has= "plano permite?" vstenantFeaturesStore.isEnabled= "ativo agora?") - Testes
src/stores/__tests__/tenantFeaturesStore.spec.js— 17 cenários incluindo regressão V#34
Bloco 2 — Pendentes Sessão 5
- V#42 —
entitlementsStore.loadFor*no catch agora NÃO marca como carregado (estado fica como "not loaded" → próximo request retenta) +logErroradicionado - V#40 —
features.is_active(migration...02) + UI: soft delete + filtro "Mostrar depreciados" + Tag de status + botão Reativar - V#36 — RPC
delete_plan_safe(migration...03): bloqueia DELETE se houver subscriptions ativas. SaasPlansPage migrada - V#35 — Migration
...04: 17 → 11 policies. Removidas 3 read-auth duplicadas em plans/features/plan_features + 3 subsets/no-ops em subscriptions.COMMENT ON POLICYem todas
Bloco 3 — Testes T#5 / T#7 / T#8
- T#5
tenantStore.spec.js— 15 testes: singleflight, regressão V#5 (não herdar tenant de outro user), erros, setActiveTenant, reset, getters. Stub localStorage in-memory (env=node sem jsdom) - T#7
validators.spec.js— 38 testes: sanitização do intake (digitsOnly, CPF/CNPJ, phone, email, CEP, toISODate) - T#8
database-novo/tests/run.cjs— runner Node + docker exec. 33 cenários SQL cobrindo set_tenant_feature_exception, delete_plan_safe, intake, features.is_active, defesa em camadas, twilio config
Bloco 4 — 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 muito em paciente vulnerável buscando atendimento).
5 camadas:
- Honeypot — campo invisível (frontend), bot rejeita
- Validação básica
- Rate limit por IP —
check_rate_limitRPC - Math captcha condicional — só ativa após N falhas do mesmo IP (default 3)
- Modo paranoid global toggle (saas_security_config.captcha_required_globally)
Implementação:
- Migration
...06(4 tabelas:saas_security_config,public_submission_attempts,submission_rate_limits,math_challenges) - Migration
...07(RPCs:check_rate_limit,record_submission_attempt,generate_math_challenge,verify_math_challenge,cleanup_expired_math_challenges) - Edge function
submit-patient-intakereescrita (dual endpoint: submit +/captcha-challenge) - Componente
MathCaptchaChallenge.vue(lazy) - Tela
/saas/securitycom card explicativo (6 seções), KPIs 24h, toggles, sliders, dashboard de IPs ativos, modo paranoid
Bloco 5 — SaaS Twilio Config (operacional editável)
- Migration
...08(tabelasaas_twilio_configsingleton + RPCsget_twilio_config/update_twilio_config) - Decisão de segurança:
TWILIO_AUTH_TOKENpermanece em env var (único secret); SID/webhook/rate/margem migram pra DB - Edge function
twilio-whatsapp-provisionlê do banco com fallback pra env (back-compat) - Tela
/saas/twilio-configcom card explicativo + status do AUTH_TOKEN (ping pra detectar se está setado) - Bug fix:
friendlyErrorMessage()emtwilioWhatsappService.jstraduz "Edge Function returned a non-2xx status code" pra mensagens contextuais
🛑 Pendências (V# abertos por área)
auth (3)
- V#2 (médio) — Listener
supabase.auth.onAuthStateChangeduplicado - V#6 (baixo) —
globalRolecache sem TTL e sem invalidação por realtime - V#10 (médio) — Bloqueio SaaS em tenant-app só por
startsWithde path (frágil)
pacientes (3)
- V#3 (médio) — Pacientes não tem composables/services — toda lógica em pages
- V#8 (baixo) —
agenda_eventos.selectpatient_id comlimit 1000arbitrário - V#9 (médio) —
PatientsCadastroPagecom 1985 linhas (precisa quebrar)
router (1)
- V#9 (baixo) —
ensureMenuBuiltroda em toda navegação autenticada
saas (2)
- V#17 (baixo) — 23
console.*em páginas SaaS - V#18 (baixo) —
tenantFeaturesStoresem TTL real (cache pode ficar stale)
🗂️ Migrations criadas hoje (ordem cronológica)
database-novo/migrations/
├── 20260419000001_tenant_features_b2_governance.sql (V#34/V#41)
├── 20260419000002_features_is_active.sql (V#40)
├── 20260419000003_delete_plan_safe.sql (V#36)
├── 20260419000004_consolidate_policies.sql (V#35)
├── 20260419000005_restrict_intake_rpc.sql (A#20 — REVOKE anon)
├── 20260419000006_layered_bot_defense.sql (A#20 rev2 — schema)
├── 20260419000007_bot_defense_rpcs.sql (A#20 rev2 — RPCs)
└── 20260419000008_saas_twilio_config.sql (Twilio config)
Todas aplicadas no banco local. Se for pra cloud, aplicar nessa ordem.
🗜️ Arquivos criados/modificados (código)
Criados:
src/components/security/MathCaptchaChallenge.vue
src/views/pages/saas/SaasTenantFeaturesPage.vue
src/views/pages/saas/SaasSecurityPage.vue
src/views/pages/saas/SaasTwilioConfigPage.vue
src/stores/__tests__/tenantStore.spec.js
src/stores/__tests__/tenantFeaturesStore.spec.js
src/utils/__tests__/validators.spec.js
database-novo/tests/run.cjs
supabase/functions/submit-patient-intake/index.js (reescrita)
Modificados:
src/stores/tenantFeaturesStore.js (lógica B2 + RPC)
src/stores/entitlementsStore.js (V#42 fix + JSDoc)
src/services/twilioWhatsappService.js (friendlyErrorMessage)
src/views/pages/saas/SaasFeaturesPage.vue (V#40 soft delete)
src/views/pages/saas/SaasPlansPage.vue (V#36 RPC)
src/views/pages/saas/SaasTwilioWhatsappPage.vue (toast warn em vez de error)
src/views/pages/clinic/clinic/ClinicFeaturesPage.vue (texto erro plano-denied)
src/views/pages/public/CadastroPacienteExterno.vue (honeypot + math captcha)
src/router/routes.saas.js (3 rotas novas)
src/navigation/menus/saas.menu.js (3 itens menu novos)
supabase/functions/twilio-whatsapp-provision/index.ts (lê config DB)
.env (limpo Turnstile vars)
📊 Números finais
| Métrica | Antes (Sessão 5) | Hoje |
|---|---|---|
| A# auditoria | 30 (1 aberto) | 30 (0 abertos) |
| V# verificações | 42 (15 abertos) | 42 (9 abertos) |
| Suites de teste vitest | 6 (109 tests) | 8 (179 tests) |
| Suites SQL integration | 0 | 1 (33 tests) |
| Migrations totais | 5 (Sessão 5) | 13 (+8 hoje) |
| Telas SaaS novas | — | 3 (/tenant-features, /security, /twilio-config) |
🎯 Ordem sugerida quando voltar
- Decidir se commita o trabalho da Sessão 6 (~30 arquivos, 8 migrations).
- Continuar Sessão 6 (em andamento): A+B+C+D escolhidos, faltando concluir B/C/D.
- Outras opções:
- Nova área de revisão sênior — financeiro / comunicação / documentos
- Deploy real (Supabase cloud + secrets + edge functions)
- Testes T#9 + T#10
⚠️ Nada commitado (ainda)
Working directory tem ~30 arquivos modificados desde Sessão 5. Revise com git status + git diff antes de decidir o que comitar.
Nada quebrou: vitest 179/179 + SQL 33/33.