01Il problema del database

Nel primo articolo ho mostrato come l'architettura serverless a zero costi usi GitHub Actions per aggiornare i prezzi e Vercel per mostrare il sito. C'era però un ostacolo importante: come gestisco gli utenti della newsletter senza avere un database?

In un sistema tradizionale con double opt-in, il flusso è questo:

  • L'utente inserisce l'email.
  • Il server genera un token casuale (es. 1234abcd), lo salva nel database con una data di scadenza (es. +24 ore) e lo invia via email all'utente.
  • L'utente clicca il link. Il server cerca il token nel database. Se esiste e non è scaduto, lo elimina e segna l'utente come "verificato".

Per Radar Benzina, non volevo pagare e gestire un database in cloud (es. PostgreSQL o MongoDB) solo per memorizzare dei token temporanei. La sfida era fare tutto questo in modalità stateless (senza stato).

02Cosa significa "Stateless"?

In un'architettura stateless, il server soffre di "amnesia". Tra una richiesta HTTP e l'altra, non ricorda nulla di ciò che è successo in precedenza.

Se il server non può memorizzare il token generato, l'unica soluzione è dare il token direttamente all'utente (dentro il link di conferma) e trovare un modo matematico e sicuro per verificare, quando l'utente ci clicca sopra, che quel token sia stato generato effettivamente dal sistema e non inventato da un bot.

03La magia della crittografia HMAC-SHA256

Per risolvere il problema, ho usato una funzione crittografica chiamata HMAC-SHA256 (Hash-based Message Authentication Code). L'idea è molto simile al funzionamento dei JSON Web Tokens (JWT).

Quando un utente si iscrive, il server su Vercel non salva nulla. Crea invece un "messaggio" unendo l'email e un timestamp di scadenza (es. "mario@email.it|1715000000"). Poi, usando una chiave segreta (HMAC_SECRET) che conosce solo lui, calcola una firma digitale univoca per questo messaggio.

import hmac
import hashlib

def genera_token(email, timestamp, secret):
    messaggio = f"{email}|{timestamp}".encode()
    firma = hmac.new(secret.encode(), messaggio, hashlib.sha256)
    return firma.hexdigest()

Il link che arriva via email all'utente contiene tutto: l'email, il timestamp e la firma appena calcolata. Il server può dimenticarsi immediatamente di questa operazione.

04Verifica e aggiunta dell'utente

Quando l'utente clicca sul link di conferma, il server su Vercel riceve l'email, il timestamp e la firma originale.

Il server "non ricorda" di aver generato questa firma, ma può ricalcolarla! Prende l'email e il timestamp passati nell'URL, ricalcola la firma usando la sua chiave segreta e la confronta con quella fornita nel link. Se combaciano perfettamente, significa che il link è autentico e non è stato manomesso.

Infine, il server verifica che il timestamp non sia più vecchio di 24 ore. Se tutto è corretto, utilizza le API di GitHub (tramite una GitHub App dedicata) per aggiungere istantaneamente l'email al file di testo destinatari.txt, facendone il commit. Zero database, sicurezza matematica e rispetto della privacy policy.

05L'invariante critica: il Secret Condiviso

Questo sistema ha una sola grande "fragilità" architetturale: la chiave segreta (HMAC_SECRET). Questa chiave non viene utilizzata solo su Vercel per generare i link di iscrizione, ma viene utilizzata anche dalle GitHub Actions al mattino, per generare in automatico i link di disiscrizione personalizzati per ogni utente.

Questo significa che lo stesso identico segreto deve risiedere sia tra le variabili d'ambiente di Vercel, sia nei secrets del repository GitHub. Se per qualsiasi motivo i due segreti si disallineassero (es. lo aggiorno su Vercel ma dimentico di aggiornarlo su GitHub), tutti i link di disiscrizione inviati nelle email mattutine risulterebbero magicamente "invalidi" al click, rompendosi silenziosamente.

Così ho risolto la gestione utenti, mantenendo intatta la promessa del "costo zero". C'era però un'altra sfida in agguato: le API esterne per mappe e ricerca. Nel prossimo articolo illustrerò come ho ottimizzato i costi e il bilanciamento delle API per non sforare mai i tier gratuiti! 🚀