Skip to content

Auth/OPAQUE — lier l'identifiant à users.id (UUID), plus à l'email #135

Description

@aliceout

Contexte

L'identifiant OPAQUE (userIdentifier) est actuellement l'email. Une enveloppe OPAQUE est liée cryptographiquement à l'identifiant fixé à l'inscription et ne peut pas être re-générée côté serveur (il faudrait le mot de passe en clair). Comme l'email est mutable, on a dû :

  • stocker l'identifiant d'inscription dans opaque_records.user_identifier (colonne nullable, migration 0020 + backfill 0021),
  • le rejouer au login (auth-login.ts : record?.opaqueIdentifier ?? userIdentifier),
  • l'épingler au change-email (auth-account.ts : COALESCE(existing, oldEmail)).

L'invariant actuel est cohérent (« user_identifier = l'email sous lequel l'enveloppe courante a été créée » ; tous les flux d'écriture mettent à jour enveloppe + identifiant ensemble, le change-email épingle parce qu'il bouge l'email sans re-créer d'enveloppe). Mais le design sent la rustine : l'identité crypto est une donnée mutable (l'email), d'où la colonne nullable + les fallbacks ?? email / COALESCE.

Le client ne passe jamais d'identifiant OPAQUE (vérifié : client.startRegistration/finishRegistration/startLogin/finishLogin ne reçoivent que le password) — l'identifiant est choisi uniquement côté serveur. On peut donc changer ce que le serveur inscrit sans toucher au client.

Proposition

Lier l'enveloppe à users.id (UUID immuable) au lieu de l'email. L'email n'est alors plus jamais l'identité crypto ; le change-email redevient un simple UPDATE (+ notif + révocation) qui ne touche plus opaque_records.

Les 4 flux d'écriture d'enveloppe doivent basculer ensemble (sinon risque de lockout) :

  • auth-register-v2.ts (createRegistrationResponse + insert opaque_records)
  • auth-change-password.ts (:116, :163)
  • auth-recovery.ts (:355, :442)
  • auth-reset.ts (:186, :270)

Le login lit déjà opaque_records.user_identifier → marche pour tout le monde.

Option A — forward-only (sûre, recommandée pour commencer)

  • Inscription + les 3 re-registrations utilisent users.id.
  • Comptes existants inchangés (leur enveloppe + identifiant email restent ; ils ne peuvent pas être re-keyés sans le mot de passe → legacy permanent, le fallback reste pour eux).
  • Zéro risque pour l'existant.

Option B — nettoyage complet (après A)

  • Confirmer 0 ligne user_identifier NULL en prod (la 0021 a backfillé, l'inscription remplit toujours).
  • Migration NOT NULL sur user_identifier.
  • Supprimer le fallback ?? email (login) et le COALESCE (change-email).
  • change-email ne touche plus opaque_records du tout.

Incidence sécurité

Aucune incidence négative. Neutre sur le cœur OPAQUE (l'identifiant est un input public, pas un secret ; la seule exigence est la cohérence écriture↔login). Le mot de passe n'est toujours pas transmis, l'enveloppe toujours indéchiffrable côté serveur, la dérivation de la clé qui déwrappe le KEK est indépendante de l'identifiant.

Petits gains :

  • Privacy : la crypto est liée à un UUID opaque, plus à une PII (l'email).
  • Liaison d'identité : un UUID est unique pour toujours (un email est réattribuable → deux enveloppes historiquement liées au même identifiant).
  • Robustesse / dispo : un identifiant immuable rend l'invariant de cohérence impossible à casser par dérive → réduit le risque de re-créer un lockout type 1.5.

Seul risque = disponibilité (lockout), et il échoue fermé (jamais d'accès non autorisé) : si l'identifiant à l'écriture diverge de celui rejoué au login (bug), la personne ne peut plus se connecter, point. D'où l'exigence : basculer les 4 flux de façon cohérente + tests.

Le design actuel est correct et sûr. Ce n'est pas une faille à réparer — c'est de la dette technique / propreté. Ne rien faire est acceptable.

Critères d'acceptation

  • Inscription + change-password + recovery + reset lient l'enveloppe à users.id.
  • Tests d'intégration : inscription → login → change-password → change-email → login (compte « identifiant-UUID »), tous verts.
  • (Option B) Vérif 0 NULL en prod, migration NOT NULL, suppression des fallbacks, change-email sans accès opaque_records.
  • Auth-Spec.md documente la règle : identifiant OPAQUE = users.id, jamais l'email (+ note legacy email pour les comptes pré-bascule).
  • Comptes legacy (email-identifiant) continuent de se connecter (fallback conservé tant qu'il en reste).

Refs

  • Finding 1.5 (docs/Audit-2026-06.md) — corrigé et prouvé par tests, c'est ce qui a déclenché la discussion.
  • Discussion : pourquoi Proton fige l'email (l'adresse y est le produit + l'identité PGP + le handle de routage fédéré) alors que pour Nodea l'email n'est qu'une clé de login → choix inverse justifié.

Metadata

Metadata

Assignees

No one assigned

    Labels

    authLogin, sessions, MFA, password recovery, OPAQUEcryptoE2EE, key derivation, HKDF, integrity, KEK rotationtech debt

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions