From f479dcde7d974cafa7f1a8a86c7c7e7e156a70b7 Mon Sep 17 00:00:00 2001 From: guisilvagomes Date: Fri, 24 Oct 2025 12:19:27 -0300 Subject: [PATCH] docs: atualiza README da pasta MEDICONNECT 2 - remove Netlify e atualiza arquitetura Cloudflare --- MEDICONNECT 2/README.md | 1145 ++++++++++++++++----------------------- 1 file changed, 476 insertions(+), 669 deletions(-) diff --git a/MEDICONNECT 2/README.md b/MEDICONNECT 2/README.md index 2394e02e1..de2ce5683 100644 --- a/MEDICONNECT 2/README.md +++ b/MEDICONNECT 2/README.md @@ -1,90 +1,292 @@ -## MEDICONNECT – Documentação Técnica e de Segurança +# MediConnect - Sistema de Agendamento Médico -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. +Aplicação SPA (React + Vite + TypeScript) consumindo **Supabase** (Auth, PostgREST) diretamente do frontend, hospedada no **Cloudflare Pages**. + +--- + +## 🚀 Acesso ao Sistema + +- **URL Principal:** https://mediconnectbrasil.app/ +- **URL Cloudflare:** https://mediconnect-5oz.pages.dev/ + +--- + +## 🏗️ Arquitetura Atual (Outubro 2025) + +``` +Frontend (Vite/React) → Supabase API + ↓ + Cloudflare Pages +``` + +**Mudança importante:** O sistema **não usa mais Netlify Functions**. Toda comunicação é direta entre frontend e Supabase via services (`authService`, `userService`, `patientService`, etc.). --- ## 🚀 Guias de Início Rápido -**Primeira vez rodando o projeto?** Escolha seu guia: +**Primeira vez rodando o projeto?** -- 📖 **[QUICK-START.md](./QUICK-START.md)** - Comandos rápidos (5 minutos) -- 📚 **[README-INSTALACAO.md](./README-INSTALACAO.md)** - Guia completo com troubleshooting -- 🚢 **[DEPLOY.md](./DEPLOY.md)** - Como fazer deploy no Netlify (produção) +### Instalação Rápida (5 minutos) -**Arquitetura da aplicação:** +```powershell +# 1. Instalar dependências +pnpm install -``` -Frontend (Vite/React) → Netlify Functions → Supabase API +# 2. Iniciar servidor de desenvolvimento +pnpm dev + +# 3. Acessar http://localhost:5173 ``` -As Netlify Functions protegem as credenciais do Supabase e funcionam como proxy/backend. +### Build e Deploy + +```powershell +# Build de produção +pnpm build + +# Deploy para Cloudflare +npx wrangler pages deploy dist --project-name=mediconnect --branch=production +``` + +📚 **Documentação completa:** Veja o [README principal](../README.md) com arquitetura, API e serviços. --- -## ⚠️ MUDANÇAS RECENTES NA API (21/10/2025) +## ⚠️ SISTEMA DE AUTENTICAÇÃO E PERMISSÕES -### Base de Dados Limpa +### Autenticação JWT com Supabase -**Todos os usuários, pacientes, laudos e agendamentos foram deletados.** Motivo: limpeza de dados inconsistentes e roles incorretos. +O sistema usa **Supabase Auth** com tokens JWT. Todo login retorna: +- `access_token` (JWT, expira em 1 hora) +- `refresh_token` (para renovação automática) -### Novas Permissões (RLS) +### Interceptors Automáticos + +```typescript +// Adiciona token automaticamente em todas as requisições +axios.interceptors.request.use(config => { + const token = localStorage.getItem('access_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +// Refresh automático quando token expira +axios.interceptors.response.use( + response => response, + async error => { + if (error.response?.status === 401) { + const refreshToken = localStorage.getItem('refresh_token') + const newTokens = await authService.refreshToken(refreshToken) + // Retry request original + } + } +) +``` + +### Roles e Permissões (RLS) + +#### 👑 Admin/Gestor: +- ✅ **Acesso completo a todos os recursos** +- ✅ Criar/editar/deletar usuários, médicos, pacientes +- ✅ Visualizar todos os agendamentos e prontuários #### 👨‍⚕️ 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** +- ✅ Podem agendar consultas #### 👩‍💼 Secretárias: - - ✅ Veem **todos os pacientes** - ✅ Veem **todos os agendamentos** - ✅ Veem **todos os laudos** +- ✅ Podem criar/editar agendamentos -#### 👑 Admins/Gestores: +--- -- ✅ **Acesso completo a tudo** +## 📡 API e Serviços -### Novos Endpoints de Criação (Atualizado 21/10 - tarde) +### Estrutura de Services -⚠️ **IMPORTANTE**: A API mudou! `create-doctor` e `create-patient` (REST) **NÃO ENVIAM MAGIC LINK** e **NÃO CRIAM AUTH USER**. +O projeto usa uma arquitetura de **services** que encapsulam toda comunicação com o Supabase: -**`create-user`** - Criação completa com autenticação (RECOMENDADO): +``` +src/services/ +├── api/ +│ ├── client.ts # Cliente HTTP (Axios configurado) +│ └── config.ts # Configurações da API +├── auth/ +│ ├── authService.ts # Login, signup, recuperação de senha +│ └── types.ts +├── users/ +│ └── userService.ts # getUserInfo, createUser, deleteUser +├── patients/ +│ └── patientService.ts # CRUD de pacientes +├── doctors/ +│ └── doctorService.ts # CRUD de médicos +├── appointments/ +│ └── appointmentService.ts # Agendamentos +└── availability/ + └── availabilityService.ts # Disponibilidade médica +``` -- 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** +### Principais Endpoints -**`create-doctor`** (Edge Function) - Criação de médico SEM autenticação: +#### 🔐 Autenticação (authService) -- 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 +```typescript +// Login +await authService.login({ email, password }) +// Retorna: { access_token, refresh_token, user } -**`POST /rest/v1/patients`** - Criação de paciente SEM autenticação: +// Recuperação de senha +await authService.requestPasswordReset(email) +// Envia email com link de reset -- 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 +// Atualizar senha +await authService.updatePassword(accessToken, newPassword) +// Usado na página /reset-password -**Quando usar cada endpoint:** +// Refresh token +await authService.refreshToken(refreshToken) +``` -- **`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) +#### 👤 Usuários (userService) + +```typescript +// Buscar informações do usuário autenticado (com roles) +const userInfo = await userService.getUserInfo() +// Retorna: { id, email, full_name, roles: ['medico', 'admin'] } + +// Criar usuário com role +await userService.createUser({ + email: 'user@example.com', + full_name: 'Nome Completo', + role: 'medico' // ou 'admin', 'paciente', 'secretaria' +}) + +// Deletar usuário +await userService.deleteUser(userId) +``` + +#### 🏥 Pacientes (patientService) + +```typescript +// Listar pacientes +const patients = await patientService.list() + +// Buscar por ID +const patient = await patientService.getById(id) + +// Criar paciente +await patientService.create({ + email: 'paciente@example.com', + full_name: 'Nome Paciente', + cpf: '12345678900', + phone_mobile: '11999999999' +}) + +// Atualizar paciente +await patientService.update(id, { phone_mobile: '11888888888' }) + +// Deletar paciente +await patientService.delete(id) +``` + +#### 👨‍⚕️ Médicos (doctorService) + +```typescript +// Listar médicos +const doctors = await doctorService.list() + +// Buscar por ID +const doctor = await doctorService.getById(id) + +// Buscar disponibilidade +const slots = await doctorService.getAvailableSlots(doctorId, date) +``` + +#### 📅 Agendamentos (appointmentService) + +```typescript +// Listar agendamentos (filtrado por role automaticamente) +const appointments = await appointmentService.list() + +// Criar agendamento +await appointmentService.create({ + patient_id: 'uuid-paciente', + doctor_id: 'uuid-medico', + scheduled_at: '2025-10-25T10:00:00', + reason: 'Consulta de rotina' +}) + +// Atualizar status +await appointmentService.updateStatus(id, 'confirmed') +// Status: requested, confirmed, completed, cancelled + +// Cancelar +await appointmentService.cancel(id, 'Motivo do cancelamento') +``` + +--- + +## 🔧 Configuração da API + +```typescript +// src/services/api/config.ts +export const API_CONFIG = { + SUPABASE_URL: "https://yuanqfswhberkoevtmfr.supabase.co", + SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + AUTH_URL: `${SUPABASE_URL}/auth/v1`, + REST_URL: `${SUPABASE_URL}/rest/v1`, + APP_URL: "https://mediconnectbrasil.app", + TIMEOUT: 30000, + STORAGE_KEYS: { + ACCESS_TOKEN: "mediconnect_access_token", + REFRESH_TOKEN: "mediconnect_refresh_token", + USER: "mediconnect_user" + } +} +``` + +--- + +## 🚀 Deploy no Cloudflare Pages + +### Via Wrangler CLI + +```powershell +# Build +pnpm build + +# Deploy para production +npx wrangler pages deploy dist --project-name=mediconnect --branch=production + +# Deploy com mudanças não commitadas +npx wrangler pages deploy dist --project-name=mediconnect --commit-dirty=true +``` + +### Configuração do Projeto + +- **Production Branch:** `production` +- **Build Command:** `pnpm build` +- **Build Output:** `dist` +- **Custom Domain:** `mediconnectbrasil.app` + +### URLs + +- **Production:** https://mediconnectbrasil.app/ +- **Preview:** https://mediconnect-5oz.pages.dev/ +- **Branch Preview:** https://[branch].mediconnect-5oz.pages.dev/ --- @@ -95,111 +297,181 @@ As Netlify Functions protegem as credenciais do Supabase e funcionam como proxy/ | `VITE_SUPABASE_URL` | Sim | URL base do projeto Supabase (`https://.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) | + +**Nota:** As variáveis já estão configuradas no código em `src/services/api/config.ts`. Não é necessário criar arquivo `.env` para desenvolvimento local. Boas práticas: - Nunca exponha Service Role Key no frontend. -- Não comitar `.env` – usar `.env.example` como referência. +- Não comitar `.env` – usar `.env.example` como referência (se houver). --- -## 2. Arquitetura de Autenticação +## 2. Fluxo de Login e Validação de Roles -### 🔐 Endpoints de Autenticação (Atualizado 21/10/2025) +### Exemplo: Login de Médico -#### **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 - -#### **Magic Link (Login sem Senha)** - -- **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 ` -- **Resposta**: `{ id, email, created_at }` -- **Uso**: Verificar sessão atual - -#### **Logout** - -- **Endpoint**: `POST /auth/v1/logout` -- **Netlify Function**: `/auth-logout` -- **Headers**: `Authorization: Bearer ` -- **Resposta**: `204 No Content` -- **Uso**: Encerrar sessão e invalidar tokens - -### 🔄 Fluxo de Autenticação - -1. **Login**: Usuário envia email+senha → `authService.login` → `POST /auth-login` -2. **Tokens**: Resposta contém `access_token` (curto prazo) + `refresh_token` (longo prazo) -3. **Interceptor**: Anexa `Authorization: Bearer ` + `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 +```typescript +// pages/LoginMedico.tsx +const handleLogin = async (e: FormEvent) => { + e.preventDefault() + + try { + // 1. Autenticar com Supabase + const loginResponse = await authService.login({ email, password }) + + // 2. Salvar tokens + localStorage.setItem('access_token', loginResponse.access_token) + localStorage.setItem('refresh_token', loginResponse.refresh_token) + + // 3. Buscar informações do usuário com roles + const userInfo = await userService.getUserInfo() + const roles = userInfo.roles || [] + + // 4. Validar permissões + const isAdmin = roles.includes('admin') + const isGestor = roles.includes('gestor') + const isMedico = roles.includes('medico') + + if (!isAdmin && !isGestor && !isMedico) { + toast.error("Você não tem permissão para acessar esta área") + await authService.logout() + return + } + + // 5. Redirecionar para o painel + navigate('/painel-medico') + + } catch (error) { + toast.error("Email ou senha incorretos") + } +} +``` --- -## 3. Modelo de Autorização & Roles +## 3. Recuperação de Senha -Roles previstas: `admin`, `gestor`, `medico`, `secretaria`, `paciente`, `user`. +### Fluxo Completo -Camadas: +1. **Usuário clica em "Esqueceu a senha?"** +2. **Sistema envia email com link de recuperação** +3. **Usuário clica no link → redireciona para `/reset-password`** +4. **Sistema extrai token do URL (#access_token=...)** +5. **Usuário define nova senha** +6. **Sistema atualiza senha e redireciona para login** -- 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). +### Implementação -Princípios: +```typescript +// Solicitar recuperação (páginas de login) +const handlePasswordReset = async () => { + try { + await authService.requestPasswordReset(email) + toast.success("Link de recuperação enviado para seu email") + } catch (error) { + toast.error("Erro ao enviar email de recuperação") + } +} -- 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). +// Página de reset (ResetPassword.tsx) +useEffect(() => { + // Extrair token do URL + const hash = window.location.hash + const params = new URLSearchParams(hash.substring(1)) + const token = params.get('access_token') + + if (token) { + setAccessToken(token) + setIsLoading(false) + } +}, []) + +const handleSubmit = async (e: FormEvent) => { + try { + await authService.updatePassword(accessToken, newPassword) + toast.success("Senha atualizada com sucesso!") + navigate('/login-paciente') + } catch (error) { + toast.error("Erro ao atualizar senha") + } +} +``` --- -## 4. Armazenamento de Tokens +## 5. Estrutura do Banco de Dados -Status revisado: Access Token agora em memória (via `tokenStore`), Refresh Token em `sessionStorage` (escopo aba). LocalStorage legado é migrado e limpo. +### Tabelas Principais -Motivações da mudança: +#### `profiles` +```sql +- id (uuid, PK) +- email (text, unique) +- full_name (text) +- phone (text) +- created_at (timestamp) +- updated_at (timestamp) +``` -- 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. +#### `patients` +```sql +- id (uuid, PK, FK -> profiles) +- email (text, unique) +- full_name (text) +- cpf (text, unique) +- phone_mobile (text) +- birth_date (date) +- address (text) +- created_at (timestamp) +``` + +#### `doctors` +```sql +- id (uuid, PK, FK -> profiles) +- email (text, unique) +- full_name (text) +- specialty (text) +- crm (text, unique) +- phone (text) +- created_at (timestamp) +``` + +#### `appointments` +```sql +- id (uuid, PK) +- patient_id (uuid, FK -> patients) +- doctor_id (uuid, FK -> doctors) +- scheduled_at (timestamp) +- status (enum: requested, confirmed, completed, cancelled) +- reason (text) +- notes (text) +- created_at (timestamp) +``` + +#### `user_roles` +```sql +- user_id (uuid, FK -> profiles) +- role (text: admin, gestor, medico, secretaria, paciente) +- created_at (timestamp) +``` + +--- + +## 6. Armazenamento de Tokens + +O sistema usa uma estratégia híbrida para máxima segurança: -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 | +| Access Token | localStorage | 1 hora (renovado automaticamente) | +| Refresh Token | localStorage | 30 dias (ou revogação backend) | +| User Snapshot | localStorage | Limpo em logout | + +**Segurança:** +- Tokens são limpos automaticamente no logout +- Refresh automático quando access_token expira (401) +- Interceptors garantem tokens válidos em todas as requisições Riscos remanescentes: @@ -225,587 +497,122 @@ 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) +## 7. Scripts Utilitários -Dependemos de Row Level Security para proteger dados. A aplicação pressupõe policies: +### Gerenciamento de Usuários -- 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. +```bash +# Listar todos os usuários +node scripts/manage-users.js list -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. +# Criar usuário +node scripts/manage-users.js create email@example.com "Nome Completo" ---- +# Deletar usuário +node scripts/manage-users.js delete user-id -## 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`. -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 +# Limpar usuários de teste +node scripts/cleanup-users.js ``` -Dev: +### Testes de API -``` -pnpm dev -``` +```bash +# Testar recuperação de senha +node test-password-recovery.js -Build: +# Criar usuário Fernando (exemplo) +node create-fernando.cjs -``` -pnpm build +# Buscar usuário Fernando +node search-fernando.cjs ``` --- -## 16. Estrutura Simplificada +## 8. Padrões de Código -``` -src/ - services/ - pages/ - components/ - entities/ -``` +### Nomenclatura ---- +- **Componentes:** PascalCase (`LoginPaciente.tsx`) +- **Serviços:** camelCase (`authService.ts`) +- **Hooks:** camelCase com prefixo `use` (`useAuth.ts`) +- **Tipos:** PascalCase (`LoginInput`, `AuthUser`) -## 17. Próximos Passos Técnicos (Geral) +### Estrutura de Componentes -- 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). +```typescript +// Imports +import React, { useState, useEffect } from "react" +import { useNavigate } from "react-router-dom" +import { serviceImport } from "../services" ---- - -## 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"][""]`). -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 +// Types +interface Props { + // ... } + +// Component +const ComponentName: React.FC = ({ ...props }) => { + // Hooks + const navigate = useNavigate() + const [state, setState] = useState() + + // Effects + useEffect(() => { + // ... + }, []) + + // Handlers + const handleAction = async () => { + // ... + } + + // Render + return ( +
+ {/* JSX */} +
+ ) +} + +export default ComponentName ``` -Integração: usar antes de criar paciente para alertar duplicidade. +--- -### 18.7 Checklist ao Criar Novo Recurso +## 9. Tecnologias Utilizadas -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. +### Frontend +- **React** 18.3.1 - Biblioteca UI +- **TypeScript** 5.9.3 - Tipagem estática +- **Vite** 7.1.10 - Build tool +- **React Router** 6.30.1 - Roteamento +- **Tailwind CSS** 3.4.17 - Estilização +- **Axios** 1.12.2 - Cliente HTTP +- **React Hot Toast** 2.4.1 - Notificações +- **date-fns** 4.1.0 - Manipulação de datas -### 18.8 Futuro: Automação CI +### Backend +- **Supabase** - Backend as a Service +- **PostgreSQL** - Banco de dados relacional +- **Supabase Auth** - Autenticação JWT -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. +### Deploy +- **Cloudflare Pages** - Hospedagem frontend +- **Wrangler** 4.44.0 - CLI Cloudflare --- -## 19. Referência Rápida +## 10. Suporte e Contato -| 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` | +- **Equipe:** Squad 18 - Rise Up +- **Repositório:** https://git.popcode.com.br/RiseUP/riseup-squad18.git +- **Trello:** [Squad 18 - Idealização/Planejamento](https://trello.com/b/CCl3Azxk/squad-18-idealizacao-planejamento) --- -## 19.1 Acessibilidade (A11y) +**Desenvolvido com ❤️ pela Squad 18** -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 `` 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 (`
Hello
`). 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-' '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.