MFA (post-login, Redis)¶
Contrôleur HTTP du flux d'authentification à deux facteurs (2FA) par code à 6 chiffres envoyé par email pour les actions sensibles post-login : changement d'email, de mot de passe, activation/désactivation du MFA, achat carte cadeau, validation email, etc. Source : mfa.controller.ts.
Le 2FA au login ne passe pas par ce contrôleur ni par Redis — il est géré par le plugin Better Auth twoFactor (sendOtp / verifyOtp côté client). Voir auth.md.
Le code MFA fait 6 chiffres (MFA_CODE_LENGTH), expire selon emailCodeExpiresIn (env config) et est stocké dans Redis sous la clé mfa_verification_<customerId>_<action>_v2. Le rate-limit utilise un backoff exponentiel (30 s × 2^(tryCount-1), capé à 5 tentatives).
Actions MFA supportées via Redis : email-validation, withdraw, bank-account-creation, password-update, email-update, mfa-enabled-update, gift-card-purchase (cf. MFAVerificationAction).
L'action login est retirée : POST /auth/mfa/login-confirm-code n'existe plus.
PATCH /auth/mfa¶
Active ou désactive le MFA pour l'investisseur courant. Déclenche d'abord une vérification MFA (envoi d'un code par email) ; le frontend doit ensuite appeler POST /auth/mfa/confirm-code avec l'action mfa-enabled-update pour finaliser le changement.
Pré-conditions¶
- Session investisseur Better Auth valide (
JwtAuthGuard) - Rôle
CUSTOMER(UserRoleGuard) - La nouvelle valeur
enableddoit différer de l'état courant
Request body¶
Validé via patchMfaEnabledRequestBody (api-communication).
Response¶
200 OK (corps vide — le handler ne retourne rien) — un email contenant un code à 6 chiffres est envoyé via Customer.io. Le client doit ensuite confirmer via /auth/mfa/confirm-code.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
2fa.mfa-enabled.patch-is-same |
400 | enabled vaut déjà la valeur actuelle de customer.mfaEnabled |
POST /auth/mfa/confirm-code¶
Confirme un code MFA pour une action authentifiée (post-login) : changement d'email, de mot de passe, activation/désactivation du MFA, achat de carte cadeau, etc. Selon l'action contenue dans le contexte de vérification, déclenche le handler associé (InvestorAccountService.passwordUpdateConfirmed, MFAPreAuthFlowService.generateVerificationToken, etc.).
Pré-conditions¶
- Session investisseur Better Auth valide (
JwtAuthGuard) - Rôle
CUSTOMER(UserRoleGuard) - Un contexte MFA actif doit exister en Redis pour cette action (déclenché auparavant par
PATCH /auth/mfaouPOST /auth/mfa/trigger-mfa)
Request body¶
Validé via mfaVerificationRequestPayload (api-communication).
Response¶
Le retour dépend de l'action confirmée :
gift-card-purchase→{ "token": "<uuid>" }(token one-shot consommable par le flux d'achat carte cadeau, expire selonbusinessRules.mfa.verificationTokenExpirationInMinutes)mfa-enabled-update,email-update,password-update,email-validation→ effet de bord côté serveur, pas de body de retour structuréwithdraw,bank-account-creation→ no-op (TODO migration de l'ancien 2FA)
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
2fa.verification-code.no-verification-context |
400 | Aucun code MFA actif pour cette action |
2fa.verification-code.wrong-code |
400 | Code incorrect |
2fa.verification-code.code-expired |
400 | Code expiré |
2fa.login-legacy-removed |
400 | Action login retirée — utiliser Better Auth twoFactor |
GET /auth/mfa/:email/action/:action/next-retry-date¶
Endpoint public. Retourne la prochaine date à laquelle le client peut redemander un code MFA pour cet email + action. Utilisé par le frontend pour afficher un compte à rebours (ex : "Renvoyer un code dans 30 s").
Path params¶
email— adresse email du compteaction— une des valeurs deMFAVerificationAction(horslogin)
Validés via getMfaNextRetryDatePathParams (api-communication).
Response¶
nextRetryDate est undefined si aucun compteur de tentatives n'existe (jamais déclenché ou compteur expiré).
POST /auth/mfa/resend-code¶
Endpoint public. Renvoie le code MFA courant par email (ou en regénère un si le précédent a expiré). Utilisé pour les flux post-login publics (ex. validation email pendant l'onboarding).
Request body¶
Validé via postMfaResendCodeRequestBody (api-communication).
Comportement¶
- Si le code en cache est encore valide : renvoie le même code par email
- Si expiré : génère un nouveau code, le sauve en cache et l'envoie
- Action
login: rejetée (2fa.login-legacy-removed)
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
2fa.no-customer-for-email |
404 | Aucun compte avec cet email |
2fa.no-code-to-resend |
400 | Aucun contexte MFA actif pour cet email + action (rien à renvoyer) |
2fa.too-many-attempts |
403 | Rate-limit atteint avant nextRetryDate |
2fa.login-legacy-removed |
400 | Action login retirée |
POST /auth/mfa/trigger-mfa¶
Déclenche manuellement l'envoi d'un code MFA pour l'investisseur courant. Utilisé par le client pour amorcer un flux MFA post-login (ex : avant un achat de carte cadeau qui requiert un token MFA).
Pré-conditions¶
- Session investisseur Better Auth valide (
JwtAuthGuard) - Rôle
CUSTOMER(UserRoleGuard)
Request body¶
Discriminé sur action (mfaContext). Exemples :
{
"action": "password-update",
"newPasswordArgon2Hash": "<argon2>",
"currentSessionId": "<optional>"
}
Le mot de passe est stocké uniquement dans better_auth_account.password (Argon2) — le contexte password-update ne transporte que newPasswordArgon2Hash.
Response¶
201 Created (corps vide — le handler ne retourne rien) — un email contenant le code est envoyé via Customer.io. Si un code valide existe déjà avec le même contexte, il est ré-envoyé tel quel sans regénération.
Liens¶
- Service MFA :
mfa.service.ts - Rate-limit :
mfa-rate-limit.service.ts - Pre-auth flow (token one-shot) :
mfa.pre-auth-flow.service.ts - Contexte MFA :
mfaVerificationContext.ts - DTOs partagés :
auth/mfa.ts - 2FA login (Better Auth) :
auth.md