19ed377214
POST /api/webhooks/lead con header x-webhook-secret. Un endpoint solo per il
form del sito e per qualunque bridge: il contratto e' un POST, e chi lo manda
non cambia la route.
La normalizzazione dei campi sta in lead-intake.ts, non nella route, perche' e'
la parte che cambia quando si aggiunge una sorgente. Regge tre forme senza
doverle distinguere: payload piatto, `fields` annidati con {value} (Elementor
Pro), e urlencoded per i form che non mandano JSON. Riconosce i nomi italiani
(nome, telefono, azienda, messaggio), che e' come li chiama un form Elementor
scritto in italiano.
Chi compila due volte non diventa due lead. Il riconoscimento e' sull'email: il
secondo invio aggiorna last_contact_date e lascia un'attivita' con quello che
ha scritto, cosi' il messaggio non si perde ma la scheda resta una. Senza email
non si puo' dedurre nulla e si crea.
Due scelte di sicurezza, entrambe diverse dalle route /api/internal:
- Segreto assente in ambiente = 403, non "passa". Le internal possono
permetterselo perche' sono raggiungibili solo da localhost; questa e' esposta
a internet, e un deploy con la variabile dimenticata deve smettere di
accettare lead, non accettarli da chiunque.
- Il rate limit viene PRIMA del confronto sul segreto, altrimenti tentare
segreti a raffica costerebbe zero. Confronto a tempo costante con safeEqual,
lo stesso del gate admin.
src/proxy.ts non intercetta /api/*, quindi da monte non arriva nessuna
protezione: sta tutto dentro la route.
Provato contro il DB di produzione via tunnel SSH, poi ripulito (2 lead e 2
attivita' prima, 2 e 2 dopo): senza segreto 403, segreto sbagliato 403, nome
mancante 422, payload piatto 201, ripetuto 200 "updated" senza duplicare,
forma Elementor 201 con nome/telefono/messaggio mappati, urlencoded 201, e con
starts_at valorizzato il lead nasce con la data della call e un'attivita'
"meeting".
Resta da confermare con un invio VERO da Elementor la forma esatta del suo
payload: qui e' gestita in modo difensivo, non verificata sul campo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
25 lines
1.0 KiB
Bash
25 lines
1.0 KiB
Bash
# Database — Postgres su Coolify
|
|
DATABASE_URL=postgresql://user:password@host:5432/database
|
|
|
|
# NextAuth
|
|
NEXTAUTH_URL=https://hub.iamcavalli.net
|
|
NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
|
|
ADMIN_EMAIL=admin@example.com
|
|
ADMIN_PASSWORD=use-a-strong-password-min-20-chars
|
|
|
|
# Internal API secret — shared between proxy.ts and /api/internal/* routes
|
|
# Generate with: openssl rand -base64 32
|
|
INTERNAL_SECRET=generate-with-openssl-rand-base64-32
|
|
|
|
# Resend — invio del codice OTP per l'accesso al portale cliente
|
|
# RESEND_FROM deve usare un dominio verificato su Resend
|
|
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
RESEND_FROM=Nome Mittente <no-reply@iamcavalli.net>
|
|
|
|
# Ingresso lead da fuori (form del sito, bridge Zapier/Make) su
|
|
# POST /api/webhooks/lead, header x-webhook-secret.
|
|
# A differenza di INTERNAL_SECRET questa route e' esposta a internet: se la
|
|
# variabile manca, la route risponde 403 invece di lasciar passare.
|
|
# Generate with: openssl rand -base64 32
|
|
LEAD_WEBHOOK_SECRET=generate-with-openssl-rand-base64-32
|