Devlog · Fase 0 · 3/7
Uma vitrine sem banco de dados
Zero tabela, zero dado pessoal em repouso: a lista de espera de aubia.dev sai para aubia.cloud via um proxy assinado com HMAC, com um retry que nunca duplica uma inscrição.
- architecture
- security
- waitlist
- gdpr

A landing de Aubia coleta endereços de e-mail. Você poderia achar que ela, portanto, precisa de um banco de dados. Ela não tem nenhum. Nenhuma tabela, nenhum modelo Eloquent, nenhum endereço de e-mail escrito em disco do lado da vitrine. Não sofri essa ausência, eu a coloquei desde o primeiro commit. Uma página que inscreve pessoas em uma lista de espera pode não persistir nada, e é mais simples assim.
Uma vitrine não precisa de banco de dados
Todo o conteúdo de aubia.dev é estático por natureza: os textos de marketing vivem em arquivos de tradução JSON, os artigos deste blog em arquivos Markdown versionados junto com o código. Nada disso justifica um banco de dados. O único fluxo dinâmico da Fase 0, a inscrição na lista de espera, não armazena nada localmente: ele transita para aubia.cloud.
O repo web não embarca, portanto, nenhum modelo Eloquent, nenhuma migração de dado de negócio. O que toca o Redis se limita a cache transitório e à sessão: uma contagem, um token anti-abuso, nunca um endereço de e-mail durável.
Retirar o banco de dados de uma vitrine é retirar de uma vez toda uma classe de preocupações: backups, criptografia em repouso, vazamento de dados, purga RGPD. Você não protege o que não detém.
Zero dado pessoal armazenado do lado web
A regra é absoluta: todo dado pessoal passa pelo cloud, nunca por um armazenamento local do lado da vitrine. Quando você envia seu e-mail, o controller permanece deliberadamente enxuto. Ele valida, constrói um objeto de transporte tipado, delega a uma Action e retorna uma mensagem flash. Ele não conhece nem a rede, nem a assinatura, nem o banco de dados, porque não há nenhum.
final class WaitlistController
{
public function store(SubmitWaitlistRequest $request, SubmitWaitlistEmail $action): RedirectResponse
{
$data = WaitlistSubmissionData::fromArray([
'email' => $request->validated()['email'],
'locale' => $request->validated()['locale'],
// ... utm et honeypot temporel
]);
$status = $action->execute($data);
return $status->isSuccess()
? back()->with('waitlist_success', $status->messageKey())
: back()->with('waitlist_error', $status->messageKey());
}
}
O e-mail atravessa a memória do worker pelo tempo de uma chamada HTTP de saída, depois desaparece. Nada fica para trás.
Um proxy assinado para aubia.cloud
O módulo Waitlist entrou no repo no início de maio de 2026, uma vez montado o cenário da landing. Sua Action SubmitWaitlistEmail faz a ponte. Ela serializa o payload, calcula uma assinatura HMAC SHA-256 e faz um POST para aubia.cloud. A assinatura prova ao cloud que a requisição vem mesmo da vitrine oficial, e a origem está fixada no contrato.
$rawBody = json_encode($data->toCloudPayload(), JSON_THROW_ON_ERROR);
$headers = [
'Content-Type' => 'application/json',
'Origin' => 'https://aubia.dev',
];
if (config('services.cloud.waitlist_proxy_sign_hmac') !== false) {
$secret = (string) config('services.cloud.waitlist_proxy_signing_key');
$headers['X-Aubia-Signature'] = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
}
Mantive deliberadamente essa assinatura mínima: ela recai sobre o corpo JSON bruto, sem timestamp nem nonce anti-replay. A vitrine é o único chamador legítimo e sua origem é restrita por IP do lado do cloud, o que torna um ataque de replay pouco realista na Fase 0. Acrescentar um mecanismo anti-replay teria pesado o contrato sem benefício tangível neste estágio.
Em produção, essa assinatura não pode ser desativada: o código lança uma exceção se alguém tentar desligá-la. Um detalhe conta no payload enviado. O formulário armadilha os bots com um honeypot temporal, um campo started_at posto na montagem da página. Esse campo serve apenas à validação local e nunca é transmitido ao cloud, o que mantém a superfície do contrato mínima e estabiliza a assinatura.
public function toCloudPayload(): array
{
return [
'email' => $this->email,
'utm_source' => $this->utmSource,
'utm_medium' => $this->utmMedium,
'utm_campaign' => $this->utmCampaign,
'locale' => $this->locale,
];
// started_at (honeypot) volontairement exclu
}
A assinatura não está ali para substituir as armadilhas anti-bot, mas para acrescentar uma camada por cima. Um honeypot ou um atraso mínimo podem ser contornados por um bot um pouco caprichado, capaz de ler o DOM ou de programar uma espera. O HMAC, por sua vez, torna o scraping em massa caro: sem o segredo compartilhado, nenhuma requisição forjada é aceita.
Um retry que nunca duplica uma inscrição
A rede falha às vezes. Um cliente HTTP ingênuo então reproduziria a requisição, ao risco de inscrever duas vezes a mesma pessoa. O cliente de saída distingue, portanto, dois casos conforme a idempotência do verbo. Um GET, como a leitura da contagem, pode ser reproduzido sem efeito colateral. Um POST de inscrição, não: ele só reproduz se a requisição nunca chegou ao cloud.
->retry(
$retries + 1,
fn(int $attempt): int => $backoffMs + random_int(0, 100),
// Retry UNIQUEMENT sur erreur réseau : la requête n'a jamais atteint
// le cloud. Jamais sur une réponse reçue, même 5xx (double inscription).
fn(Throwable $e, PendingRequest $request): bool => $e instanceof ConnectionException,
throw: false,
)
Se o cloud respondeu, mesmo com um erro de servidor, a vitrine não tenta de novo: ela mapeia a resposta para um status de negócio e exibe a mensagem certa. A correção vive na semântica do retry, não na adivinhação.
O double opt-in vive do lado do cloud
É aubia.cloud que cuida do double opt-in: geração do token de confirmação, envio do e-mail, expiração após 48 horas e, sobretudo, o registro do consentimento. Enquanto uma inscrição não é confirmada, nenhuma comunicação sai, e os endereços não confirmados são purgados automaticamente.
A vitrine nunca vê nada disso. Ela não conhece nem o token, nem o estado de confirmação, nem o menor dado durável. Essa fronteira nítida entre um frontend público sem estado e um cloud que detém a lógica e os dados é o que torna o conjunto simples de raciocinar e de proteger.
O que a ausência de banco de dados simplifica
Sem banco de dados local, o deploy de aubia.dev se torna trivial: nenhuma migração a rodar, nenhuma conexão a provisionar, nenhum estado a replicar entre workers. A segurança se reduz na mesma medida, porque a vitrine não detém nenhum segredo de usuário a proteger. E a conformidade RGPD se desloca para um único lugar, o cloud, em vez de estar espalhada por cada superfície.
Uma vitrine deve informar e converter, não se tornar um cofre. Ao lhe retirar o banco de dados, retiro dela as suas responsabilidades mais pesadas.
Esse proxy assinado é apenas um elo. O percurso completo, esse, merece o seu próprio relato: o formulário, o anti-bot sem captcha, o double opt-in e a contagem, com os trade-offs por trás de cada uma dessas escolhas.
Para acompanhar a construção de Aubia e ser avisado quando a beta 0.1 abrir, entre na lista de espera.
Esta construção é contada aqui, artigo após artigo. Embarque: o seu feedback desenhará o que vem a seguir.
Entrar na lista de espera