espace-paie-odentas/SIGNATURE_MULTI_PARTIES.md

210 lines
7.9 KiB
Markdown

# Signatures Multiples avec /Name et /Reason - Odentas Sign
## 📋 Concept
Quand plusieurs personnes signent un document via Odentas Sign (employeur puis salarié), **chaque signature est techniquement signée par Odentas** (avec le certificat KMS), mais avec des métadonnées personnalisées indiquant **pour qui** la signature a été faite.
C'est exactement ce que fait DocuSeal : quand vous ouvrez un PDF dans Adobe, vous voyez "DocuSeal" comme signataire technique, mais les champs `/Name` et `/Reason` indiquent le vrai signataire.
## 🔑 Architecture
### Tables Supabase utilisées
**Pas besoin de nouvelles tables !** Votre système existant est parfait :
1. **`sign_requests`** : La demande de signature
2. **`signers`** : Les signataires (Employeur + Salarié)
- **NOUVELLES colonnes** :
- `signature_name` : Nom affiché dans la signature (`/Name`)
- `signature_reason` : Raison de la signature (`/Reason`)
- `signature_location` : Lieu (`/Location`)
- `signature_contact_info` : Contact (`/ContactInfo`)
3. **`sign_positions`** : Où placer les signatures
4. **`sign_events`** : Audit trail
5. **`sign_assets`** : Document final scellé
### Workflow de signature
```
1. Création de la demande
├─ sign_requests (status: pending)
├─ signers[0] (Employeur, signature_name: "Entreprise SARL", signature_reason: "Signature employeur")
└─ signers[1] (Salarié, signature_name: "Jean Dupont", signature_reason: "Signature salarié")
2. Employeur signe (interface graphique)
├─ signers[0].signed_at = now()
└─ signers[0].signature_image_s3 = "signatures/req-123/employeur.png"
3. Salarié signe (interface graphique)
├─ signers[1].signed_at = now()
└─ signers[1].signature_image_s3 = "signatures/req-123/salarie.png"
4. Scellement PAdES (API /seal-document)
├─ Lambda sign avec signers[0] → /Name="Entreprise SARL" /Reason="Signature employeur"
│ └─ PDF devient: signed-pades/contract-123-step1.pdf (1 signature PAdES)
└─ Lambda sign avec signers[1] → /Name="Jean Dupont" /Reason="Signature salarié"
└─ PDF final: signed-pades/contract-123-final.pdf (2 signatures PAdES)
5. Résultat
└─ sign_assets.signed_pdf_s3_key = "signed-pades/contract-123-final.pdf"
```
## 🔧 Modifications nécessaires
### 1. Migration Supabase
**Fichier** : `supabase/migrations/20251029_add_signature_metadata_to_signers.sql`
```sql
ALTER TABLE signers
ADD COLUMN signature_name TEXT NOT NULL,
ADD COLUMN signature_reason TEXT NOT NULL,
ADD COLUMN signature_location TEXT DEFAULT 'France',
ADD COLUMN signature_contact_info TEXT;
```
### 2. Lambda `odentas-pades-sign`
**Input modifié** :
```javascript
{
"s3Key": "source/contract-123.pdf",
"signatureMetadata": {
"name": "Entreprise SARL", // → /Name dans le dictionnaire de signature
"reason": "Signature employeur", // → /Reason
"location": "Paris, France", // → /Location
"contactInfo": "contact@entreprise.com" // → /ContactInfo
},
"signerOrder": 1 // 1ère signature ou 2ème
}
```
**Dans la Lambda**, lors de la création du dictionnaire de signature :
```javascript
const signatureDict = {
Type: 'Sig',
Filter: 'Adobe.PPKLite',
SubFilter: 'ETSI.CAdES.detached',
Name: signatureMetadata.name, // ← ICI
Reason: signatureMetadata.reason, // ← ICI
Location: signatureMetadata.location, // ← ICI
ContactInfo: signatureMetadata.contactInfo, // ← ICI
M: `D:${timestamp}`,
ByteRange: [0, 0, 0, 0],
Contents: '<signature_hex>'
};
```
### 3. API Route `/api/odentas-sign/seal-document`
**Fichier** : `app/api/odentas-sign/seal-document/route.ts`
Cette route :
1. Récupère tous les `signers` ayant signé
2. Les trie par ordre chronologique (`signed_at`)
3. Appelle la Lambda séquentiellement pour chaque signataire
4. Chaque appel Lambda utilise les métadonnées du signataire (`signature_name`, `signature_reason`)
5. Le PDF de sortie d'une signature devient l'entrée de la suivante
6. Enregistre le PDF final dans `sign_assets`
### 4. Quand créer les signataires
**Dans** : `app/api/odentas-sign/requests/create/route.ts`
```typescript
const signersData = body.signers.map((s) => ({
request_id: signRequest.id,
role: s.role,
name: s.name,
email: s.email,
// Nouvelles colonnes
signature_name: s.name, // ou entreprise.name pour l'employeur
signature_reason: `Signature du contrat ${signRequest.ref} en tant que ${s.role.toLowerCase()}`,
signature_location: 'France',
signature_contact_info: s.email,
}));
```
## 📊 Résultat dans Adobe Acrobat
Quand vous ouvrez le PDF final dans Adobe :
```
┌─────────────────────────────────────────┐
│ Signatures du document (2) │
├─────────────────────────────────────────┤
│ ✓ Odentas Media SAS │ ← Certificat technique
│ Nom: Entreprise SARL │ ← /Name
│ Raison: Signature employeur │ ← /Reason
│ Lieu: Paris, France │ ← /Location
│ Contact: contact@entreprise.com │ ← /ContactInfo
│ Date: 29/10/2025 14:30:00 │
├─────────────────────────────────────────┤
│ ✓ Odentas Media SAS │ ← Même certificat
│ Nom: Jean Dupont │ ← /Name différent
│ Raison: Signature salarié │ ← /Reason différent
│ Lieu: France │
│ Contact: jean.dupont@example.com │
│ Date: 29/10/2025 15:45:00 │
└─────────────────────────────────────────┘
```
## 🔄 Flux complet
```mermaid
sequenceDiagram
participant UI as Interface Web
participant API as API Odentas Sign
participant DB as Supabase
participant Lambda as Lambda PAdES Sign
participant S3 as AWS S3
UI->>API: POST /requests/create
API->>DB: INSERT sign_requests
API->>DB: INSERT signers (avec signature_name/reason)
API->>S3: Upload PDF source
UI->>API: Employeur signe (interface)
API->>DB: UPDATE signers[0].signed_at
API->>S3: Upload signature image
UI->>API: Salarié signe (interface)
API->>DB: UPDATE signers[1].signed_at
API->>S3: Upload signature image
UI->>API: POST /seal-document
API->>DB: SELECT signers ORDER BY signed_at
loop Pour chaque signataire
API->>Lambda: Invoke avec signature_metadata
Lambda->>S3: GET PDF (source ou précédent)
Lambda->>Lambda: Ajouter signature PAdES
Lambda->>S3: PUT PDF signé
Lambda-->>API: signedS3Key
end
API->>DB: INSERT sign_assets (PDF final)
API->>DB: UPDATE sign_requests (status: completed)
API-->>UI: Document scellé
```
## ✅ Avantages
1. **Pas de nouvelles tables** : Réutilise `signers` existant
2. **Flexible** : Peut gérer 2, 3 ou N signatures
3. **Conforme PAdES** : Chaque signature est indépendante et valide
4. **Audit trail** : `sign_events` trace tout le processus
5. **Compatible Adobe** : Affiche correctement les noms dans Adobe Acrobat
6. **Identique DocuSeal** : Même principe technique
## 🎯 Prochaines étapes
1. ✅ Appliquer la migration Supabase
2. ⏳ Modifier la Lambda pour accepter `signatureMetadata`
3. ⏳ Tester avec un contrat CDDU
4. ⏳ Vérifier dans Adobe que `/Name` et `/Reason` apparaissent correctement
5. ⏳ Intégrer l'appel à `/seal-document` dans votre workflow existant
## 📝 Notes
- Le **certificat est toujours Odentas** (émis par AWS KMS)
- Les **métadonnées changent** pour chaque signataire
- Les signatures sont **appliquées séquentiellement** (importante pour la validité)
- Chaque signature **englobe la précédente** (signature incrémentale PAdES)