From 508ffe502216ba794e25abc1438a8f39bf9442ef Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Thu, 16 Oct 2025 20:55:48 -0300 Subject: [PATCH] =?UTF-8?q?chore:=20trust=20host=20header=20e=20valida?= =?UTF-8?q?=C3=A7=C3=A3o=20centralizada=20de=20dom=C3=ADnios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents.md | 337 +++++++++++++++--------------------- docs/DEV.md | 2 +- middleware.ts | 14 +- next.config.ts | 5 +- src/config/allowed-hosts.ts | 20 +++ 5 files changed, 173 insertions(+), 205 deletions(-) create mode 100644 src/config/allowed-hosts.ts diff --git a/agents.md b/agents.md index 920a7dc..3f56060 100644 --- a/agents.md +++ b/agents.md @@ -1,217 +1,158 @@ -# Plano de Desenvolvimento — Sistema de Chamados +# Plano de Desenvolvimento — Sistema de Chamados + +> **Diretriz máxima**: documentação, comunicação e respostas sempre em português brasileiro. + +## Contatos +- **Esdras Renan** — monkeyesdras@gmail.com -> **Diretriz máxima:** todas as respostas, comunicações e documentações devem ser redigidas em português brasileiro. - -## Contato principal -- **Esdras Renan** — monkeyesdras@gmail.com - ## Credenciais padrão (Better Auth) -- Administrador: `admin@sistema.dev` / `admin123` -- Agente Demo: `agente.demo@sistema.dev` / `agent123` -- Cliente Demo: `cliente.demo@sistema.dev` / `cliente123` -> Execute `pnpm auth:seed` após configurar `.env`. O script atualiza as contas acima ou cria novas conforme variáveis `SEED_USER_*`. - -## Sincronização com Convex -- Usuários e tickets demo são garantidos via `convex/seed.ts`. -- Após iniciar `pnpm convex:dev`, acesse `/dev/seed` uma vez por ambiente local para carregar dados reais de demonstração no banco do Convex. - -## Setup local rápido +| Papel | Usuário | Senha | +| --- | --- | --- | +| Administrador | `admin@sistema.dev` | `admin123` | +| Agente Demo | `agente.demo@sistema.dev` | `agent123` | +| Cliente Demo | `cliente.demo@sistema.dev` | `cliente123` | + +> Execute `pnpm auth:seed` após configurar `.env` para (re)criar os usuários acima (campos `SEED_USER_*` podem sobrescrever credenciais). + +## Backend Convex +- Seeds de usuários/tickets demo: `convex/seed.ts`. +- Para DEV: rode `pnpm convex:dev` e acesse `/dev/seed` uma vez para popular dados realistas. + +## Stack atual (16/10/2025) +- **Next.js**: `16.0.0-beta.0` (Turbopack em produção + cache de filesystem em DEV). + - `next.config.ts` mantém `experimental.turbopackFileSystemCacheForDev` e `experimental.trustHostHeader`; a lista de domínios válidos mora em `src/config/allowed-hosts.ts` e é aplicada pelo `middleware.ts`. +- **React / React DOM**: `19.2.0`. +- **Trilha de testes**: Vitest (`pnpm test`) sem modo watch por padrão (`--run --passWithNoTests`). +- **CI**: workflow `Quality Checks` (`.github/workflows/quality-checks.yml`) roda `pnpm install`, `prisma:generate`, `lint`, `test`, `build`. Variáveis críticas (`BETTER_AUTH_SECRET`, `NEXT_PUBLIC_APP_URL`, etc.) são definidas apenas no runner — não afetam a VPS. +- **Deploy**: pipeline `ci-cd-web-desktop.yml` (runner self-hosted). Build roda com pnpm 9, Node 20. Web é publicado em `/home/renan/apps/sistema` e o Swarm aponta `sistema_web` para essa pasta. + +## Setup local (atualizado) 1. `pnpm install` -2. Ajuste `.env` (ou crie a partir do exemplo) e confirme `NEXT_PUBLIC_CONVEX_URL` apontando para o Convex local. +2. Copie `.env.example` → `.env.local`. + - Principais variáveis para DEV: + ``` + NODE_ENV=development + NEXT_PUBLIC_APP_URL=http://localhost:3000 + BETTER_AUTH_URL=http://localhost:3000 + BETTER_AUTH_SECRET=dev-only-long-random-string + NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 + DATABASE_URL=file:./prisma/db.dev.sqlite + ``` 3. `pnpm auth:seed` 4. (Opcional) `pnpm queues:ensure` 5. `pnpm convex:dev` 6. Em outro terminal: `pnpm dev` +7. Acesse `http://localhost:3000` e valide login com os usuários padrão. -## App Desktop (Agente de Máquinas) -- Código: `apps/desktop` (Tauri v2 + Vite). -- Padrões de URL: - - Produção: usa `https://tickets.esdrasrenan.com.br` por padrão (fallback em release). - - Desenvolvimento: use `apps/desktop/.env` (ver `.env.example`). -- Comandos úteis: - - `pnpm -C apps/desktop tauri dev` — dev completo (abre WebView em 1420 + backend Rust). - - `pnpm -C apps/desktop build` — build do frontend (dist). - - `pnpm -C apps/desktop tauri build` — gera instaladores (bundle) por SO. -- Saída dos pacotes: `apps/desktop/src-tauri/target/release/bundle/`. -- Fluxo atualizado: - 1) Coleta perfil (hostname/OS/MAC/seriais/métricas). - 2) Provisiona via `POST /api/machines/register` com `MACHINE_PROVISIONING_SECRET`, solicitando o **perfil de acesso** (Colaborador ou Gestor) e os dados do usuário associado. O backend garante a vinculação única da máquina ao colaborador ou gestor informado. - 3) Envia heartbeats a cada 5 min para `/api/machines/heartbeat` com inventário básico + estendido (discos, GPUs, serviços, softwares). - 4) Abre `APP_URL/machines/handshake?token=...&redirect=...` para autenticar a sessão: colaboradores são direcionados ao portal (`/portal`), gestores ao painel completo (`/dashboard`). A rota de handshake é pública no middleware para permitir a criação da sessão sem login prévio. -- Segurança: token salvo no cofre do SO (Keyring). Store guarda apenas metadados não sensíveis. -- Endpoint extra: `POST /api/machines/inventory` (atualiza inventário por token ou provisioningSecret). -- Atualizações automáticas: o plugin `@tauri-apps/plugin-updater` verifica `latest.json` nos releases do GitHub. Publicar uma nova release com manifestos atualiza os clientes sem reinstalação manual. -- Ajustes administrativos: em **Admin ▸ Máquinas** é possível vincular ou alterar o perfil (colaborador/gestor) e e-mail associado através do botão “Ajustar acesso”. +### Banco de dados +- Local (DEV): `DATABASE_URL=file:./prisma/db.dev.sqlite` (guardado em `prisma/prisma/`). +- Produção: SQLite persistido no volume Swarm `sistema_sistema_db`. Migrations em PROD devem apontar para esse volume (ver `docs/DEPLOY-RUNBOOK.md`). + +### Verificações antes de PR/deploy +```bash +pnpm lint +pnpm test +pnpm build +``` + +## Aplicativo Desktop (Tauri) +- Código-fonte: `apps/desktop` (Tauri v2 + Vite + React 19). +- URLs: + - Produção: `https://tickets.esdrasrenan.com.br` + - DEV: configure `apps/desktop/.env` (exemplo fornecido). +- Comandos: + - `pnpm -C apps/desktop tauri dev` — desenvolvimento (porta 1420). + - `pnpm -C apps/desktop tauri build` — gera instaladores. +- **Fluxo do agente**: + 1. Coleta perfil da máquina (hostname, OS, MAC, seriais, métricas). + 2. Provisiona via `POST /api/machines/register` usando `MACHINE_PROVISIONING_SECRET`, informando perfil de acesso (Colaborador/Gestor) + dados do colaborador. + 3. Envia heartbeats periódicos (`/api/machines/heartbeat`) com inventário básico + estendido (discos SMART, GPUs, serviços, softwares, CPU window). + 4. Realiza handshake em `APP_URL/machines/handshake?token=...&redirect=...` para receber cookies Better Auth + sessão (colaborador → `/portal`, gestor → `/dashboard`). + 5. Token persistido no cofre do SO (Keyring); store guarda apenas metadados. + 6. Envio manual de inventário via botão (POST `/api/machines/inventory`). + 7. Updates automáticos: plugin `@tauri-apps/plugin-updater` consulta `latest.json` publicado nos releases do GitHub. +- **Admin ▸ Máquinas**: permite ajustar perfil/email associado, visualizar inventário completo e remover máquina. ### Sessão "machine" no frontend -- Ao autenticar como `machine`, o frontend consulta `/api/machines/session` e popula `machineContext` (assignedUserId, email, name, persona). -- Mesmo quando `/api/auth/get-session` retorna `null` na WebView, o portal passa a derivar a role a partir do `machineContext` e utiliza `assignedUserId` como `viewerId` — assim o colaborador consegue abrir chamados via desktop. -- Na UI interna, o menu do usuário (canto inferior do sidebar) oculta o botão "Encerrar sessão" quando a sessão é de máquina (ou quando `machineContext` está presente). -- Página de diagnóstico: `/portal/debug` exibe o status/JSON de `get-session` e `machines/session` com os mesmos cookies da aba. +- Ao autenticar como máquina, o front chama `/api/machines/session`, popula `machineContext` (assignedUser*, persona) e deriva role/`viewerId`. +- Mesmo quando `get-session` é `null` na WebView, o portal utiliza `machineContext` para saber o colaborador/gestor logado. +- UI remove opção "Sair" no menu do usuário quando detecta sessão de máquina. +- `/portal/debug` exibe JSON de `get-session` e `machines/session` (útil para diagnosticar cookies/bearer). -### Sinalizador de desktop (opcional – futuro) -- Podemos adicionar um cookie (ex.: `desktop_shell=1`) no handshake para diferenciar acessos do app desktop de acessos web convencionais. -- Esse cookie permitiria customizações de UI específicas (ex.: ocultar "Sair" apenas no desktop) sem depender de heurísticas do ambiente. +### Observações adicionais +- Planejamos usar um cookie `desktop_shell` no futuro para diferenciar acessos do desktop vs navegador (não implementado). -## Desenvolvimento local — boas práticas (atualizado) -- Ambientes separados: mantenha seu `.env.local` só para DEV e o `.env` da VPS só para PROD. Nunca commitar arquivos `.env`. -- Convex em DEV: rode `pnpm convex:dev` e aponte o front para `http://127.0.0.1:3210` via `NEXT_PUBLIC_CONVEX_URL`. -- Banco local: por padrão `DATABASE_URL=file:./prisma/db.sqlite`. Se quiser isolar por projeto, use `db.dev.sqlite`. -- Seeds em DEV: use `pnpm auth:seed` (usuários Better Auth) e acesse `/dev/seed` uma vez para dados de demonstração do Convex. -- Seeds em PROD: só quando realmente necessário; não fazem parte do deploy automático. +## Qualidade e testes +- **Lint**: `pnpm lint` (ESLint flat config). +- **Testes unitários/integrados (Vitest)**: + - Cobertura atual inclui utilitários (`tests/*.test.ts`), rotas `/api/machines/*` e `sendSmtpMail`. + - Executar `pnpm test -- --watch` apenas quando precisar de modo interativo. +- **Build**: `pnpm build` (`next build --turbopack`). +- **CI**: falhas mais comuns + - `ERR_PNPM_OUTDATED_LOCKFILE`: mantenha `pnpm-lock.yaml` atualizado (principalmente após alterar dependências do desktop). + - Variáveis Better Auth ausentes (`BETTER_AUTH_SECRET`): definidas no workflow (`Quality Checks`). + - Falha de host (Next 16): confira `src/config/allowed-hosts.ts`; o middleware retorna 403 quando o domínio do Traefik não está listado. -### Exemplo de `.env.local` (DEV) -``` -# Base do app -NODE_ENV=development -NEXT_PUBLIC_APP_URL=http://localhost:3000 -BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET=dev-only-long-random-string +## Produção / Deploy +- Runner self-hosted (VPS). Build roda fora de `/srv/apps/sistema` e rsync publica em `/home/renan/apps/sistema`. +- Swarm: `stack.yml` monta `/home/renan/apps/sistema.current` → `/app` (via symlink). +- Para liberar novo release manualmente: + ```bash + ln -sfn /home/renan/apps/sistema.build. /home/renan/apps/sistema.current + docker service update --force sistema_web + ``` +- Resolver `P3009` (migration falhou) sempre no volume `sistema_sistema_db`: + ```bash + docker service scale sistema_web=0 + docker run --rm -it -e DATABASE_URL=file:/app/data/db.sqlite \ + -v /home/renan/apps/sistema.current:/app \ + -v sistema_sistema_db:/app/data -w /app \ + node:20-bullseye bash -lc 'corepack enable; corepack prepare pnpm@9 --activate; pnpm i --no-frozen-lockfile; pnpm exec prisma migrate resolve --rolled-back ; pnpm exec prisma migrate deploy' + docker service scale sistema_web=1 + ``` -# Convex (DEV) -NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 - -# Banco local (Prisma) -DATABASE_URL=file:./prisma/db.sqlite - -# SMTP de desenvolvimento (ex.: Mailpit) -SMTP_ADDRESS=localhost -SMTP_PORT=1025 -SMTP_TLS=false -SMTP_USERNAME= -SMTP_PASSWORD= -MAILER_SENDER_EMAIL="Dev " - -# (Opcional) OAuth DEV – não usado por padrão neste projeto -GITHUB_CLIENT_ID= -GITHUB_CLIENT_SECRET= -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -``` - -Observações: -- `COOKIE_DOMAIN` não é necessário em DEV neste projeto. -- Variáveis de provisionamento de máquinas (`MACHINE_PROVISIONING_SECRET`, etc.) só se você for testar as rotas de máquinas/inventário. - -### Passo a passo local -1) `pnpm install` -2) `pnpm prisma:generate` -3) `pnpm convex:dev` (terminal A) -4) `pnpm dev` (terminal B) -5) (Opcional) `pnpm auth:seed` e visitar `http://localhost:3000/dev/seed` - -## Deploy via GitHub Actions (produção) -- Fluxo: `git push main` ⇒ runner self‑hosted na VPS sincroniza código e aplica o stack (Traefik/Swarm) sem derrubar o serviço (start-first). -- Disparo do deploy web: apenas quando há mudanças em arquivos do app (src/, public/, prisma/, next.config.ts, package.json, pnpm-lock.yaml, tsconfig.json, middleware.ts, stack.yml). -- Disparo do deploy Convex: apenas quando há mudanças em `convex/**`. -- O `.env` da VPS é preservado; caches do servidor (`node_modules`, `.pnpm-store`) não são tocados. -- Smoke de provisionamento (`/api/machines/register` + heartbeat) roda só se `RUN_MACHINE_SMOKE=true` (default: desativado para evitar quedas em caso de instabilidade). -- Banco Prisma (SQLite) persiste em volume nomeado (`sistema_db`); não é recriado a cada deploy. - -## Bancos e seeds — DEV x PROD -- DEV: use os seeds à vontade (usuários com `pnpm auth:seed`, dados demo do Convex em `/dev/seed`). -- PROD: evite seeds automáticos; para criar um admin use `SEED_USER_*` e `pnpm auth:seed` em um container Node efêmero. -- Alterações de schema: sempre via migrações (`prisma migrate`). O CI aplica `migrate deploy` no start do container web. - -## Dicas rápidas -- Imagens em `public/`: trocou o arquivo → push. Para bust de cache, versionar o nome (ex.: `logo.v2.png`) ou usar query (`?v=sha`). -- Problemas de permissão de build: garanta que `.next` pertence ao usuário do runner (se necessário, remover `.next` no host e rebuildar). -- Se precisar inspecionar/backup do SQLite em PROD, prefira um bind dedicado (`/srv/apps/sistema-data:/app/data`) ou use `docker run -v sistema_db:/data` para copiar o arquivo. - -## Checklist para novo computador -1. Instale Node.js 20+ e habilite o Corepack (`corepack enable`) para usar o `pnpm`. -2. Garanta o `pnpm` atualizado (`corepack prepare pnpm@latest --activate`) antes de clonar o repositório. -3. Clone o projeto: `git clone git@github.com:esdrasrenan/sistema-de-chamados.git` e entre na pasta. -4. Copie o arquivo `.env` já configurado do computador atual para a raiz do repositório (nunca faça commit desse arquivo). -5. Instale as dependências com `pnpm install`. -6. Gere os clientes locais necessários: `pnpm prisma:generate`. -7. Semeie as credenciais Better Auth: `pnpm auth:seed`. -8. Se for trabalhar com filas padrão, execute `pnpm queues:ensure`. -9. Inicie o backend Convex em um terminal (`pnpm convex:dev`) e, em outro, suba a aplicação Next.js (`pnpm dev`). -10. Acesse `http://localhost:3000` e teste login com os usuários padrão listados acima antes de continuar o desenvolvimento. - -## Estado atual -- Autenticação Better Auth com guardas client-side (`AuthGuard`) bloqueando rotas protegidas. -- Menu de usuário (rodapé da sidebar) concentra acesso às configurações ("Meu perfil" → `/settings`) e logout. Removemos o item redundante "Configurações" do menu lateral. -- Formulários de novo ticket (dialog, página e portal) com seleção de responsável, placeholders claros e validação obrigatória de assunto/descrição/categorias. -- Relatórios, dashboards e páginas administrativas utilizam `AppShell`, garantindo header/sidebar consistentes. - - Use `SiteHeader` no `header` do `AppShell` para título/lead e ações. - - O conteúdo deve ficar dentro de `
`. - - Persistir filtro global de empresa com `usePersistentCompanyFilter` (localStorage) para manter consistência entre relatórios. - -## Entregas recentes -- Exportações CSV (Backlog, Canais, CSAT, SLA e Horas por cliente) com parâmetros de período. -- PDF do ticket (via pdfkit standalone), com espaçamento e traduções PT-BR. -- Play interno/externo com somatório por tipo por ticket e relatório por cliente. -- Admin > Empresas & clientes: cadastro/edição, `Cliente avulso?` e `Horas contratadas/mês`. +## Estado do portal / app web +- Autenticação Better Auth com `AuthGuard`. +- Sidebar inferior agrega avatar, link para `/settings` e logout (oculto em sessões de máquina). +- Formulários de ticket (novo/editar/comentários) usam editor rico + anexos; placeholders e validação PT-BR. +- Relatórios e painéis utilizam `AppShell` + `SiteHeader`. +- `usePersistentCompanyFilter` mantém filtro global de empresa em relatórios/admin. +- Exportações CSV: backlog, canais, CSAT, SLA, horas (rotas `/api/reports/*.csv`). +- PDF do ticket (`/api/tickets/[id]/export/pdf`). +- Play interno/externo com métricas por tipo. +- Admin > Empresas: cadastro + “Cliente avulso?”, horas contratadas, vínculos de usuários. - Admin > Usuários: vincular colaborador à empresa. -- Dashboard: cards de filas (Chamados/Laboratório/Visitas) e indicadores principais. -- Lista de tickets: filtro por Empresa, coluna Empresa, alinhamento vertical e melhor espaçamento entre colunas. - -## Entregas recentes relevantes -- Sessão de máquina confiável no desktop: CORS com credenciais habilitado, aplicação de múltiplos cookies via `NextResponse.cookies.set(...)`, fallback no portal para usar `machineContext` quando `get-session` for `null`. -- Portal (cliente): esconder Fila/Prioridade, listar apenas tickets do solicitante, editor rico + anexos nos comentários, botão “Sair” oculto no desktop. -- Convex: permissão de comentário para solicitante corrigida (primeiro comentário público após criação do ticket). -- Desktop (Windows): fallback para preencher `extended.windows.osInfo` via `sysinfo` quando o PowerShell retornar vazio. -- Correção do redirecionamento após logout evitando retorno imediato ao dashboard. -- Validações manuais dos formulários de rich text para eliminar `ZodError` durante edição. -- Dropdown de responsáveis na criação de tickets com preenchimento automático pelo autor e evento inicial de comentário. -- Indicadores visuais de campos obrigatórios e botão "Novo ticket" funcional no cabeçalho do detalhe. -- Seeds (Better Auth e Convex) ampliados para incluir agente e cliente de teste. - -## Fluxos suportados - -### Equipe interna (admin/agent/collaborator) -- Criar tickets com categorias, responsável inicial e anexos. -- Abrir novos tickets diretamente a partir do detalhe via dialog reutilizável. -- Acessar `/settings` para ajustes pessoais e efetuar logout pelo menu. - -### Papéis -- Papéis válidos: `admin`, `manager`, `agent`, `collaborator` (papel `customer` removido). -- Colaboradores acessam o portal (`/portal`) e visualizam apenas os próprios tickets; gestores herdam a visão completa da empresa mesmo quando autenticados via agente desktop. -- Gestores veem os tickets da própria empresa e só podem registrar comentários públicos. - -## Próximos passos sugeridos -1. Disparo de e-mails automáticos quando uso de horas ≥ 90% do contratado. -2. Ações rápidas (status/fila) diretamente na listagem de tickets. -3. Limites e monitoramento para anexos por tenant. -4. PDF do ticket com layout idêntico ao app (logo/cores/fontes). +- Dashboard: cards por fila e indicadores principais. -## Referências de endpoints úteis -- Backlog CSV: `/api/reports/backlog.csv?range=7d|30d|90d[&companyId=...]` -- Canais CSV: `/api/reports/tickets-by-channel.csv?range=7d|30d|90d[&companyId=...]` -- CSAT CSV: `/api/reports/csat.csv?range=7d|30d|90d` -- SLA CSV: `/api/reports/sla.csv` -- Horas por cliente CSV: `/api/reports/hours-by-client.csv?range=7d|30d|90d` - -## Referências de inventário de máquinas -- UI (Admin > Máquinas): filtros, pesquisa, inventário enriquecido (GPUs, discos, serviços) e exclusão de máquina — ver docs/admin-inventory-ui.md -- Endpoints do agente: +## Fluxos suportados +- **Equipe interna** (`admin`, `agent`, `collaborator`): cria/acompanha tickets, comenta, altera status/fila, gera relatórios. +- **Gestores** (`manager`): visualizam tickets da empresa, comentam publicamente, acessam dashboards. +- **Colaboradores** (`collaborator`): portal (`/portal`), tickets próprios, comentários públicos, editor rico, anexos. +- **Sessão Máquina**: desktop registra heartbeat/inventário e redireciona colaborador/gestor ao portal apropriado com cookies válidos. + +## Backlog recomendado +1. E-mails automáticos quando uso de horas ≥ 90% do contratado. +2. Ações rápidas (status/fila) diretamente na lista de tickets. +3. Limites de anexos por tenant + monitoramento. +4. Layout do PDF do ticket alinhado ao visual da aplicação. +5. Experimentos com React Compiler (Next 16). + +## Referências rápidas +- **Endpoints agent desktop**: - `POST /api/machines/register` - `POST /api/machines/heartbeat` - `POST /api/machines/inventory` - -## Rotina antes de abrir PR -- `pnpm lint` -- `pnpm build --turbopack` -- `pnpm exec vitest run` -- Revisar toasts/labels em PT-BR e ausência de segredos no diff. - -## Convenções -- Convex deve retornar apenas tipos primitivos; converta datas via mappers em `src/lib/mappers`. -- Manter textos em PT-BR e evitar comentários supérfluos no código. -- Reutilizar componentes shadcn existentes e seguir o estilo do arquivo editado. -- Validações client-side críticas devem sinalizar erros inline e exibir toast. - -## Estrutura útil -- `convex/` — queries e mutations (ex.: `tickets.ts`, `users.ts`). -- `src/components/tickets/` — UI interna (dialog, listas, header, timeline). -- `src/components/portal/` — formulários e fluxos do portal do cliente. -- `scripts/` — seeds Better Auth e utilidades. -- `src/components/auth/auth-guard.tsx` — proteção de rotas client-side. - -## Histórico resumido -- Scaffold Next.js + Turbopack configurado com Better Auth e Convex. -- Portal do cliente entregue com isolamento por `viewerId`. -- Fluxo de convites e painel administrativo operacionais. -- Iteração atual focada em UX de criação de tickets, consistência de layout e guardas de sessão. +- **Relatórios CSV**: + - Backlog: `/api/reports/backlog.csv?range=7d|30d|90d[&companyId=...]` + - Canais: `/api/reports/tickets-by-channel.csv?...` + - CSAT: `/api/reports/csat.csv?...` + - SLA: `/api/reports/sla.csv?...` + - Horas: `/api/reports/hours-by-client.csv?...` +- **Docs complementares**: + - `docs/DEV.md` — guia diário atualizado. + - `docs/STATUS-2025-10-16.md` — snapshot do estado atual e backlog. + - `docs/DEPLOY-RUNBOOK.md` — runbook do Swarm. + - `docs/admin-inventory-ui.md`, `docs/plano-app-desktop-maquinas.md` — detalhes do inventário/agente. + +--- +_Última atualização: 16/10/2025 (Next.js 16 beta, Turbopack, fluxos desktop + portal documentados)._ diff --git a/docs/DEV.md b/docs/DEV.md index 14f975a..996297d 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -5,7 +5,7 @@ Este documento consolida o estado atual do ambiente de desenvolvimento, descreve ## Resumo rápido - **Node/PNPM**: Node 20.9+ (Next.js 16 exige essa versão mínima) + pnpm 9 (habilite via `corepack enable && corepack prepare pnpm@9 --activate`). -- **Next.js 16 (beta)**: Projeto já está em `next@16.0.0-beta.0`, com Turbopack como bundler padrão e cache de filesystem habilitado em `next.config.ts` (além de `server.allowedHosts` configurado para os domínios via Traefik). +- **Next.js 16 (beta)**: Projeto já está em `next@16.0.0-beta.0`, com Turbopack como bundler padrão, cache de filesystem habilitado e `experimental.trustHostHeader` + middleware validando os domínios expostos pelo Traefik. - **Lint/Test/Build**: `pnpm lint`, `pnpm test`, `pnpm build`. O script de testes usa `vitest --run --passWithNoTests`, eliminando o modo watch interativo. - **Banco DEV**: SQLite em `prisma/prisma/db.dev.sqlite`. Defina `DATABASE_URL="file:./prisma/db.dev.sqlite"` ao chamar CLI do Prisma. - **Desktop (Tauri)**: fonte em `apps/desktop`. Usa Radix tabs + componentes shadcn-like, integra com os endpoints `/api/machines/*` e suporta atualização automática via GitHub Releases. diff --git a/middleware.ts b/middleware.ts index 620918d..96e9f59 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,5 +1,7 @@ -import { NextRequest, NextResponse } from "next/server" import { getCookieCache } from "better-auth/cookies" +import { NextRequest, NextResponse } from "next/server" + +import { isAllowedHost } from "@/config/allowed-hosts" // Rotas públicas explícitas (não autenticadas) // Permite handshake de máquina sem sessão prévia para criar a sessão de máquina. @@ -9,8 +11,16 @@ const ADMIN_ONLY_PATHS = [/^\/admin(?:$|\/)/] const APP_HOME = "/dashboard" export async function middleware(request: NextRequest) { + if (process.env.NODE_ENV === "production" && !isAllowedHost(request.headers.get("host"))) { + return new NextResponse("Invalid Host header", { status: 403 }) + } + const { pathname, search } = request.nextUrl + if (pathname.startsWith("/api")) { + return NextResponse.next() + } + if (PUBLIC_PATHS.some((pattern) => pattern.test(pathname))) return NextResponse.next() const session = await getCookieCache(request) @@ -70,7 +80,7 @@ export async function middleware(request: NextRequest) { export const config = { runtime: "nodejs", // Evita executar para assets e imagens estáticas - matcher: ["/((?!api|_next/static|_next/image|favicon.ico|icon.png).*)"], + matcher: ["/((?!_next/static|_next/image|favicon.ico|icon.png).*)"], } async function attemptSessionRefresh(request: NextRequest): Promise { diff --git a/next.config.ts b/next.config.ts index 64f11cf..1790907 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,10 +3,7 @@ import type { NextConfig } from "next" const nextConfig = { experimental: { turbopackFileSystemCacheForDev: true, - }, - // @ts-expect-error server.allowedHosts ainda não tipado no beta - server: { - allowedHosts: ["tickets.esdrasrenan.com.br", "convex.esdrasrenan.com.br", "localhost"], + trustHostHeader: true, }, } satisfies NextConfig diff --git a/src/config/allowed-hosts.ts b/src/config/allowed-hosts.ts new file mode 100644 index 0000000..f0d7396 --- /dev/null +++ b/src/config/allowed-hosts.ts @@ -0,0 +1,20 @@ +const ALLOWED_HOST_VALUES = [ + "tickets.esdrasrenan.com.br", + "convex.esdrasrenan.com.br", + "localhost", + "localhost:3000", + "127.0.0.1", + "127.0.0.1:3000", +] as const + +const ALLOWED_HOST_SET = new Set(ALLOWED_HOST_VALUES.map((host) => host.toLowerCase())) + +export const ALLOWED_HOSTS = ALLOWED_HOST_VALUES + +export function isAllowedHost(hostHeader: string | null | undefined): boolean { + if (!hostHeader) { + return false + } + + return ALLOWED_HOST_SET.has(hostHeader.toLowerCase()) +}