# 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; } ``` ### 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 ` ```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 ` ```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