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>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
# 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.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#11–V#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#22–V#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#15–A#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#33–V#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
|
||||
Reference in New Issue
Block a user