fix: prevent MFA lockout and move enrollment to modal dialog
Three fixes for MFA enrollment and sign-in:
- Defer TOTP registration with Logto until after 6-digit code verification.
Previously setupTotp() immediately registered the secret, so abandoning
enrollment mid-way left MFA active without a working authenticator.
- Move entire MFA enrollment flow (QR code, verify, backup codes) into a
Modal dialog instead of replacing the Card content inline.
- Fix sign-in MFA flow: submitMfa() no longer calls identifyUser() after
TOTP verify — user is already identified, and passing the MFA
verificationId to identification returned 422 ("method not activated").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,7 @@ public class AccountController {
|
||||
@PostMapping("/mfa/totp/verify")
|
||||
public Map<String, Boolean> verifyTotp(@AuthenticationPrincipal Jwt jwt,
|
||||
@RequestBody TotpVerifyRequest request) {
|
||||
boolean ok = accountService.verifyTotpCode(request.secret(), request.code());
|
||||
boolean ok = accountService.verifyAndEnableTotp(jwt.getSubject(), request.secret(), request.code());
|
||||
return Map.of("verified", ok);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,11 +108,22 @@ public class AccountService {
|
||||
new SecureRandom().nextBytes(secretBytes);
|
||||
String secret = base32Encode(secretBytes);
|
||||
|
||||
var result = logtoClient.createTotpVerification(userId, secret);
|
||||
String qrCode = result.containsKey("secretQrCode")
|
||||
? String.valueOf(result.get("secretQrCode"))
|
||||
: String.valueOf(result.getOrDefault("qrCode", ""));
|
||||
return new MfaSetupData(secret, qrCode);
|
||||
// Build otpauth URI locally — do NOT register with Logto yet.
|
||||
// The secret is only registered after the user verifies the 6-digit code.
|
||||
var user = logtoClient.getUser(userId);
|
||||
String email = user != null ? String.valueOf(user.getOrDefault("primaryEmail", "")) : "";
|
||||
String label = email.isBlank() ? userId : email;
|
||||
String otpauthUri = String.format(
|
||||
"otpauth://totp/Cameleer:%s?secret=%s&issuer=Cameleer&algorithm=SHA1&digits=6&period=30",
|
||||
label, secret);
|
||||
|
||||
return new MfaSetupData(secret, otpauthUri);
|
||||
}
|
||||
|
||||
public boolean verifyAndEnableTotp(String userId, String secret, String code) {
|
||||
if (!verifyTotpCode(secret, code)) return false;
|
||||
logtoClient.createTotpVerification(userId, secret);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean verifyTotpCode(String secret, String code) {
|
||||
|
||||
Reference in New Issue
Block a user