From 8cb366ee53b0b0d5bdf19342b36b79a7ba837956 Mon Sep 17 00:00:00 2001 From: odentas Date: Thu, 23 Oct 2025 17:32:56 +0200 Subject: [PATCH] chore: RGPD compliance audit & cleanup + UX signature salarie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Audit RGPD complet: création RGPD_AUDIT_LOCALISATION_DONNEES.md - Suppression complète intégration n8n (API route, hooks, env vars) - Suppression variables Airtable (env vars) - Confirmation GoCardless: serveurs EEE + SCC - 8/9 services confirmés UE (89% compliance) - Ajout message info dans modale signature salarie (scroll down) --- .env.example | 4 - .env.local.example | 7 - RGPD_AUDIT_LOCALISATION_DONNEES.md | 235 ++++ app/(app)/contrats/[id]/page.tsx | 26 - app/(app)/contrats/demo/page.tsx | 27 - app/api/contrats/[id]/virement/route.ts | 73 -- .../DocuSealSignatureModal.tsx | 14 +- hub_signature_batch.html | 1063 ----------------- 8 files changed, 246 insertions(+), 1203 deletions(-) create mode 100644 RGPD_AUDIT_LOCALISATION_DONNEES.md delete mode 100644 app/api/contrats/[id]/virement/route.ts delete mode 100644 hub_signature_batch.html diff --git a/.env.example b/.env.example index efe3e72..f3efed2 100644 --- a/.env.example +++ b/.env.example @@ -20,12 +20,8 @@ AWS_REGION=eu-west-3 AWS_ACCESS_KEY_ID=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-key STRUCTURE_API_TOKEN=your-api-token -AIRTABLE_BASE_ID=your-base-id -AIRTABLE_API_KEY=your-airtable-pat -AIRTABLE_TABLE_CONTRATS=Contrats de travail UPSTREAM_API_BASE=https://your-api-gateway.amazonaws.com/default UPSTREAM_API_PREFIX= -N8N_NOTES_WEBHOOK_URL=https://your-n8n.com/webhook/... # DocuSeal direct API (recommended) DOCUSEAL_API_BASE=https://api.docuseal.com diff --git a/.env.local.example b/.env.local.example index e25ff32..e055186 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,12 +1,5 @@ NEXT_PUBLIC_API_BASE=https://api-dev.odentas.fr -AIRTABLE_BASE_ID= -AIRTABLE_API_KEY= -AIRTABLE_TABLE_CONTRATS=Contrats de travail DOCUSEAL_API_BASE= DOCUSEAL_TOKEN= # Optional fallback # DOCUSEAL_PROXY_BASE= - -# Webhook n8n (virement effectué → mise à jour Airtable) -# Exemple: https://n8n.example.com/webhook/virement-effectue -N8N_VIREMENT_WEBHOOK_URL= diff --git a/RGPD_AUDIT_LOCALISATION_DONNEES.md b/RGPD_AUDIT_LOCALISATION_DONNEES.md new file mode 100644 index 0000000..8781281 --- /dev/null +++ b/RGPD_AUDIT_LOCALISATION_DONNEES.md @@ -0,0 +1,235 @@ +# 🇪🇺 Audit RGPD - Localisation des données clients + +> **Date de l'audit** : 23 octobre 2025 +> **Projet** : Nouvel Espace Paie Odentas +> **Objectif** : Vérifier que toutes les données clients restent dans l'Union Européenne + +--- + +## 📊 Tableau de conformité RGPD + +| Service | Localisation | Infrastructure | Statut RGPD | Données transférées | Action requise | +|---------|--------------|----------------|-------------|---------------------|----------------| +| **AWS S3** | 🇪🇺 eu-west-3 (Paris) | AWS | ✅ Conforme | Documents, logs emails | - | +| **AWS SES** | 🇪🇺 eu-west-3 (Paris) | AWS | ✅ Conforme | Envoi emails | - | +| **AWS Lambda** | 🇪🇺 eu-west-3 (Paris) | AWS API Gateway | ✅ Conforme | Processing données | - | +| **Supabase** | 🇪🇺 EU | Cloudflare + serveurs UE | ✅ Conforme | Base de données complète | - | +| **Vercel Functions** | 🇪🇺 cdg1 (Paris) | Vercel Edge | ✅ Conforme | API routes, exécution | - | +| **Docuseal** | 🇪🇺 api.docuseal.eu | Version EU | ✅ Conforme | Signatures électroniques | - | +| **PostHog** | 🇪🇺 eu.i.posthog.com | Instance EU | ✅ Conforme | Analytics utilisateurs | - | +| **GoCardless** | 🇪🇺 EEE | Serveurs EEE + SCC UE | ✅ Conforme | Mandats SEPA, paiements | ✅ **Confirmé par support** | +| **PDFMonkey** | ⚠️ **En attente** | Heroku + AWS (région ?) | ⚠️ **À confirmer** | Contrats CDDU (données salariés) | **Mail envoyé - en attente réponse** | + +--- + +## 🔍 Détails par service + +### ✅ Services conformes RGPD + +#### AWS (S3, SES, Lambda) +- **Région** : `eu-west-3` (Paris, France) +- **Configuration** : `AWS_REGION=eu-west-3` +- **Données stockées** : + - S3 bucket `odentas-docs` : Documents PDF, contrats, fiches de paie + - S3 bucket `stockage-logs-emails` : Logs d'envoi d'emails + - Lambda : Processing des données via API Gateway +- **Statut** : ✅ **100% conforme** + +#### Supabase +- **Instance** : `fusqtpjififcmgbhmosq.supabase.co` +- **Infrastructure** : Serveurs EU via Cloudflare +- **Données stockées** : Base de données complète (profils, organisations, salariés, contrats, etc.) +- **Statut** : ✅ **100% conforme** + +#### Vercel +- **Région Functions** : `cdg1` (Paris, France) +- **Configuration** : `vercel.json` → `"regions": ["cdg1"]` +- **Important** : Les builds se font aux USA mais **aucune donnée client** n'y transite + - Le code source est compilé aux USA + - Le code compilé est déployé sur cdg1 + - Les données clients ne quittent jamais cdg1 +- **Statut** : ✅ **100% conforme** + +#### Docuseal +- **API** : `https://api.docuseal.eu` +- **Version** : Européenne (`.eu`) +- **Données** : Signatures électroniques (contrats, avenants) +- **Statut** : ✅ **100% conforme** + +#### PostHog +- **Instance** : `https://eu.i.posthog.com` +- **Configuration** : + ```env + NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com + ``` +- **Données** : Analytics utilisateurs (événements, pages vues) +- **Statut** : ✅ **100% conforme** + +--- + +### ⚠️ Services à vérifier + +#### PDFMonkey +- **API** : `https://api.pdfmonkey.io` +- **Entreprise** : Française (Paris - 51 rue de Ponthieu, 75008) +- **Infrastructure déclarée** : Heroku + AWS (région non spécifiée) +- **Données envoyées** : + - Informations contrats CDDU (nom, prénom, dates, salaires) + - Informations structure employeur + - Informations production +- **Problème** : Heroku héberge par défaut aux USA (`us-east-1`) +- **Action** : + - ✅ Mail envoyé à `tinymonkey@pdfmonkey.io` le 23/10/2025 + - ⏳ En attente de confirmation de la région de traitement +- **Statut** : ⚠️ **En attente de réponse** + +#### GoCardless +- **Environment** : `live` +- **API** : GoCardless UK/EU +- **Infrastructure** : Serveurs dans l'Espace Économique Européen (EEE) +- **Données** : Mandats SEPA, paiements, coordonnées bancaires +- **Mécanismes de protection** : + - Opérations principales de traitement des paiements : ✅ Serveurs EEE + - Prestataires tiers (hors EEE) : ✅ Clauses Contractuelles Types (SCC) de l'UE + - Contrôle préalable des fournisseurs avec mécanismes RGPD approuvés +- **Documentation** : + - https://gocardless.com/legal/data-protection/ + - https://gocardless.com/fr-fr/privacy/fr-gdpr/ +- **Confirmation** : ✅ Support GoCardless - 23 octobre 2025 +- **Statut** : ✅ **Conforme RGPD** + +--- + +## 📝 Notes sur les services retirés + +### 🚨 Priorité CRITIQUE + +1. **PDFMonkey - Attendre réponse** + - ✅ Mail envoyé le 23/10/2025 + - ⏳ Attendre confirmation de la localisation des serveurs + - Si réponse négative (serveurs hors EU) → **Migrer vers alternative** : + - DocRaptor (option EU disponible) + - PDF.co (serveurs EU) + - Solution auto-hébergée (Puppeteer + AWS Lambda eu-west-3) + +--- + +## 📝 Notes sur les services retirés + +### Airtable et n8n +Ces services ont été **complètement retirés** du projet le 23 octobre 2025 : + +#### Actions effectuées : +- ✅ Suppression de l'API route `/api/contrats/[id]/virement` +- ✅ Suppression du hook `useToggleVirement` dans les pages contrats +- ✅ Suppression des variables d'environnement n8n de `.env.local` +- ✅ Suppression des variables d'environnement Airtable de `.env.local` +- ✅ Nettoyage des fichiers `.env.example` et `.env.local.example` + +#### Résidus restants (inactifs) : +- Fichiers de backup : `.env.local.bak`, `.env.local.bak2` +- Fichier HTML standalone : `hub_signature_batch.html` +- Fichiers de documentation : `VIREMENTS_SALAIRES_STAFF_*.md` +- Commentaire dans `app/api/access/route.ts` (ligne 412) +- Lambda AWS ancienne : `/tmp/aws-toolkit-vscode/lambda/.../postDocuSealAvenantPDF/index.js` + +**Impact RGPD** : ✅ **Aucun** - Aucune donnée n'est envoyée à ces services + +--- + +## 🎯 Plan d'action + +### 🚨 Priorité CRITIQUE + +1. **PDFMonkey - Attendre réponse** + - ✅ Mail envoyé le 23/10/2025 + - ⏳ Attendre confirmation de la localisation des serveurs + - Si réponse négative (serveurs hors EU) → **Migrer vers alternative** : + - DocRaptor (option EU disponible) + - PDF.co (serveurs EU) + - Solution auto-hébergée (Puppeteer + AWS Lambda eu-west-3) + +--- + +## 📝 Emails et confirmations + +### Email envoyé à PDFMonkey + +**Date** : 23 octobre 2025 +**Destinataire** : `tinymonkey@pdfmonkey.io` +**Objet** : Question sur la localisation des données (RGPD) + +``` +Bonjour, + +Nous utilisons PDFMonkey pour générer des PDF contenant des données personnelles +de salariés (contrats de travail CDDU) et devons nous assurer de la conformité RGPD. + +Pouvez-vous nous confirmer dans quelle région AWS/Heroku sont hébergées et +traitées les données que nous vous envoyons via l'API ? + +Spécifiquement : +- Les données restent-elles dans l'Union Européenne ? +- Quelle est la région de vos serveurs de production (Heroku et AWS) ? + +Merci d'avance pour votre retour rapide. +``` + +### Réponse de GoCardless + +**Date** : 23 octobre 2025 +**Source** : Support GoCardless +**Statut** : ✅ **Conforme RGPD** + +**Résumé de la réponse** : +- ✅ **Opérations principales** : Serveurs dans l'Espace Économique Européen (EEE) +- ✅ **Prestataires tiers** : Clauses Contractuelles Types (SCC) de l'Union européenne +- ✅ **Contrôle préalable** : Mécanismes approuvés par le RGPD (constat d'adéquation, règles d'entreprise contraignantes) +- ✅ **Protection des données** : Normes de l'Union européenne respectées + +**Documentation** : +- Politique RGPD : https://gocardless.com/fr-fr/privacy/fr-gdpr/ +- Protection des données : https://gocardless.com/legal/data-protection/ + +**Citation** : +> "L'ensemble de nos principales opérations de traitement des paiements européens sont exécutées sur des serveurs situés dans l'Espace économique européen (EEE). [...] Dès lors que des données sont stockées dans ces services, nous veillons à ce qu'elles soient protégées selon les normes de l'Union européenne, en utilisant un mécanisme de transfert approuvé par le RGPD." + +--- + +## 📝 Notes importantes + +### Concernant Vercel +Les builds Vercel aux USA **n'ont AUCUN impact** sur les données clients : +- Le **code source** est compilé aux USA +- Le **code compilé** est déployé sur `cdg1` (Paris) +- Les **API Functions** s'exécutent sur `cdg1` +- Les **données clients** ne transitent **JAMAIS** par les serveurs de build +- Vercel ne stocke pas de données clients, uniquement des logs techniques + +✅ **Aucun risque RGPD** de ce côté. + +### CDNs externes +Plusieurs CDNs sont utilisés pour charger des bibliothèques JavaScript : +- `cdn.docuseal.com` (formulaires de signature) +- `cdn.jsdelivr.net` (Bootstrap, Flatpickr) +- `cdnjs.cloudflare.com` (Font Awesome) + +Ces CDNs peuvent servir depuis des serveurs hors UE, mais ils ne contiennent **aucune donnée client**, uniquement des fichiers statiques (CSS, JS, fonts). + +✅ **Pas de problème RGPD** : Seuls des assets publics transitent, pas de données personnelles. + +--- + +## 📊 Résumé + +| Statut | Nombre de services | +|--------|-------------------| +| ✅ Conforme RGPD | 8 services | +| ⚠️ À vérifier | 1 service | +| ❌ Non conforme | 0 service | +| ✅ Retiré | 2 services (Airtable, n8n) | + +### Taux de conformité actuel +**8 / 9 confirmés = 89%** + +Avec la vérification en cours (PDFMonkey), le taux devrait atteindre **100%**. diff --git a/app/(app)/contrats/[id]/page.tsx b/app/(app)/contrats/[id]/page.tsx index 5d902af..89f2ff5 100644 --- a/app/(app)/contrats/[id]/page.tsx +++ b/app/(app)/contrats/[id]/page.tsx @@ -406,30 +406,6 @@ function useContratDetail(id: string) { }); } -// Exemple mutation (toggle virement). Adapte au vrai endpoint si différent. -function useToggleVirement(id: string) { - const qc = useQueryClient(); - return useMutation({ - mutationFn: async (params: { value: boolean; contractRef?: string }) => { - const { value, contractRef } = params; - // Normaliser en "Oui" / "Non" pour l'écosystème (Airtable via n8n) - const status = value ? "Oui" : "Non"; - const res = await fetch(`/api/contrats/${id}/virement`, { - method: "POST", - headers: { "Content-Type": "application/json", Accept: "application/json" }, - credentials: "include", - body: JSON.stringify({ virement_effectue: status, contract_ref: contractRef || undefined }), - }); - if (!res.ok) { - const t = await res.text().catch(() => ""); - throw new Error(t || `HTTP ${res.status}`); - } - return (await res.json()) as { ok: boolean }; - }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["contrat", id] }), - }); -} - // ---------- UI helpers ---------- function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) { return ( @@ -862,8 +838,6 @@ export default function ContratPage() { } fetchSignedUrls(); }, [payslipsQuery.data]); - const toggleMut = useToggleVirement(id); - // Redirection de sécurité côté client useEffect(() => { diff --git a/app/(app)/contrats/demo/page.tsx b/app/(app)/contrats/demo/page.tsx index aa58ff9..387ef93 100644 --- a/app/(app)/contrats/demo/page.tsx +++ b/app/(app)/contrats/demo/page.tsx @@ -305,30 +305,6 @@ function useContratDetail(id: string) { // Pas de mode normal : cette page est TOUJOURS en mode démo } -// Exemple mutation (toggle virement). Adapte au vrai endpoint si différent. -function useToggleVirement(id: string) { - const qc = useQueryClient(); - return useMutation({ - mutationFn: async (params: { value: boolean; contractRef?: string }) => { - const { value, contractRef } = params; - // Normaliser en "Oui" / "Non" pour l'écosystème (Airtable via n8n) - const status = value ? "Oui" : "Non"; - const res = await fetch(`/api/contrats/${id}/virement`, { - method: "POST", - headers: { "Content-Type": "application/json", Accept: "application/json" }, - credentials: "include", - body: JSON.stringify({ virement_effectue: status, contract_ref: contractRef || undefined }), - }); - if (!res.ok) { - const t = await res.text().catch(() => ""); - throw new Error(t || `HTTP ${res.status}`); - } - return (await res.json()) as { ok: boolean }; - }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["contrat", id] }), - }); -} - // ---------- UI helpers ---------- function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) { return ( @@ -780,9 +756,6 @@ export default function ContratDemoPage() { // } // fetchSignedUrls(); // }, [payslipsQuery.data]); - - const toggleMut = useToggleVirement(id); - // Redirection de sécurité côté client useEffect(() => { diff --git a/app/api/contrats/[id]/virement/route.ts b/app/api/contrats/[id]/virement/route.ts deleted file mode 100644 index e810b2a..0000000 --- a/app/api/contrats/[id]/virement/route.ts +++ /dev/null @@ -1,73 +0,0 @@ -// app/api/contrats/[id]/virement/route.ts -import { NextRequest, NextResponse } from "next/server"; -import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; -import { cookies } from "next/headers"; - -export const dynamic = "force-dynamic"; -export const revalidate = 0; -export const runtime = "nodejs"; - -export async function POST(req: NextRequest, { params }: { params: { id: string } }) { - const contractId = params?.id; - if (!contractId) return NextResponse.json({ error: "missing_id" }, { status: 400 }); - - const supabase = createRouteHandlerClient({ cookies }); - const { data: { session } } = await supabase.auth.getSession(); - if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 }); - - let body: any = {}; - try { - body = await req.json(); - } catch { - return NextResponse.json({ error: "invalid_json" }, { status: 400 }); - } - - // Normaliser en "Oui" / "Non" - const raw = body?.virement_effectue; - const normalized = ((): "Oui" | "Non" => { - if (typeof raw === "string") { - const s = raw.trim().toLowerCase(); - return s === "oui" || s === "true" || s === "1" ? "Oui" : "Non"; - } - return raw ? "Oui" : "Non"; - })(); - - const contract_ref = String(body?.contract_ref || ""); - - const webhookUrl = process.env.N8N_VIREMENT_WEBHOOK_URL || process.env.N8N_WEBHOOK_VIREMENT_URL; - if (!webhookUrl) { - // Pas de webhook configuré, mais on répond OK pour ne pas bloquer l'UI - console.warn("ℹ️ N8N webhook URL not configured (N8N_VIREMENT_WEBHOOK_URL)"); - return NextResponse.json({ ok: true, skipped: true }); - } - - const payload = { - contract_id: contractId, - contract_ref: contract_ref || null, - virement_effectue: normalized, // "Oui" / "Non" - // Contexte utilisateur minimal - user_id: session.user.id, - user_email: session.user.email, - triggered_at: new Date().toISOString(), - }; - - try { - const res = await fetch(webhookUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - cache: "no-store", - }); - if (!res.ok) { - const text = await res.text().catch(() => ""); - console.error("❌ N8N webhook error:", res.status, text); - return NextResponse.json({ ok: false, error: "webhook_failed", status: res.status }, { status: 502 }); - } - } catch (e: any) { - console.error("💥 N8N webhook call threw:", e?.message || e); - return NextResponse.json({ ok: false, error: "webhook_exception" }, { status: 502 }); - } - - return NextResponse.json({ ok: true }); -} - diff --git a/app/signature-salarie/DocuSealSignatureModal.tsx b/app/signature-salarie/DocuSealSignatureModal.tsx index a68be54..ea79032 100644 --- a/app/signature-salarie/DocuSealSignatureModal.tsx +++ b/app/signature-salarie/DocuSealSignatureModal.tsx @@ -149,9 +149,17 @@ export default function DocuSealSignatureModal({
{/* Header */}
-

- Signature électronique du contrat -

+
+

+ Signature électronique du contrat +

+

+ + + + Descendez tout en bas du contrat pour accéder au champs de signature. +

+
-
- - - - - - - - - - - - - - - - \ No newline at end of file