210 lines
7.9 KiB
Markdown
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)
|