Comando: cat projects/holding-website.md
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
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 projeto416 chaves de tradução em paridade nos três idiomas, com slugs traduzidos e hreflang.
declaradoDocumentação do projetoRoteiro 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)