feat: integrazione hCaptcha nel form contatti

- contatti.astro: widget hCaptcha, token incluso nel payload,
  messaggio di errore se challenge non completata
- docs/INFRA.md: documentata configurazione hCaptcha (site key,
  secret key in appSettings, flusso di verifica)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:11:08 +02:00
parent 3824083190
commit 2a13365871
2 changed files with 39 additions and 5 deletions

View File

@@ -88,3 +88,23 @@ restituiscono la homepage Astro invece del contenuto atteso.
| `https://api.smart-roots.net/api/contatti` | POST | Invio form contatti | | `https://api.smart-roots.net/api/contatti` | POST | Invio form contatti |
Il controller è in `SmartRootsServices/SRServices/Controllers/SmartRootsSite/ContattiController.cs`. Il controller è in `SmartRootsServices/SRServices/Controllers/SmartRootsSite/ContattiController.cs`.
Validazioni lato server: campi obbligatori, messaggio minimo 10 caratteri,
rate limiting (3 invii/ora per IP), honeypot anti-bot, verifica hCaptcha.
---
## hCaptcha
Il form contatti è protetto da **hCaptcha** (piano gratuito).
- **Site key** (pubblica, nel frontend): `813a0980-869a-42f4-9a7c-abaae7dccd96`
- **Secret key** (privata, solo server): configurata in `<appSettings>` nel `web.config`
di SmartRootsServices sul server — **non è in source control**
```xml
<add key="HcaptchaSecret" value="..." />
```
Il controller verifica il token chiamando `https://hcaptcha.com/siteverify` prima
di processare il form. Se la verifica fallisce restituisce HTTP 400.

View File

@@ -10,6 +10,7 @@ import Footer from '../components/Footer.astro';
canonical="https://www.smart-roots.net/contatti" canonical="https://www.smart-roots.net/contatti"
noindex={true} noindex={true}
> >
<script slot="head" src="https://js.hcaptcha.com/1/api.js" async defer></script>
<Nav variant="back" /> <Nav variant="back" />
@@ -67,6 +68,8 @@ import Footer from '../components/Footer.astro';
></textarea> ></textarea>
</div> </div>
<div class="h-captcha" data-sitekey="813a0980-869a-42f4-9a7c-abaae7dccd96" style="margin-bottom:1.5rem"></div>
<button type="submit" class="btn-submit" id="submit-btn"> <button type="submit" class="btn-submit" id="submit-btn">
Invia messaggio Invia messaggio
</button> </button>
@@ -126,12 +129,23 @@ import Footer from '../components/Footer.astro';
okMsg.style.display = 'none'; okMsg.style.display = 'none';
errMsg.style.display = 'none'; errMsg.style.display = 'none';
const hcaptchaToken = (document.querySelector('[name="h-captcha-response"]') as HTMLInputElement)?.value;
if (!hcaptchaToken) {
errMsg.innerHTML = errMsgDefault;
errMsg.textContent = 'Completa la verifica antispam.';
errMsg.style.display = 'block';
submitBtn.disabled = false;
submitBtn.textContent = 'Invia messaggio';
return;
}
const payload = { const payload = {
nome: (document.getElementById('nome') as HTMLInputElement).value.trim(), nome: (document.getElementById('nome') as HTMLInputElement).value.trim(),
azienda: (document.getElementById('azienda') as HTMLInputElement).value.trim(), azienda: (document.getElementById('azienda') as HTMLInputElement).value.trim(),
email: (document.getElementById('email') as HTMLInputElement).value.trim(), email: (document.getElementById('email') as HTMLInputElement).value.trim(),
ambito: (document.getElementById('ambito') as HTMLSelectElement).value, ambito: (document.getElementById('ambito') as HTMLSelectElement).value,
messaggio:(document.getElementById('messaggio') as HTMLTextAreaElement).value.trim(), messaggio: (document.getElementById('messaggio') as HTMLTextAreaElement).value.trim(),
hcaptchaToken,
}; };
try { try {