From 63a13c0ce922274f3c70c20064f6a90c01e420c0 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Wed, 15 Apr 2026 23:02:37 +0200 Subject: [PATCH] fix: setupTotp() clobbering mfa_confirmed_at breaks login MFA check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When MFA was already enabled and the user clicked "Opnieuw instellen" on the TOTP card, setupTotp() unconditionally set mfa_confirmed_at to null. If the user then cancelled the dialog without confirming, the login controller's check `mfa_enabled && mfa_confirmed_at` evaluated to false (true && null), silently skipping the MFA challenge. Fix: only set mfa_method and mfa_confirmed_at when MFA is not yet enabled (first-time setup). For re-setup or adding TOTP as a second method, only rotate the mfa_secret — matching the guard already applied to setupEmail(). Co-Authored-By: Claude Opus 4.6 (1M context) --- api/app/Services/MfaService.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/api/app/Services/MfaService.php b/api/app/Services/MfaService.php index dd61b091..fa9df915 100644 --- a/api/app/Services/MfaService.php +++ b/api/app/Services/MfaService.php @@ -34,16 +34,22 @@ final class MfaService /** * Begin TOTP setup: generate a secret and return QR code data. * MFA is NOT yet active — user must confirm with a valid code. + * If MFA is already enabled (re-setup or adding as second method), + * only the secret is rotated — confirmed_at and preferred method + * are preserved so a cancelled setup doesn't break the login flow. */ public function setupTotp(User $user): array { $secret = $this->google2fa->generateSecretKey(32); - $user->update([ - 'mfa_secret' => encrypt($secret), - 'mfa_method' => MfaMethod::TOTP->value, - 'mfa_confirmed_at' => null, - ]); + $updateData = ['mfa_secret' => encrypt($secret)]; + + if (! $user->mfa_enabled) { + $updateData['mfa_method'] = MfaMethod::TOTP->value; + $updateData['mfa_confirmed_at'] = null; + } + + $user->update($updateData); $qrCodeUrl = $this->google2fa->getQRCodeUrl( company: 'Crewli',