Passa al contenuto principale

Problemi comuni

Un playbook pratico per i problemi che si presentano più spesso in produzione. Ogni voce segue lo stesso schema: sintomo, controlli, soluzione.

Blocco login (5 tentativi falliti / 15 minuti)​

Sintomo: una password corretta viene rifiutata e la risposta contiene errors.auth.tooManyAttempts con i minuti rimanenti. Si tratta del blocco per-account, non del rate limiter per IP (un 429 con errors.general.rateLimitExceeded indica invece il limiter).

Controlli:

  • Il contatore di blocco risiede nella tabella LoginAttempt: una riga per email tentata con FailedCount, LockedUntil e LastAttemptAt. Poiché è nel database, i container blue-green e i riavvii condividono la stessa vista.
  • SuperAdmin: Impostazioni > Accessi ed errori (/dashboard/amministrazione/settings/accessi-errori, API /api/v1/auth-events) registra gli eventi di autenticazione mascherati, incluso account_locked.
  • I log dell'app mostrano un warning mascherato (Login attempt for locked account) con i minuti rimanenti.

Soluzione:

  • Attendi la finestra di 15 minuti. Il contatore riparte allo scadere della finestra e un login riuscito elimina la riga.
  • Non eliminare né modificare a mano le righe di blocco: il blocco è temporale e il successivo tentativo fallito ricreerebbe semplicemente lo stato.
  • Se i blocchi si ripetono senza che l'utente riprovi, trattalo come credential stuffing: ispeziona gli eventi di autenticazione e i log del rate-limit, e ruota la password.

Email non recapitata​

Sintomo: un utente non riceve un'email di attivazione, OTP, recupero password o newsletter.

Controlli:

  • L'invio transazionale è condizionato dal feature flag enable_email_sender.
  • Configurazione del provider: EMAIL_SENDER_EMAIL_PROVIDER più le sue credenziali. In sviluppo, un provider vuoto significa modalità console (i messaggi sono loggati, non inviati). Vedi Configurazione email.
  • L'invio massivo/newsletter usa il provider selezionato nelle impostazioni di Comunicazione (DB), con il default d'ambiente come fallback. Un provider con quota esaurita (429/402) parcheggia il job fino alla successiva mezzanotte UTC, e una allow-list di email di test non vuota redirige ogni messaggio solo a quegli indirizzi.
  • Lista di soppressione: newsletter_suppressions contiene i destinatari permanentemente esclusi (bounce, complaint, manuale, unsubscribe). La coda di invio marca gli indirizzi soppressi come Suppressed, così inviati + falliti + soppressi quadrano con il totale del job.
  • Log di consegna: Impostazioni > Email inviate (SuperAdmin, /api/v1/email-delivery-logs) unisce il log locale (retention 180 giorni) con le analytics Cloudflare live quando configurate. Filtra per destinatario, stato, provider e intervallo date.
  • Log del container per errori del provider (flo logs app <tenant>, oppure il visualizzatore log app in Impostazioni e Audit).

Soluzione:

  • Correggi le variabili d'ambiente del provider e riavvia il container: le variabili d'ambiente sono lette all'avvio.
  • Per gli alias Gmail, verifica la configurazione "Invia come"; per SMTP personalizzato, controlla host/porta/TLS e credenziali.
  • Per le newsletter, riseleziona il provider o impostane le credenziali; riprova dopo il reset della quota se il provider era parcheggiato.
  • Valuta il motivo di soppressione prima di rimuovere una soppressione: un unsubscribe è una preferenza legittima, non un fallimento di consegna.
  • Rigenera i messaggi transazionali falliti con POST /api/v1/maintenance/email/replay-failed e l'header X-Maintenance-Key (Maintenance__ApiKey). Supporta dryRun e filtri per destinatario/utente, e risponde 503 quando nessun provider email è risolvibile oppure rifiuta quando enable_email_sender è spento (errors.emailDeliveryLog.replayEmailDisabled).

Problemi con i webhook di pagamento​

Sintomo: il provider riporta un pagamento riuscito ma l'ordine resta in sospeso, oppure il webhook risponde 400 con errors.payment.invalidWebhook.

Controlli:

  • URL webhook configurato presso il provider: POST https://<dominio>/api/v1/payments/{provider}/webhook, dove {provider} è il gateway configurato (stripe o paypal, corrispondente a PAYMENT__PROVIDER). L'endpoint è anonimo, limita il body a 64 KB ed è rate-limitato a 120/min per IP.
  • Verifica della firma: Stripe usa PAYMENT__STRIPE__WEBHOOKSECRET (il valore whsec_) contro l'header Stripe-Signature; PayPal usa PAYMENT__PAYPAL__WEBHOOKID per la verifica della firma. Un segreto errato o mancante fa fallire il parsing, che emerge come errors.payment.invalidWebhook.
  • Dedup ed elaborazione: le righe PaymentWebhookEvent registrano id evento esterno, tipo evento, timestamp di ricezione/elaborazione ed eventuali messaggi di errore.
  • Ciclo di vita ordini: gli ordini di prenotazione in sospeso/trattenuti scadono secondo pianificazione quando nessun webhook li conferma.

Soluzione:

  • Copia il segreto di firma dalla dashboard del provider nella variabile d'ambiente, riavvia il container e re-invia l'evento dal provider.
  • Ordini Commerce (piano studio): POST /api/v1/commerce/orders/{id}/reconcile rilegge il gateway e aggiorna l'ordine.
  • Ordini di pagamento prenotazioni: risolvi un ordine in revisione con POST /api/v1/payments/admin/orders/{id}/review; ritenta l'emissione fattura elettronica con POST /api/v1/payments/orders/{id}/invoice/retry (disponibile quando il job fattura è NeedsReview o Failed).
  • Verifica che il gateway sia configurato: GET /api/v1/payments/admin/readiness.

Modalità manutenzione​

Sintomo: le API protette rispondono 503 con errors.general.maintenanceMode.

Controlli:

  • flo --vps production instance maintenance <tenant> status restituisce enabled, activeRequests e leaseBoundMaintenanceBlocked per ogni backend.
  • La variabile d'ambiente MAINTENANCE_MODE è il default al boot; una commutazione live la sovrascrive per il processo in esecuzione senza riavvio.

Soluzione:

  • Disattivala: flo --vps production instance maintenance <tenant> off. Il comando verifica lo stato risultante su ogni backend.
  • Se viene riportato leaseBoundMaintenanceBlocked, MON controlla il tenant: completa flo failover auto disarm prima di modificare lo stato di manutenzione.
  • Se una commutazione fallisce e lo stato non può essere verificato, la CLI ferma i backend per bloccare il traffico. Trattalo come incidente operativo e ispeziona lo stato del cutover e ogni container prima di avviare qualsiasi cosa.
  • Per aggiornare la cache dei feature-flag con la manutenzione attiva, usa flo --vps production instance maintenance-cache-invalidate <tenant>.

Avvisi di failover e lease​

Sintomo: un tenant sullo standby smette di servire, gli health check falliscono oppure i log riportano dinieghi di lease (lease-missing, lease-expired, lease-invalid-signature, lease-fenced, lease-stale-epoch e motivi simili). Un valore non valido di FAILOVER_ENABLED rifiuta l'avvio invece di disarmare silenziosamente un nodo.

Controlli:

flo control whoami
flo --vps <node> vps stats
flo --vps <node> failover status
flo --vps <node> failover monitor --all -y
flo --vps <node> failover incident list
  • Leggi il record durevole prima di agire: flo failover incident show <id>, poi flo failover incident explain <id>.

Soluzione:

  • incident resume <id> -y solo dopo una nuova sonda di sicurezza live; incident abort <id> -y solo dopo aver provato il recupero di sorgente e standby.
  • Non eliminare mai incidenti, lease, epoch o file di fence per far passare un drill, e non modificare a mano il database MON.
  • Il failback resta manuale: flo --vps <standby> failover back <tenant> --to <primary> -y, poi ricrea la protezione con flo --vps <primary> failover replication setup <tenant> -y.
  • Le distribuzioni rifiutano tenant fenced e tenant senza primary lease pubblicata; correggi lo stato DR invece di aggirare il controllo.

Runbook operatore completo: Failover e Disaster Recovery.