2025-10-21 13:02:56 -03:00

30 KiB
Raw Blame History

MEDICONNECT Documentação Técnica e de Segurança

Aplicação SPA (React + Vite + TypeScript) consumindo Supabase (Auth, PostgREST, Edge Functions) via Netlify Functions. Este documento consolida: variáveis de ambiente, arquitetura de autenticação, modelo de segurança atual, riscos, controles implementados e próximos passos.


🚀 Guias de Início Rápido

Primeira vez rodando o projeto? Escolha seu guia:

Arquitetura da aplicação:

Frontend (Vite/React) → Netlify Functions → Supabase API

As Netlify Functions protegem as credenciais do Supabase e funcionam como proxy/backend.


⚠️ MUDANÇAS RECENTES NA API (21/10/2025)

Base de Dados Limpa

Todos os usuários, pacientes, laudos e agendamentos foram deletados. Motivo: limpeza de dados inconsistentes e roles incorretos.

Novas Permissões (RLS)

👨‍⚕️ Médicos:

  • Veem todos os pacientes
  • Veem apenas seus próprios laudos (filtro: created_by = médico)
  • Veem apenas seus próprios agendamentos (filtro: doctor_id = médico)
  • Editam apenas seus próprios laudos e agendamentos

👤 Pacientes:

  • Veem apenas seus próprios dados
  • Veem apenas seus próprios laudos (filtro: patient_id = paciente)
  • Veem apenas seus próprios agendamentos

👩‍💼 Secretárias:

  • Veem todos os pacientes
  • Veem todos os agendamentos
  • Veem todos os laudos

👑 Admins/Gestores:

  • Acesso completo a tudo

Novos Endpoints de Criação (Atualizado 21/10 - tarde)

⚠️ IMPORTANTE: A API mudou! create-doctor e create-patient (REST) NÃO ENVIAM MAGIC LINK e NÃO CRIAM AUTH USER.

create-user - Criação completa com autenticação (RECOMENDADO):

  • Obrigatório: email, full_name, role
  • Opcional: phone, create_patient_record, cpf, phone_mobile
  • 🔐 Envia magic link automaticamente para ativar conta
  • Cria: Auth user + Profile + Role + (opcionalmente) registro em patients
  • Use este para criar qualquer usuário que precisa fazer login

create-doctor (Edge Function) - Criação de médico SEM autenticação:

  • Obrigatório: cpf, crm, crm_uf, full_name, email
  • Validações: CRM (4-7 dígitos), CPF (11 dígitos), UF válido
  • NÃO cria auth user - apenas registro em doctors
  • Use apenas se precisar criar registro de médico sem login

POST /rest/v1/patients - Criação de paciente SEM autenticação:

  • Obrigatório: full_name, cpf, email, phone_mobile, created_by
  • NÃO cria auth user - apenas registro em patients
  • Use apenas se precisar criar registro de paciente sem login

Quando usar cada endpoint:

  • create-user com role="medico": Admin criando médico que precisa fazer login
  • create-user com role="paciente" + create_patient_record=true: Admin criando paciente com login
  • create-user com role="admin"/"secretaria": Criar usuários administrativos
  • create-doctor: Apenas para registros de médicos sem necessidade de login (raro)
  • POST /rest/v1/patients: Apenas para registros de pacientes sem necessidade de login (raro)

1. Variáveis de Ambiente (.env / .env.local)

Variável Obrigatória Descrição
VITE_SUPABASE_URL Sim URL base do projeto Supabase (https://<ref>.supabase.co)
VITE_SUPABASE_ANON_KEY Sim Chave pública (anon) usada para Auth password grant e PostgREST
VITE_APP_ENV Não Identifica ambiente (ex: dev, staging, prod)
VITE_SERVICE_EMAIL Não (desativado) Email de usuário técnico (não usar em produção no momento)
VITE_SERVICE_PASSWORD Não (desativado) Senha do usuário técnico (não usar em produção no momento)

Boas práticas:

  • Nunca exponha Service Role Key no frontend.
  • Não comitar .env usar .env.example como referência.

2. Arquitetura de Autenticação

🔐 Endpoints de Autenticação (Atualizado 21/10/2025)

Login com Email e Senha

  • Endpoint: POST /auth/v1/token?grant_type=password
  • Netlify Function: /auth-login
  • Body: { "email": "usuario@exemplo.com", "password": "senha123" }
  • Resposta: { access_token, token_type: "bearer", expires_in: 3600, refresh_token, user: { id, email } }
  • Uso: Login tradicional com credenciais
  • Endpoint: POST /auth/v1/otp
  • Netlify Function: /auth-magic-link
  • Body: { "email": "usuario@exemplo.com" }
  • Resposta: 200 OK (email enviado)
  • Uso: Reenviar link de ativação ou login sem senha
  • Nota: create-user já envia magic link automaticamente na criação

Dados do Usuário Autenticado

  • Endpoint: GET /auth/v1/user
  • Netlify Function: /auth-user
  • Headers: Authorization: Bearer <access_token>
  • Resposta: { id, email, created_at }
  • Uso: Verificar sessão atual

Logout

  • Endpoint: POST /auth/v1/logout
  • Netlify Function: /auth-logout
  • Headers: Authorization: Bearer <access_token>
  • Resposta: 204 No Content
  • Uso: Encerrar sessão e invalidar tokens

🔄 Fluxo de Autenticação

  1. Login: Usuário envia email+senha → authService.loginPOST /auth-login
  2. Tokens: Resposta contém access_token (curto prazo) + refresh_token (longo prazo)
  3. Interceptor: Anexa Authorization: Bearer <access_token> + apikey em todas as requisições
  4. Refresh: Em 401, tenta renovar token automaticamente
  5. Enriquecimento: GET /user-info busca roles, profile e permissions completos

🆕 Criação de Usuário

Edge Function create-user executa:

  • Cria auth user
  • Cria profile
  • Atribui role
  • Envia magic link automaticamente
  • Opcionalmente cria registro em patients (se create_patient_record=true)

🔒 Motivos para Netlify Functions

  • Protege SUPABASE_ANON_KEY no backend
  • RLS controla acesso por auth.uid()
  • Evita exposição de credenciais no frontend

3. Modelo de Autorização & Roles

Roles previstas: admin, gestor, medico, secretaria, paciente, user.

Camadas:

  • Supabase Auth: autenticação e identidade (user.id).
  • PostgREST + RLS: enforcement de linha/coluna (ex: paciente só vê seus próprios registros; médico vê pacientes atribuídos / futuras policies).
  • Edge Functions: operações privilegiadas (criação de usuário composto; agregações que cruzam tabelas sensíveis).

Princípios:

  • Menor privilégio: roles específicas são anexadas à tabela user_roles / claim custom (via função user-info).
  • Expansão de permissões sempre via backend controlado (Edge ou admin interface separada).

4. Armazenamento de Tokens

Status revisado: Access Token agora em memória (via tokenStore), Refresh Token em sessionStorage (escopo aba). LocalStorage legado é migrado e limpo.

Motivações da mudança:

  • Reduz superfície de ataque para XSS persistente (access token não persiste após reload se atacante injeta script tardio).
  • Session scoping limita reutilização indevida do refresh token após fechamento total do navegador.

Persistência atual:

Tipo Local Expiração Natural
Access Token Memória JS exp claim (curto prazo)
Refresh Token sessionStorage exp claim / revogação backend
User Snapshot Memória JS Limpo em logout / reload opcional

Riscos remanescentes:

  • XSS ainda pode ler refresh token dentro da mesma aba.
  • Ataques supply-chain podem capturar tokens em runtime.

Mitigações planejadas:

  1. CSP + bloqueio de inline script não autorizado.
  2. Auditoria de dependências e lockfile imutável.
  3. (Opcional) Migrar refresh para cookie httpOnly + rotacionamento curto (exige backend/proxy).

Fallback / Migração:

  • Em primeira utilização o tokenStore migra chaves legacy (authToken, refreshToken, authUser) e remove do localStorage.

Operações:

  • tokenStore.setTokens(access, refresh?) atualiza memória e session.
  • tokenStore.clear() remove tudo (usado em logout e erro crítico de refresh).

Fluxo de Refresh:

  1. Requisição falha com 401.
  2. Wrapper (http.ts) obtém refresh do tokenStore.
  3. Se sucesso, novo par é salvo (access renovado em memória, refresh substituído em session).
  4. Se falha, limpeza e redirecionamento esperados pelo layer de UI.

Próximos passos (prioridade decrescente):

  1. Testes e2e validando não persistência pós reload sem refresh.
  2. Detecção de reuse (se Supabase expor sinalização) e invalidação proativa.
  3. Adicionar heurística antiflood de refresh (backoff exponencial).

5. Regras de Segurança no Banco (RLS)

Dependemos de Row Level Security para proteger dados. A aplicação pressupõe policies:

  • Tabelas de domínio (patients, doctors) filtradas por auth.uid() (ex: patient.id = auth.uid()).
  • Tabela de roles apenas legível para o próprio usuário e roles administrativas.
  • Operações de escrita restritas ao proprietário ou a roles privilegiadas.

Checklist a validar (fora do front): [] Policies para SELECT/INSERT/UPDATE/DELETE em cada tabela sensível. [] Policies específicas para evitar enumerar usuários (ex: profiles). [] Remoção de permissões públicas redundantes.


6. Edge Functions

Usadas para:

  • user-info: agrega roles + profile + permissões derivadas.
  • create-user: fluxo atômico de criação (signup + role + domínio) quando disponível.

Critérios para mover lógica para Edge:

  • Necessidade de Service Role Key (não pode ir ao front).
  • Lógica multi-tabela que exige atomicidade e validação adicional.
  • Redução de round-trips (performance e consistência).

7. Decisão: Proxy Backend (A Avaliar)

Status: NÃO implementado.

Quando justificar criar proxy:

Cenário Benefício do Proxy
Necessidade de Service Role Segredo fora do client
Orquestração complexa >1 função Transações / consistência
Rate limiting custom Proteção anti-abuso
Auditoria centralizada Logs correlacionados

Custos de um proxy:

  • Latência adicional.
  • Manutenção (deploy, uptime, patches de segurança).
  • Duplicação parcial de capacidades já cobertas por RLS.

Decisão atual: permanecer sem proxy até surgir necessidade concreta (service role / complexidade). Reavaliar trimestralmente.


8. Hardening do Cliente

Implementado:

  • Interceptor único normaliza erros e tenta 1 refresh controlado.
  • Remoção de tokens técnicos persistidos.
  • Remoção de senha do domínio (ex: MedicoCreate).

Planejado:

  • Content Security Policy estrita (nonce ou hashes para scripts inline).
  • Sanitização consistente para HTML dinâmico (não inserir dangerouslySetInnerHTML sem validação).
  • Substituir localStorage por memória + fallback volátil.
  • Feature Policy / Permissions Policy (desabilitar sensores não usados).
  • SRI (Subresource Integrity) para libs CDN (se adotadas no futuro).

9. Logging & Observabilidade

Diretrizes:

  • Nunca logar tokens ou refresh tokens.
  • Em produção, anonimizar IDs sensíveis onde possível (hash irreversível).
  • Separar logs de segurança (auth failures, tentativas repetidas) de logs de aplicação.

Próximo passo: Implementar adaptador de log (console wrapper) com níveis + redaction de padrões (regex para JWT / emails).


10. Tratamento de Erros

Wrapper http fornece shape padronizado ApiResponse<T>. Princípios:

  • Não propagar stack trace de servidor ao usuário final.
  • Exibir mensagem genérica em 5xx; detalhada em 4xx previsível (ex: validação).
  • Em 401 após falha de refresh -> limpar sessão e redirecionar login.

11. Ameaças Principais & Contramedidas

Ameaça Vetor Contramedida Atual Próximo Passo
XSS persistente Input não sanitizado Sem campos com HTML arbitrário CSP + sanitização + remover localStorage
Token theft XSS / extensão maliciosa Sem service role key Migrar tokens p/ memória
Enumeração de usuários Erros detalhados em login Mensagem genérica Rate limit + monitorar padrões
Escalada de privilégio Manipular roles client-side Roles derivadas no backend (user-info) Policies de atualização de roles estritas
Replay refresh token Interceptação TLS + troca de token no refresh Reduzir lifetime e detectar reuse

12. Roadmap de Segurança (Prioridade)

  1. (P1) Migrar tokens para memória + session fallback.
  2. (P1) Validar/Documentar RLS efetiva para cada tabela.
  3. (P2) Implementar logging redaction adapter.
  4. (P2) CSP + lint anti dangerouslySetInnerHTML.
  5. (P3) Mecanismo de invalidação global de sessão (revogar refresh em logout server-side se necessário).
  6. (P3) Testes automatizados de rota protegida (e2e smoke).

13. Serviços Atuais (Resumo)

Domínio Arquivo Observações
Autenticação authService.ts login, logout, refresh, user-info, getCurrentAuthUser
Médicos medicoService.ts CRUD + remoção de password do payload
Pacientes pacienteService.ts Listagem/CRUD com normalização
Roles userRoleService.ts list/assign/delete
Criação Usuário userCreationService.ts Edge first fallback manual
Relatórios (planejado) Pendende confirmar implementação real (reportService.ts)
Consultas (planejado) Padronizar nome tabela (consultas vs consultations)
SMS smsService.ts Placeholder

Arquivos legados/deprecados destinados a remoção após verificação de ausência de imports: consultaService.ts, relatorioService.ts, listarPacientes.*, pacientes.js, api.js.


14. Convenções de Código

  • DB snake_case -> front camelCase.
  • Limpeza de campos undefined antes de mutações (evita null overwrites).
  • Requisições POST/PUT/PATCH com Prefer: return=representation quando necessário.
  • ApiResponse: { success: boolean, data?: T, error?: string, message?: string }.

15. Scripts Básicos

Instalação:

pnpm install

Dev:

pnpm dev

Build:

pnpm build

16. Estrutura Simplificada

src/
  services/
  pages/
  components/
  entities/

17. Próximos Passos Técnicos (Geral)

  • Implementar serviços faltantes (reports/consultas) alinhados ao padrão http wrapper.
  • Testes unitários dos mapeadores (medico/paciente) e do fluxo de refresh.
  • Avaliar substituição de localStorage (Roadmap P1).
  • Revisar necessidade de proxy a cada trimestre (documentar decisão em CHANGELOG/ADR).

18. Desenvolvimento: Tipagem, Validação e Testes

18.1 Geração de Tipos a partir do OpenAPI

Arquivo da especificação parcial: docs/api/openapi.partial.json

Gerar (ou regenerar) os tipos TypeScript:

pnpm gen:api-types

Resultado: src/types/api.d.ts (não editar manualmente). Atualize o spec antes de regenerar.

Fluxo para adicionar/alterar endpoints:

  1. Editar openapi.partial.json (paths / schemas).
  2. Rodar pnpm gen:api-types.
  3. Ajustar services para usar novos tipos (components["schemas"]["<Nome>"]).
  4. Adicionar/atualizar validação Zod (se aplicável).
  5. Criar ou atualizar testes.

18.2 Schemas de Validação (Zod)

Arquivo central: src/validation/schemas.ts

Inclui:

  • loginSchema
  • patientInputSchema + mapper mapPatientFormToApi
  • doctorCreateSchema / doctorUpdateSchema
  • reportInputSchema + mapper mapReportFormToApi

Boas práticas:

  • Validar antes de chamar service.
  • Usar mapper para manter isolamento entre modelo de formulário e payload API (snake_case).
  • Adicionar novos schemas aqui ou dividir em módulos se crescer (ex: validation/patient.ts).

18.3 Testes (Vitest)

Config: vitest.config.ts

Scripts:

pnpm test        # execução única
pnpm test:watch  # modo watch

Suites atuais:

  • patient.mapping.test.ts: mapeamento form -> API
  • doctor.schema.test.ts: normalização de UF, campos obrigatórios
  • report.schema.test.ts: payload mínimo e erros

Adicionar novo teste:

  1. Criar arquivo em src/tests/*.test.ts.
  2. Importar schema/service a validar.
  3. Cobrir pelo menos 1 caso feliz e 1 caso de erro.

18.4 Padrões de Services

Cada service deve:

  • Usar tipos gerados (components["schemas"]) para payload/response quando possível.
  • Encapsular mapeamentos snake_case -> camelCase em funções privadas (ex: mapReport).
  • Limpar chaves com valor undefined antes de enviar (já adotado em pacientes/relatórios).
  • Emitir { success, data?, error? } uniformemente.

18.5 Endpoints de Arquivos (Foto / Anexos Paciente)

Formalizados na spec com uploads multipart/form-data:

  • /auth/v1/pacientes/{id}/foto (POST/DELETE)
  • /auth/v1/pacientes/{id}/anexos (GET/POST)
  • /auth/v1/pacientes/{id}/anexos/{anexoId} (DELETE)

Quando backend estabilizar response detalhado (ex: tipos MIME), atualizar schema PacienteAnexo e regenerar tipos.

18.6 Validação de CPF

Endpoint /pacientes/validar-cpf retorna schema ValidacaoCPF:

{
  "valido": boolean,
  "existe": boolean,
  "paciente_id": string | null
}

Integração: usar antes de criar paciente para alertar duplicidade.

18.7 Checklist ao Criar Novo Recurso

  1. Definir schema no OpenAPI (entrada + saída).
  2. Gerar tipos (pnpm gen:api-types).
  3. Criar service com wrappers padronizados.
  4. Adicionar Zod schema (form/input).
  5. Criar testes (mínimo: validação + mapeamento).
  6. Atualizar README (se conceito novo).
  7. Verificar se precisa RLS/policy nova no backend.

18.8 Futuro: Automação CI

Pipeline desejado:

  • Lint → Build → Test → (Gerar tipos e verificar diff do api.d.ts) → Deploy.
  • Falhar se docs/api/openapi.partial.json mudou sem api.d.ts regenerado.

19. Referência Rápida

Ação Comando
Instalar deps pnpm install
Dev server pnpm dev
Build pnpm build
Gerar tipos API pnpm gen:api-types
Rodar testes pnpm test
Testes em watch pnpm test:watch
Atualizar spec + tipos editar spec → pnpm gen:api-types

19.1 Acessibilidade (A11y)

Recursos implementados para melhorar usabilidade, leitura e inclusão:

Preferências do Usuário

Gerenciadas via hook useAccessibilityPrefs (localStorage, chave única accessibility-prefs). As opções persistem entre sessões e são aplicadas ao elemento <html> como classes utilitárias.

Preferência Chave interna Classe aplicada Efeito Principal
Tamanho da Fonte fontSize (inline style root) Escala tipográfica global
Modo Escuro darkMode dark Ativa tema dark Tailwind
Alto Contraste highContrast high-contrast Contraste forte (cores simplificadas)
Fonte Disléxica dyslexicFont dyslexic-font Aplica fonte OpenDyslexic (fallback legível)
Espaçamento Linhas lineSpacing line-spacing Aumenta line-height em blocos de texto
Reduzir Movimento reducedMotion reduced-motion Remove / suaviza animações não essenciais
Filtro Luz Azul lowBlueLight low-blue-light Tonalidade quente para conforto visual noturno
Modo Foco focusMode focus-mode Atenua elementos fora de foco (leitura seletiva)
Leitura de Texto textToSpeech (sem classe) TTS por hover (limite 180 chars)

Atalho de teclado: Alt + A abre/fecha o menu de acessibilidade. Esc fecha quando aberto.

Componente AccessibilityMenu

  • Dialog semântico com role="dialog", aria-modal="true", foco inicial e trap de tab.
  • Botões toggle com aria-pressed e feedback textual auxiliar.
  • Reset central limpa preferências e cancela síntese de fala ativa.

Formulários

  • Todos os campos críticos com id + label associada.
  • Atributos autoComplete coerentes (ex: email, name, postal-code, bday, new-password).
  • Padrões (pattern) e inputMode para CPF, CEP, telefone, DDD, números.
  • aria-invalid + mensagens condicionais (ex: confirmação de senha divergente).
  • Normalização para envio (CPF/telefone/cep) realizada no service antes do request (sem formatação).

Tabela de Pacientes

  • Usa scope="col" nos cabeçalhos, suporte dark mode, indicador VIP com aria-label.

Temas & CSS

Classes utilitárias adicionadas em index.css permitindo expansão futura sem alterar componentes. O design evita uso de inline style exceto na escala de fonte global, facilitando auditoria e CSP.

Boas Práticas Futuras

  1. Adicionar detecção automática de prefers-reduced-motion para estado inicial.
  2. Implementar fallback de TTS selecionável por foco + tecla (reduzir leitura acidental).
  3. Testes automatizados de acessibilidade (axe-core) e verificação de contraste.
  4. Suporte a aumentar espaçamento de letras (letter-spacing) opcional.

19.2 Testes de Acessibilidade & Fallback de Render (Status Temporário)

Resumo do Problema: Durante a criação de testes de interface para o AccessibilityMenu, o ambiente de testes (Vitest + jsdom e também happy-dom) deixou de materializar a árvore DOM de componentes React inclusive para um componente mínimo (<div>Hello</div>). Não houve erros de compilação nem warnings relevantes, apenas container.innerHTML === '' após render(...).

Hipóteses já investigadas (sem sucesso):

  • Troca de @vitejs/plugin-react-swc por @vitejs/plugin-react (padrão Babel) + pin de versão do Vite (5.4.10).
  • Alternância de ambiente (jsdom -> happy-dom).
  • Remoção/isolamento de ícones (lucide-react) e libs auxiliares (mock de @axe-core/react).
  • Render manual via createRoot e flush de microtasks.
  • Ajustes de transform / esbuild jsx automatic.

Decisão Temporária (para garantir “teste que funciona”):

  1. Marcar suites unitárias dependentes de render React como describe.skip enquanto a causa raiz é isolada.
  2. Introduzir um teste E2E real em browser (Puppeteer) que valida a funcionalidade essencial do menu.

Arquivos Impactados:

  • Skipped (com TODO):
    • src/__tests__/accessibilityMenu.semantic.test.tsx
    • src/__tests__/miniRender.test.tsx
    • src/__tests__/manualRootRender.test.tsx
  • Novo teste E2E:
    • src/__tests__/accessibilityMenu.e2e.test.ts

Script E2E:

pnpm test:e2e-menu

O teste:

  1. Sobe (ou reutiliza) o dev server Vite (porta 5173).
  2. Abre a SPA no Chromium headless.
  3. Clica no botão do menu de acessibilidade.
  4. Verifica presença do diálogo (role="dialog") e depois fecha.

Critério de Aceite Provisório: Enquanto o bug de render unitário persistir, a cobertura de comportamento crítico do menu é garantida pelo teste E2E (abre, foca, fecha). As preferências de acessibilidade continuam cobertas por testes unitários puros (sem render React) onde aplicável.

Próximos Passos para Retomar Testes Unitários:

  1. Criar reprodutor mínimo externo (novo repo) com dependências congeladas para confirmar se é interação específica local.
  2. Rodar pnpm ls --depth 0 e comparar versões de react, react-dom, @types/react, vitest, @vitejs/plugin-react.
  3. Forçar transpile isolado de um teste (vitest --run --no-threads --dom) para descartar interferência de thread pool.
  4. Se persistir, habilitar logs detalhados de Vite (DEBUG=vite:*) e inspecionar saída transformada de um teste simples.
  5. Reintroduzir gradativamente (mini -> menu) removendo mocks temporários.

Quando Corrigir:

  • Remover skips (describe.skip).
  • Reativar (opcional) auditoria axe-core com @axe-core/react.
  • Documentar causa raiz aqui (ex: conflito de plugin, polyfill global, etc.).

Risco Residual: Falhas específicas de acessibilidade sem cobertura E2E mais profunda (ex: foco cíclico em condições de teclado complexas) podem passar. Mitigação: expandir cenários E2E após estabilizar ambiente unitário.

Estado Atual: Fallback E2E ativo e validado. (Atualizar este bloco quando o pipeline unitário React estiver normalizado.)



18. ADRs (Decisões Arquiteturais) Resumidas

ID Decisão Status Justificativa
ADR-001 Sem proxy backend inicial Ativo RLS + Edge Functions suficientes agora
ADR-002 Tokens em memória + refresh em sessionStorage Ativo Redução de risco XSS mantendo simplicidade
ADR-003 Criação de usuário via Edge fallback manual Ativo Resiliência caso função indisponível

Registrar novas decisões futuras em uma pasta docs/adr.


19. Checklist de Release (Segurança)

[] Remover credenciais de desenvolvimento do README / código. [] Validar CSP ativa no ambiente (report-only -> enforce). [] Executar análise de dependências (npm audit / pnpm audit) e corrigir críticas. [] Verificar que nenhum token aparece em logs. [] Confirmar policies RLS completas.


20. Notas Finais

Este documento substitui versões anteriores e consolida segurança + operação. Atualize sempre que fluxos críticos mudarem (auth, roles, storage de tokens, Edge Functions novas).


Última atualização: (manter manualmente) 2025-10-03.


21. Logging Centralizado & Redaction

Implementado logger.ts substituindo gradualmente console.*.

Características:

  • Níveis: debug, info, warn, error.
  • Redação automática de:
    • Padrões de JWT (três segmentos base64url).
    • Campos com token, password, secret, email.
    • Emails em strings.
  • Nível dinâmico: produção => info+, demais => debug.

Uso:

import { logger } from 'src/services/logger';
logger.info('login success', { userId });

Práticas recomendadas:

  • Não logar payloads completos com PII.
  • Remover valores sensíveis antes de enviar para meta.
  • Usar error somente para falhas não recuperáveis ou que exigem telemetria.

Backlog de logging:

  • Adicionar transporte opcional (Sentry / Logtail).
  • Exportar métricas (Prometheus / OTEL) para 401s e latência.

Status adicional:

  • Mascaramento de CPF implementado (***CPF***XX).
  • Contador global de 401 consecutivos com limite (3) antes de limpeza forçada de sessão.

22. Política CSP (Rascunho)

Objetivo: mitigar XSS e exfiltração de contexto.

Cabeçalho sugerido (Report-Only inicial):

Content-Security-Policy-Report-Only: \
  default-src 'self'; \
  script-src 'self' 'strict-dynamic' 'nonce-<nonce-value>' 'unsafe-inline'; \
  style-src 'self' 'unsafe-inline'; \
  img-src 'self' data: blob:; \
  font-src 'self'; \
  connect-src 'self' https://*.supabase.co; \
  frame-ancestors 'none'; \
  base-uri 'self'; \
  form-action 'self'; \
  object-src 'none'; \
  upgrade-insecure-requests; \
  report-uri https://example.com/csp-report

Adoção:

  1. Aplicar em modo report-only (Netlify / edge) e coletar violações.
  2. Eliminar dependências inline e remover 'unsafe-inline'.
  3. Adicionar hashes/nonce definitivos.
  4. Migrar para modo enforce.

Complementos:

  • Lint contra dangerouslySetInnerHTML sem sanitização.
  • Biblioteca de sanitização (ex: DOMPurify) caso HTML dinâmico seja necessário.

23. Contador de 401 Consecutivos

Mecânica:

  • Cada resposta final 401 (sem refresh bem-sucedido) incrementa contador global.
  • Sucesso de requisição ou refresh resetam o contador.
  • Ao atingir 3, sessão é limpa (tokenStore.clear()) e próximo acesso exigirá novo login.

Racional: evitar loops silenciosos de requisições falhando e reduzir superfície de brute force de refresh.

Parâmetros:

  • Limite atual: 3 (configurável em src/services/authConfig.ts).

24. Verificação de Drift do OpenAPI

Script: pnpm check:api-drift

Fluxo CI recomendado:

  1. Rodar pnpm check:api-drift.
  2. Se falhar, forçar desenvolvedor a executar pnpm gen:api-types e commitar.

Implementação: gera tipos em memória via openapi-typescript e compara com src/types/api.d.ts normalizando quebras de linha.


25. Mascaramento de CPF no Logger

Padrão suportado: 11 dígitos com ou sem formatação (000.000.000-00). Saída: ***CPF***00 (mantendo apenas os dois últimos dígitos para correlação mínima).

Objetivo: evitar exposição de identificador completo em logs persistentes.