espace-paie-odentas/ODENTAS_SIGN_API.md
odentas b790faf12c feat: Implémentation complète du système Odentas Sign
- Remplacement de DocuSeal par solution souveraine Odentas Sign
- Système d'authentification OTP pour signataires (bcryptjs + JWT)
- 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers
- Interface moderne avec canvas de signature et animations (framer-motion, confetti)
- Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants)
- PDF viewer avec @react-pdf-viewer (compatible Next.js)
- Stockage S3: source/, signatures/, evidence/, signed/, certs/
- Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets
- Evidence bundle automatique (JSON metadata + timestamps)
- Templates emails: OTP et completion
- Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161)
- Mode test détecté automatiquement (emails whitelist)
- Tests complets avec PDF CDDU réel (2 signataires)
2025-10-27 19:03:07 +01:00

496 lines
12 KiB
Markdown

# Odentas Sign - API Documentation
**Système de signature électronique souverain conforme eIDAS**
## 🎯 Vue d'ensemble
Odentas Sign remplace DocuSeal par une solution totalement interne et souveraine pour la signature des contrats de travail. Le système comprend :
- **Odentas Sign** : Interface de signature avec authentification OTP
- **Odentas Seal** : Scellage PAdES avec KMS (lambda-odentas-pades-sign)
- **Odentas TSA** : Horodatage RFC3161 (lambda-tsaStamp)
- **Odentas Archive** : Stockage S3 avec Object Lock 10 ans
## 📁 Structure du bucket S3 `odentas-sign`
```
odentas-sign/
├── source/ # PDFs non signés
├── signed/ # PDFs signés et scellés
├── evidence/ # Bundles de preuves (JSON)
├── signatures/ # Images de signatures
└── certs/ # Certificats de scellage
```
## 🔐 Architecture des tables Supabase
### `sign_requests`
Demandes de signature électronique
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | uuid | Identifiant unique |
| `ref` | text | Référence du contrat (ex: CDDU-2025-0102) |
| `title` | text | Titre du document |
| `source_s3_key` | text | Clé S3 du PDF source |
| `status` | text | `pending`, `in_progress`, `completed`, `cancelled` |
| `created_at` | timestamp | Date de création |
### `signers`
Signataires (Employeur + Salarié)
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | uuid | Identifiant unique |
| `request_id` | uuid | Référence à `sign_requests` |
| `role` | text | `Employeur` ou `Salarié` |
| `name` | text | Nom du signataire |
| `email` | text | Email pour OTP |
| `otp_hash` | text | Hash bcrypt du code OTP |
| `otp_expires_at` | timestamp | Expiration du code (15 min) |
| `otp_attempts` | integer | Nombre de tentatives (max 3) |
| `otp_last_sent_at` | timestamp | Dernière date d'envoi OTP |
| `signed_at` | timestamp | Date de signature |
| `signature_image_s3` | text | Clé S3 de l'image de signature |
| `consent_text` | text | Texte de consentement accepté |
| `consent_at` | timestamp | Date du consentement |
| `ip_signed` | inet | Adresse IP lors de la signature |
| `user_agent` | text | User-Agent du navigateur |
### `sign_positions`
Positions des champs de signature dans le PDF
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | uuid | Identifiant unique |
| `request_id` | uuid | Référence à `sign_requests` |
| `role` | text | `Employeur` ou `Salarié` |
| `page` | integer | Numéro de page (1-indexed) |
| `x`, `y`, `w`, `h` | numeric | Position et dimensions |
| `kind` | text | `signature`, `text`, `date`, `checkbox` |
| `label` | text | Label optionnel |
### `sign_events`
Logs d'audit complets
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigserial | Identifiant auto-incrémenté |
| `request_id` | uuid | Référence à `sign_requests` |
| `signer_id` | uuid | Référence à `signers` (optionnel) |
| `ts` | timestamp | Horodatage de l'événement |
| `event` | text | Type d'événement (voir ci-dessous) |
| `ip` | text | Adresse IP |
| `user_agent` | text | User-Agent |
| `metadata` | jsonb | Données additionnelles |
**Types d'événements :**
- `request_created` : Demande créée
- `otp_sent` : OTP envoyé
- `otp_verified` : OTP vérifié avec succès
- `otp_verification_failed` : OTP incorrect
- `otp_max_attempts_exceeded` : Trop de tentatives
- `signed` : Signature enregistrée
- `all_signed` : Tous les signataires ont signé
- `sealing_started` : Début du scellage
- `request_completed` : Demande complétée
- `request_cancelled` : Demande annulée
### `sign_assets`
Assets finaux (PDF signé, preuves, horodatage)
| Colonne | Type | Description |
|---------|------|-------------|
| `request_id` | uuid | Référence à `sign_requests` |
| `signed_pdf_s3_key` | text | Clé S3 du PDF signé et scellé |
| `evidence_json_s3_key` | text | Clé S3 du bundle de preuves |
| `tsa_tsr_s3_key` | text | Clé S3 de la réponse TSA |
| `pdf_sha256` | text | Hash SHA-256 du PDF |
| `tsa_token_sha256` | text | Hash SHA-256 du token TSA |
| `sealed_at` | timestamp | Date du scellage |
| `seal_algo` | text | Algorithme utilisé (RSASSA_PSS_SHA_256) |
| `seal_kms_key_id` | text | ID de la clé KMS |
| `tsa_policy_oid` | text | OID de la politique TSA |
| `tsa_serial` | text | Numéro de série TSA |
| `retain_until` | timestamp | Date de fin de rétention (10 ans) |
## 🔌 Endpoints API
### 1. Créer une demande de signature
**POST** `/api/odentas-sign/requests/create`
```typescript
// Request
{
contractId: string; // ID du contrat dans cddu_contracts
contractRef: string; // Numéro du contrat (ex: CDDU-2025-0102)
pdfS3Key: string; // Clé S3 du PDF source
title: string; // Titre du document
signers: [
{
role: 'Employeur' | 'Salarié';
name: string;
email: string;
}
];
positions: [ // Optionnel
{
role: 'Employeur' | 'Salarié';
page: number; // Numéro de page (1-indexed)
x: number; // Position X (en points)
y: number; // Position Y (en points)
w: number; // Largeur
h: number; // Hauteur
kind?: 'signature'; // Type de champ
label?: string; // Label optionnel
}
];
}
// Response 201
{
success: true;
request: {
id: string;
ref: string;
title: string;
status: 'pending';
created_at: string;
};
signers: [
{
signerId: string;
role: string;
name: string;
email: string;
signatureUrl: string; // URL pour ce signataire
}
];
}
```
### 2. Récupérer les détails d'une demande
**GET** `/api/odentas-sign/requests/[id]`
```typescript
// Response 200
{
success: true;
request: {
id: string;
ref: string;
title: string;
source_s3_key: string;
status: string;
created_at: string;
progress: {
total: number;
signed: number;
percentage: number;
};
};
signers: Array<{
id: string;
role: string;
name: string;
email: string;
signed_at: string | null;
signature_image_s3: string | null;
}>;
positions: Array<Position>;
}
```
### 3. Envoyer un code OTP
**POST** `/api/odentas-sign/signers/[id]/send-otp`
```typescript
// Response 200
{
success: true;
message: 'Code de vérification envoyé par email';
expires_at: string;
signer: {
name: string;
email: string;
};
}
// Response 429 (trop rapide)
{
error: 'Veuillez attendre X secondes avant de redemander un code';
}
```
### 4. Vérifier le code OTP
**POST** `/api/odentas-sign/signers/[id]/verify-otp`
```typescript
// Request
{
otp: string; // Code à 6 chiffres
}
// Response 200
{
success: true;
message: 'Code vérifié avec succès';
sessionToken: string; // JWT valide 30 minutes
signer: {
id: string;
name: string;
email: string;
role: string;
};
request: {
id: string;
ref: string;
title: string;
};
}
// Response 401 (code incorrect)
{
error: 'Code incorrect. X tentative(s) restante(s).';
remainingAttempts: number;
}
```
### 5. Enregistrer la signature
**POST** `/api/odentas-sign/signers/[id]/sign`
**Headers :** `Authorization: Bearer <sessionToken>`
```typescript
// Request
{
signatureImageBase64: string; // data:image/png;base64,xxx
consentText: string; // Texte de consentement accepté
}
// Response 200
{
success: true;
message: 'Signature enregistrée avec succès';
signed_at: string;
all_signed: boolean; // true si tous ont signé
signer: {
id: string;
name: string;
email: string;
role: string;
};
request: {
id: string;
ref: string;
title: string;
};
}
```
### 6. Vérifier le statut d'un signataire
**GET** `/api/odentas-sign/signers/[id]/status`
**Headers (optionnel) :** `Authorization: Bearer <sessionToken>`
```typescript
// Response 200
{
success: true;
signer: {
id: string;
role: string;
name: string;
email: string;
has_signed: boolean;
signed_at: string | null;
};
request: {
id: string;
ref: string;
title: string;
status: string;
created_at: string;
progress: {
total: number;
signed: number;
percentage: number;
};
};
other_signers: Array<{
role: string;
name: string;
has_signed: boolean;
}>;
}
```
### 7. Annuler une demande
**POST** `/api/odentas-sign/requests/[id]/cancel`
```typescript
// Request
{
reason?: string; // Raison de l'annulation
}
// Response 200
{
success: true;
message: 'Demande annulée avec succès';
request: {
id: string;
ref: string;
status: 'cancelled';
};
}
```
### 8. Webhook de completion (interne)
**POST** `/api/odentas-sign/webhooks/completion`
Appelé automatiquement quand tous les signataires ont signé.
Lance le workflow de scellage PAdES + TSA + Archive.
```typescript
// Request
{
requestId: string;
}
// Response 200
{
success: true;
message: 'Workflow de scellage lancé';
request: {
id: string;
ref: string;
status: 'completed';
};
evidence_key: string; // Clé S3 du bundle de preuves
}
```
## 🔄 Workflow complet
```
1. Création du contrat
2. Génération du PDF → Upload vers source/
3. Appel API /requests/create
4. Création sign_request + signers + positions
5. Envoi emails avec liens de signature
6. Signataire clique sur le lien
7. Authentification OTP (/send-otp → /verify-otp)
8. Affichage du PDF + capture signature
9. Validation signature (/sign)
10. Upload image vers signatures/
11. Vérification si tous ont signé
12. Si oui : webhook /webhooks/completion
13. Lambda orchestration :
- Injection signatures visuelles dans PDF
- Scellage PAdES (lambda-odentas-pades-sign)
- Horodatage TSA (lambda-tsaStamp)
- Création evidence bundle
- Upload vers signed/ et evidence/
- Copie vers archive avec Object Lock 10 ans
14. Mise à jour sign_assets
15. Emails de confirmation
```
## 🔒 Conformité eIDAS
### Niveau actuel : SES (Signature Électronique Simple)
**Implémenté :**
- Authentification par OTP (email vérifié)
- Consentement explicite
- Horodatage qualifié (TSA RFC3161)
- Logs d'audit complets et immuables
- Archivage à 10 ans avec Object Lock
🔨 **Pour passer à AES (Signature Électronique Avancée) :**
- Ajouter certificat qualifié du signataire
- Lier la signature à un dispositif sécurisé
🔨 **Pour passer à QES (Signature Électronique Qualifiée) :**
- Utiliser un QSCD (Qualified Signature Creation Device)
- Intégrer avec un PSC (Prestataire de Services de Confiance) qualifié
## 🛠️ Variables d'environnement
```env
# Supabase
NEXT_PUBLIC_SUPABASE_URL=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx
# S3
AWS_REGION=eu-west-3
ODENTAS_SIGN_BUCKET=odentas-sign
# KMS & TSA
KMS_KEY_ID=xxx
TSA_URL=https://timestamp.sectigo.com
# JWT
JWT_SECRET=xxx # Ou utilise NEXTAUTH_SECRET
# App
NEXT_PUBLIC_APP_URL=https://espace-paie.odentas.fr
```
## 📝 TODO Phase 2
- [ ] Interface frontend de signature (/app/signer/[requestId]/[signerId])
- [ ] Lambda d'orchestration (injection signatures + PAdES + TSA)
- [ ] Template email OTP
- [ ] Templates emails de notification (completion)
- [ ] Migration depuis DocuSeal (mode parallèle)
- [ ] Dashboard admin pour suivre les signatures
- [ ] API de téléchargement du PDF final
- [ ] Webhooks pour notifier le système principal
## 🧪 Tests
TODO: Ajouter tests unitaires et d'intégration
```bash
# Exemple de test avec curl
curl -X POST http://localhost:3000/api/odentas-sign/requests/create \
-H "Content-Type: application/json" \
-d '{
"contractId": "xxx",
"contractRef": "CDDU-2025-0102",
"pdfS3Key": "source/contrat.pdf",
"title": "Contrat de travail CDDU",
"signers": [
{"role": "Employeur", "name": "Jean Dupont", "email": "jean@company.fr"},
{"role": "Salarié", "name": "Marie Martin", "email": "marie@email.fr"}
]
}'
```
---
**Dernière mise à jour :** 27 octobre 2025