Aller au contenu

Sécurité multi-tenant

Titreo E-Pay héberge les credentials de paiement de plusieurs marchands. La sécurité est donc multi-tenant par conception. Le code de crypto vit dans api/src/infrastructure/crypto/.

Les clés API Stripe/Adyen de chaque marchand sont stockées chiffrées en base, jamais en clair.

  • Algorithme : AES-256-GCM (chiffrement authentifié). api/src/infrastructure/crypto/aead.ts.
  • Clé maîtresse : la KEK (variable d’environnement PSP_CONFIG_KEK).
  • Stockage : FirebirdMerchantPspConfigRepo chiffre à l’écriture, déchiffre à la lecture (api/src/infrastructure/db/firebird-merchant-psp-config-repo.ts, migrations 003/005/020).

Les accès cartes cadeaux d’un marchand (nom d’enseigne et clé de l’API Titréo REST) suivent le même régime : même algorithme, même KEK, table merchant_gift_configs (migration 026). Les données associées sont gift|<merchantId>|<provider> : une ligne recopiée sous un autre marchand ne se déchiffre pas. Les accès sont vérifiés auprès de Titréo avant d’être enregistrés, et la clé n’est jamais renvoyée par l’API (api/src/infrastructure/db/firebird-merchant-gift-config-repo.ts).

Les champs sensibles de l’outbox (ex. CVV de carte cadeau, tokens) sont également chiffrés via payload-crypto.ts avant insertion, avec des données associées (AAD) liées à l’identifiant de la ligne.

  • Format : tep_live_<aléatoire>. Génération : api/src/infrastructure/crypto/api-key.ts.
  • Stockage hashé : seul le hash est en base (api_keys, migration 004) — une fuite de la base ne révèle pas les clés.
  • En-tête : Authorization: Bearer tep_live_…. Vérification par le use case authenticate-api-key.ts, appelé depuis le plugin api/src/http/plugins/auth.ts.
  • Scopes : chaque clé porte des permissions (sessions:read, sessions:write, psp:write, webhooks:write, admin). Le plugin require-scope.ts rejette en 403 si le scope manque.

Au-delà des API keys (intégrations serveur), il existe :

  • des tokens de session client (signés, courte durée) pour le navigateur — session-token.ts ;
  • des sessions utilisateur (cookies) pour les consoles admin/marchand — user-session-token.ts, plugin user-session.ts ;
  • du hachage de mots de passe par scrypt — scrypt-password-hasher.ts.

Quand l’API notifie le marchand (session.completed, session.failed…), elle signe la requête :

x-titreo-signature: t=<timestamp>,v1=<hex(HMAC-SHA256(timestamp.payload))>

Le marchand recalcule le HMAC avec son secret partagé et compare (comparaison à temps constant). Les SDK fournissent un vérificateur prêt à l’emploi (verifyMerchantWebhook côté JS, Webhook\Verifier côté PHP). Une tolérance sur le timestamp (~5 min) limite le rejeu.

4. Webhooks PSP entrants : la bonne URL au bon marchand

Section intitulée « 4. Webhooks PSP entrants : la bonne URL au bon marchand »

Un PSP poste sur POST /v1/webhooks/:providerType/:merchantId. L’API résout le secret de webhook du marchand visé puis vérifie la signature avant de parser le corps (verifyWebhook de l’adapter PSP). Pas de signature valide → 400, sans effet de bord.

Le bootstrap navigateur (GET /v1/sessions/:id/bootstrap) renvoie un manifest signé par une clé privée Ed25519 (asymétrique). api/src/infrastructure/crypto/ed25519-signer.ts. La clé publique est embarquée dans le loader, qui refuse de charger des adapters dont le manifest n’est pas correctement signé. Chaque module d’adapter porte aussi une intégrité SRI (sha384-…).

  • url-guard.ts : empêche l’envoi de webhooks vers des IP privées en production (anti-SSRF).
  • Journaux : pino, avec masquage (redact) des champs sensibles. Jamais de PAN, CVV, carte cadeau en clair ni token PSP dans les logs (voir CONVENTIONS.md). Le détail de ce qui est censuré, masqué ou remplacé par une empreinte est sur Traçabilité.
  • config/index.ts est le seul endroit qui lit process.env, validé par Zod ; le reste du code reçoit un objet Config typé par injection.
flowchart TB
  subgraph repos["Au repos (base)"]
    A["Configs PSP et accès cartes cadeaux — AES-256-GCM (KEK)"]
    B["API keys — hash"]
    C["Mots de passe — scrypt"]
  end
  subgraph transit["En transit"]
    D["Webhooks sortants — HMAC-SHA256"]
    E["Webhooks PSP entrants — signature PSP"]
    F["Manifest navigateur — Ed25519 + SRI"]
  end

Source de vérité complémentaire : section « Sécurité multi-tenant » de ECOSYSTEM.md, et doc/site/content/security.mdx.