Sécurise form-handler : retire token côté client, prépare proxy Worker
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
fa4b49e1b8
commit
1970b10089
98
cloudflare-worker/hubspot-proxy.js
Normal file
98
cloudflare-worker/hubspot-proxy.js
Normal file
@ -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' } }
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user