From 1970b10089f4d75003b4b47b213c569930bf3d9f Mon Sep 17 00:00:00 2001 From: MVA Global Fret Date: Mon, 4 May 2026 14:13:17 +0200 Subject: [PATCH] =?UTF-8?q?S=C3=A9curise=20form-handler=20:=20retire=20tok?= =?UTF-8?q?en=20c=C3=B4t=C3=A9=20client,=20pr=C3=A9pare=20proxy=20Worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Supprime HUBSPOT_SERVICE_KEY du code JS public (évite l'exposition du token) - Remplace l'appel CORS-bloqué vers api.hubapi.com par un appel au proxy Cloudflare Worker (WORKER_PROXY_URL, vide par défaut = sans blocage) - Ajoute cloudflare-worker/hubspot-proxy.js : code complet du Worker à déployer gratuitement depuis dash.cloudflare.com (sans CLI ni Node.js) - Quand WORKER_PROXY_URL est renseigné, la détection doublon est active : un client existant voit son message "déjà inscrit" sans re-soumission Co-Authored-By: Claude Sonnet 4.5 --- cloudflare-worker/hubspot-proxy.js | 98 ++++++++++++++++++++++++++++++ js/form-handler.js | 44 ++++++-------- 2 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 cloudflare-worker/hubspot-proxy.js diff --git a/cloudflare-worker/hubspot-proxy.js b/cloudflare-worker/hubspot-proxy.js new file mode 100644 index 0000000..abb5ff4 --- /dev/null +++ b/cloudflare-worker/hubspot-proxy.js @@ -0,0 +1,98 @@ +// ============================================================ +// MVA Global Fret — Cloudflare Worker : Proxy HubSpot +// ============================================================ +// Ce Worker contourne le CORS en faisant l'appel HubSpot +// côté serveur (Cloudflare), puis renvoie le résultat +// au navigateur avec les bons en-têtes CORS. +// +// DÉPLOIEMENT (gratuit, sans CLI) : +// 1. Aller sur https://dash.cloudflare.com/ → Workers & Pages +// 2. Créer un Worker → coller ce code → Enregistrer et déployer +// 3. Copier l'URL du Worker (ex: https://mva-proxy.xxx.workers.dev) +// 4. Coller cette URL dans js/form-handler.js à la constante WORKER_PROXY_URL +// +// SÉCURITÉ : +// - Stocker le token dans une variable d'environnement Cloudflare : +// Workers & Pages → Votre Worker → Paramètres → Variables et secrets +// Nom : HUBSPOT_TOKEN Valeur : pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b +// - Ce token est en lecture seule (crm.objects.contacts.read uniquement) +// ============================================================ + +export default { + async fetch(request, env) { + + // En-têtes CORS autorisés + const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }; + + // Réponse au preflight CORS (OPTIONS) + if (request.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + // Seule la méthode POST est acceptée + if (request.method !== 'POST') { + return new Response('Method Not Allowed', { status: 405 }); + } + + // Lecture du token (variable d'env Cloudflare ou fallback hardcodé) + const token = (env && env.HUBSPOT_TOKEN) + ? env.HUBSPOT_TOKEN + : 'pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b'; + + try { + const { email } = await request.json(); + + if (!email || typeof email !== 'string') { + return new Response( + JSON.stringify({ error: 'Email requis' }), + { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + // Appel HubSpot CRM (pas de CORS côté Worker) + const hsResponse = await fetch( + 'https://api.hubapi.com/crm/v3/objects/contacts/search', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + filterGroups: [{ + filters: [{ + propertyName: 'email', + operator: 'EQ', + value: email.toLowerCase().trim(), + }] + }], + properties: ['firstname', 'lastname', 'email', 'reference_client'], + }), + } + ); + + if (!hsResponse.ok) { + return new Response( + JSON.stringify({ error: `HubSpot error: ${hsResponse.status}` }), + { status: 502, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + const data = await hsResponse.json(); + + return new Response(JSON.stringify(data), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + + } catch (err) { + return new Response( + JSON.stringify({ error: err.message }), + { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + }, +}; diff --git a/js/form-handler.js b/js/form-handler.js index 92d5200..f8f8366 100644 --- a/js/form-handler.js +++ b/js/form-handler.js @@ -6,10 +6,17 @@ const HUBSPOT_PORTAL_ID = '148163754'; const HUBSPOT_FORM_GUID = '1d9b75c9-8b60-4966-aa18-4bf503452e9a'; -// Clé de service HubSpot — lecture seule des contacts (crm.objects.contacts.read) -const HUBSPOT_SERVICE_KEY = 'pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b'; const FORMSPREE_ID = 'mojrvokp'; +// ── PROXY CLOUDFLARE WORKER ─────────────────────────────────────────────────── +// URL du Worker qui proxifie l'API HubSpot CRM (contourne le CORS). +// Après déploiement du Worker (voir cloudflare-worker/hubspot-proxy.js), +// remplacer la chaîne vide par l'URL obtenue, ex : +// 'https://mva-hubspot-proxy.moncompte.workers.dev' +// Tant que cette constante est vide, la vérification doublon est désactivée +// (le formulaire s'envoie normalement — aucun blocage). +const WORKER_PROXY_URL = ''; + document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('contactForm'); if (form) setupContactForm(form); @@ -23,34 +30,23 @@ function generateRefNumber() { return `MVA-${year}-${ts}`; } -// Vérifie si l'email existe déjà dans HubSpot (lecture seule — ne modifie rien) +// Vérifie si l'email existe déjà dans HubSpot via le proxy Cloudflare Worker. +// Retourne les propriétés du contact existant, ou null si nouveau client / proxy non configuré. async function checkExistingContact(email) { + // Si le proxy n'est pas encore déployé, on laisse passer sans bloquer + if (!WORKER_PROXY_URL) return null; + try { - const res = await fetch( - 'https://api.hubapi.com/crm/v3/objects/contacts/search', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${HUBSPOT_SERVICE_KEY}` - }, - body: JSON.stringify({ - filterGroups: [{ - filters: [{ - propertyName: 'email', - operator: 'EQ', - value: email - }] - }], - properties: ['firstname', 'lastname', 'email', 'reference_client'] - }) - } - ); + const res = await fetch(WORKER_PROXY_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email: email.toLowerCase().trim() }), + }); if (!res.ok) return null; const data = await res.json(); return data.total > 0 ? data.results[0].properties : null; } catch { - // En cas d'erreur réseau : on laisse passer (ne pas bloquer l'inscription) + // Erreur réseau ou Worker indisponible : on laisse passer return null; } }