carlos@cesaints: ~/projects/holding-website.md — zsh

Comando: cat projects/holding-website.md

projects/holding-website.md · 1,6 KB

Site de holding criativa com painel, propostas por link secreto e faturas

O site em três idiomas de uma holding criativa e o painel interno por trás dele: pedidos de diagnóstico, audiência medida sem cookie, SEO por página e idioma, contas com papéis e auditoria. Do painel saem propostas comerciais, que viram páginas privadas abertas por link secreto, com senha opcional, validade e contador de visualizações, e as faturas imprimíveis derivadas delas. A landing page estática da mesma casa, sem dependência e sob a política de segurança mais fechada possível, completa o case.

abrir demonstraçãosimulação do sistema com dados fictícios · 7 telas

Papel
Único engenheiro, do produto à operação
Período
jul/2026 – set/2026
O que eu fiz

Eu: Arquitetura, autenticação e papéis, modelo de dados, o painel inteiro, o fluxo de propostas e faturas por link secreto, a medição de audiência, a landing page e o deploy.

Projeto privado, descrito sem identificar cliente, produto ou empresa.

Diagrama

Site, painel e documentos por link secreto no mesmo projeto

Tudo roda em Cloudflare Workers, via OpenNext. O site é pré-renderizado em português, inglês e espanhol, e o pedido de diagnóstico vai para os dados, acessados pelo Drizzle, com D1, KV e R2. O painel, com papéis por capacidade, Argon2id, CSRF e auditoria, usa os mesmos dados e publica propostas e faturas como páginas abertas por link secreto, com senha opcional e validade; um link revogado responde como um inexistente. O cliente abre o documento sem conta. A landing page estática, sem dependência e com CSP default-src 'none', é publicada pelo próprio site.

Contexto

Uma holding criativa com várias frentes de trabalho precisava de um site que explicasse o método da casa em três idiomas e transformasse visitas em pedidos de diagnóstico. Por trás do site, a equipe comercial precisava enviar propostas e faturas com cara de documento da casa, sem anexo solto por e-mail e sem pedir que o cliente criasse conta.

O que eu construí

  • O site. Páginas pré-renderizadas em português, inglês e espanhol, com slugs traduzidos, hreflang, dados estruturados e um formulário de diagnóstico validado por schema no servidor.
  • O painel. Leads, audiência, SEO por página e idioma, contas com quatro papéis (viewer, editor, admin e owner) e um registro de auditoria de cada ação.
  • Propostas e faturas por link secreto. O editor monta a proposta por blocos (entregáveis, escopo, mercados, cláusulas e cronograma) e publica com senha opcional, validade e contador de visualizações. A fatura nasce da proposta, herda cliente, moeda e um item por mercado, e sai numa folha pronta para imprimir.
  • A landing page. Uma página estática em três idiomas que converte por um único caminho: uma conversa com a mensagem pronta no idioma do visitante e a origem da campanha anexada.

O que eu faria diferente

Colocaria um teste de fumaça contra o build de produção desde o primeiro deploy. A biblioteca de hash funcionava na minha máquina e falhava no runtime de borda, e o login ficou quebrado em produção até eu investigar. Também escreveria o teste que trata Server Actions como endpoints públicos antes da primeira action, em vez de consolidar quatro guardas divergentes depois.

Restrições

  • Três idiomas com as mesmas páginas, slugs traduzidos e SEO por idioma.
  • Documentos comerciais que só quem recebeu o link abre, sem conta, sem cache e sem indexação.
  • Audiência medida sem cookie de rastreio e sem guardar IP.
  • Site e painel num runtime de borda, com autenticação própria.
  • Uma única pessoa técnica para construir e operar.

Decisões

Link secreto em vez de conta para o cliente

Contexto
O cliente precisa abrir a proposta e a fatura sem criar conta, e um link adivinhável ou guardado em cache exporia valores comerciais.
Escolha
Token aleatório de 24 caracteres (cerca de 143 bits) gerado ao publicar, senha opcional com bloqueio próprio por documento, cookie de leitura derivado do hash e restrito ao caminho do documento, e uma máquina de estados que responde 404 para link revogado, rascunho ou inexistente, sem confirmar que eles existem.
Ganhos
  • O cliente abre sem cadastro, e revogar derruba o acesso na hora.
  • A telemetria grava o id do documento, nunca o token.
Custos
  • Quem tem o link, e a senha quando houver, lê o documento; o link pode ser repassado.
  • Republicar depois de revogar gera um token novo, e o link precisa ser reenviado.

Autenticação própria, com um guarda por tipo de entrada

Contexto
O painel tem quatro papéis e roda num runtime de borda. As verificações de sessão tinham se espalhado em quatro cópias que já divergiam entre si.
Escolha
Argon2id, sessão guardada no banco com o id igual ao hash do token do cookie, expiração de 8 horas, bloqueio após 5 falhas por 15 minutos, troca obrigatória de senha, CSRF e papéis por capacidade. Um guarda para páginas, que redireciona, e outro para actions, que só recusa.
Ganhos
  • Um único lugar para revisar cada regra de acesso.
  • Um vazamento do banco não entrega sessões válidas.
Custos
  • Autenticação própria exige revisão contínua, sem a rede de segurança de um provedor pronto.

Server Actions tratadas como endpoints públicos

Contexto
Todo export de um módulo de Server Actions vira um endpoint HTTP. Três consultas de listagem estavam acessíveis sem sessão por esse caminho.
Escolha
Consultas movidas para um módulo exclusivo do servidor e um teste que reprova qualquer export novo sem guarda ou sem justificativa declarada.
Ganhos
  • A regra deixa de depender da memória de quem escreve a próxima action.
Custos
  • Cada action nova precisa declarar o guarda ou a justificativa, um passo a mais no fluxo.

Audiência sem cookie, com o número explicado na tela

Contexto
A equipe queria saber quem visita o site sem cookie de rastreio e sem ferramenta de terceiros, e um número sem definição leva a decisão errada.
Escolha
Coleta própria que descarta robôs e calcula um código de visitante que troca todo dia, sem guardar IP. A métrica de visitantes contava a mesma pessoa uma vez por dia de retorno e passou a se chamar visitantes por dia, com uma caixa "Como contamos" escrita para quem não programa.
Ganhos
  • Nenhum dado de audiência sai para terceiros.
  • O número na tela diz exatamente o que mede.
Custos
  • Não dá para reconhecer a mesma pessoa em dias diferentes, então não existe visitante único do mês.

Landing page sem dependência e sem build

Contexto
A página de campanha precisava carregar rápido em três idiomas e rodar sob a política de segurança mais fechada possível.
Escolha
HTML, CSS e JavaScript puros, sprite SVG inline, CSP com default-src 'none', sem unsafe-inline e com Trusted Types, e um portão de verificação antes de cada exportação para dentro do site, que a publica.
Ganhos
  • Nenhuma requisição de terceiro, nenhum formulário e nenhum fetch.
  • O portão reprova script ou style inline, manipuladores on*, eval e target=_blank sem noopener.
Custos
  • Sem framework, a troca de idioma e as animações são código à mão.
  • A configuração de segurança vive em cinco lugares, e o portão precisa conferir todos.

Stack e por quê

Next.js 16 e React 19
Site pré-renderizado em três idiomas e painel dinâmico no mesmo projeto, com Server Actions para escrita.
next-intl
Slugs traduzidos, hreflang e mensagens em paridade nos três idiomas.
Drizzle com D1 e SQLite
Um único acesso a dados que usa D1 em produção e SQLite local no desenvolvimento.
Cloudflare Workers via OpenNext (D1, KV, R2)
Deploy automático a cada push na branch principal.
Argon2id em implementação pura
O runtime de borda recusa WebAssembly compilado em tempo de execução.
node:test e Playwright
Testes de senha, contraste, analytics e guarda das Server Actions; roteiro do painel no navegador.
HTML, CSS e JavaScript sem framework
A landing page, sem dependência e sem build.

Resultados

  • Login que nunca tinha funcionado em produção diagnosticado e corrigido: a biblioteca de hash compilava WebAssembly em tempo de execução, e a troca manteve o formato do hash, conferido por teste contra a biblioteca antiga.

    repositório privado auditadoHistórico e teste de senha do repositório(Auditoria do repositório privado, set/2026)
  • Um teste reprova qualquer Server Action exportada sem guarda ou sem justificativa, depois da correção das três consultas acessíveis sem sessão.

    repositório privado auditadoTeste e módulo exclusivo do servidor(Auditoria do repositório privado, set/2026)
  • Links de proposta com token de 24 caracteres (cerca de 143 bits), senha opcional com bloqueio por documento e resposta 404 para link revogado, rascunho ou inexistente.

    repositório privado auditadoCódigo das páginas por link secreto(Auditoria do repositório privado, set/2026)
  • Landing page com 115 KB na primeira visita, as três fontes incluídas, e nenhuma requisição de terceiro.

    declaradoMedição registrada no projeto
  • 416 chaves de tradução em paridade nos três idiomas, com slugs traduzidos e hreflang.

    declaradoDocumentação do projeto
  • Roteiro de ponta a ponta que percorre as 8 telas do painel com login real e nenhum erro 5xx.

    declaradoDocumentação do projeto

Ângulo de segurança

Superfície de ataque

  • Páginas de proposta e fatura abertas por link, sem conta.
  • Painel com leads, contas, SEO e auditoria.
  • Formulário público de diagnóstico e coleta de audiência.
  • Server Actions, que viram endpoints públicos.

Controles implementados

  • Token de 24 caracteres, senha opcional com bloqueio por documento e cookie de leitura restrito ao caminho do documento.
  • Cabeçalhos que proíbem cache e indexação nos documentos, e noindex no painel inteiro.
  • Argon2id, sessão guardada como hash do token, bloqueio por tentativas, troca obrigatória de senha, CSRF e papéis por capacidade.
  • Um guarda para páginas, outro para actions e um teste que reprova export sem guarda.
  • Registro de auditoria de login, papéis, SEO, propostas e faturas, com IP guardado só como hash.
  • O hash da senha do documento nunca vai para o editor no navegador.
  • Landing page com CSP default-src 'none', sem unsafe-inline e com Trusted Types.

O que eu testaria hoje

  • Enumeração de tokens e diferença de resposta entre link revogado e inexistente.
  • Um editor ou viewer chamando direto as actions de contas e de auditoria.
  • Reuso do cookie de leitura de uma proposta em outra.

Evidências

  • repositório privado auditadoRepositórios privados auditados (site e landing page)(Auditoria do repositório privado, set/2026)