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

238 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- `agendaRepository``useAgendaEvents` consolidados (composable virou wrapper fino, 181→67 linhas)
- `AGENDA_EVENT_SELECT` centralizado em `agendaSelects.js`
- `_tenantGuards.js` compartilhado
- V#21 status `remarcar``remarcado` 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#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:**
1. **Honeypot** — campo invisível
2. **Validação** básica
3. **Rate limit por IP**`check_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