# 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
