Skip to content

Securite

Authentification a 2 niveaux

L'API Convergence utilise deux niveaux d'authentification :

┌──────────────┐         ┌────────────────┐         ┌──────────────┐
│  Client CFA  │         │    Opcovia     │         │  API OPCO    │
│              │         │                │         │              │
│ X-API-KEY ──────────► │ X-API-KEY ─────────────► │              │
│              │         │                │         │              │
│              │         │ Bearer token ─────────► │              │
│              │         │ (OAuth2, gere  │         │              │
│              │         │  cote serveur) │         │              │
└──────────────┘         └────────────────┘         └──────────────┘

Niveau 1 : Bearer Token (OAuth2 client_credentials)

  • Identifie l'editeur (Eduvia) aupres de l'OPCO
  • Obtenu via OAuth2 client_credentials grant (client_id + client_secret)
  • Gere cote serveur par le TokenManager : le CFA ne le voit jamais
  • Refresh automatique avant expiration
  • Stocke en memoire uniquement (pas en DB, pas en Redis)

Niveau 2 : X-API-KEY (par CFA)

  • Identifie le CFA aupres de l'OPCO
  • Fourni par le CFA dans chaque requete HTTP
  • Forwarde tel quel a l'API OPCO
  • Chaque CFA a sa propre cle, attribuee par l'OPCO

Le CFA ne fournit que son X-API-KEY. Opcovia ajoute automatiquement le Bearer token.

Flux d'authentification complet

Isolation tenant (tenantHash)

Toute l'isolation multi-tenant repose sur un hash de l'API key :

tenantHash = SHA-256(X-API-KEY)

Ce hash est utilise comme cle de partition dans :

RessourceUsage du tenantHash
Cache RedisInclus dans la cle : opcovia:cache:{opcoId}:{tenantHash}:...
Jobs BullMQChamp tenantHash dans chaque job
audit_logsColonne tenant_hash
job_resultsColonne tenant_hash
webhooksColonne tenant_hash — un CFA ne voit que ses webhooks
webhook_deliveriesFiltrees par webhook (lie au tenant)
snapshotsColonne tenant_hash
sync_jobsColonne tenant_hash

Un CFA ne peut jamais acceder aux donnees d'un autre CFA.

Chiffrement des donnees sensibles

API keys en base de donnees

Les X-API-KEY sont chiffrees avec AES-256-GCM avant stockage :

Format : iv:tag:ciphertext (hex)

Cle de chiffrement : MASTER_KEY (env var, 32 bytes hex)
Algorithme : AES-256-GCM
IV : 12 bytes aleatoires par valeur
Tag : authentification integrite (GCM)

Tables concernees :

  • job_results.api_key_encrypted — dechiffree par le worker au traitement
  • sync_jobs.api_key_encrypted — dechiffree pour chaque batch sync
  • poller_registrations.api_key_encrypted — dechiffree pour chaque poll

Fichiers en base de donnees

Les fichiers PDF (factures, conventions, certificats) sont chiffres AES-256-GCM en DB (job_results.files_encrypted) et purges apres traitement :

POST /factures (multipart)
  → Producer : chiffre fichier → INSERT en DB
  → Redis : payload SANS fichier (trop volumineux)
  → Worker : dechiffre fichier depuis DB
  → Pipeline : envoie a l'OPCO
  → Worker : UPDATE files_encrypted = NULL  ← purge

Les fichiers ne transitent jamais par Redis et ne restent en DB que le temps du traitement.

Donnees sensibles dans les logs

Le sanitizer redacte automatiquement les champs sensibles dans les logs et les payloads d'audit :

  • API keys masquees
  • Donnees personnelles redactees dans les logs

Protection SSRF (webhooks)

Les URL de callback des webhooks sont validees contre les attaques SSRF :

typescript
// Regle : HTTPS obligatoire (sauf dev)
// Interdit :
//   - localhost, 127.0.0.1, ::1
//   - 10.x.x.x
//   - 192.168.x.x
//   - 172.16-31.x.x
//   - *.local, *.internal

De plus, les redirections HTTP sont bloquees (redirect: 'manual').

Hub : authentification JWT

Connexion WebSocket

Les instances Opcovia s'authentifient aupres du hub via une API key passee en query param :

ws://hub.opcovia.dev:4000/ws?apiKey=<hub-api-key>

Les API keys sont stockees dans un fichier YAML (api-keys.yaml) et gerees par le KeyStore. Chaque cle est associee a un tenantId et un name.

JWT de grant

Chaque grant inclut un JWT signe HMAC-SHA256 :

json
{
  "opcoId": "opco-ep",
  "method": "GET",
  "route": "/dossiers",
  "instanceId": "inst-42",
  "iat": 1711728000,
  "exp": 1711728030,
  "iss": "opcovia-hub"
}

Ce JWT est envoye dans le header Opcovia-Hub-Token lors de l'appel OPCO. Il prouve que l'appel a ete autorise par le hub et permet le tracing.

API Admin

L'API admin du hub est protegee par un HUB_ADMIN_KEY distinct :

Authorization: Bearer <admin-key>

Routes admin : gestion des API keys, listing des instances, rechargement de config.

Resume des secrets

SecretStockageUsage
MASTER_KEYEnv varChiffrement AES-256-GCM des API keys et fichiers en DB
OPCO_CLIENT_ID / OPCO_CLIENT_SECRETEnv varOAuth2 client_credentials pour obtenir le Bearer token
X-API-KEY (CFA)Chiffre en DB (AES-256-GCM)Identifie le CFA aupres de l'OPCO
HUB_JWT_SECRETEnv var hubSignature HMAC des JWT de grant
HUB_ADMIN_KEYEnv var hubProtection API admin
Hub API keysFichier YAMLAuthentification WebSocket des instances
Webhook secretsDB (plain, generes)Signature HMAC-SHA256 des payloads webhook

Bonnes pratiques appliquees

  • Secrets jamais dans Redis : les API keys et fichiers ne transitent que par PostgreSQL (chiffres)
  • Purge apres traitement : les fichiers sont supprimes de la DB des que le worker a termine
  • Pas de PII dans les logs : le sanitizer redacte automatiquement
  • Pas de redirections HTTP : les webhooks utilisent redirect: 'manual'
  • Timeouts partout : 30s pour les appels OPCO, 10s pour les webhooks, TTL sur les slots hub