diff --git a/lib/Controllers/EnrollmentController.php b/lib/Controllers/EnrollmentController.php index 7250bf7..42b02fc 100644 --- a/lib/Controllers/EnrollmentController.php +++ b/lib/Controllers/EnrollmentController.php @@ -128,7 +128,7 @@ class EnrollmentController extends ControllerAbstract /** * Remove TOTP enrollment (disable 2FA) */ - #[AuthenticatedRoute('/totp/enroll', name: 'totp.enroll.remove', methods: ['DELETE'])] + #[AuthenticatedRoute('/totp/enroll/remove', name: 'totp.enroll.remove', methods: ['POST'])] public function removeEnrollment(string $code): JsonResponse { $userId = $this->userIdentity->identifier(); @@ -139,6 +139,13 @@ class EnrollmentController extends ControllerAbstract ); } + if (empty($code)) { + return new JsonResponse( + ['error' => 'Verification code is required', 'error_code' => 'invalid_request'], + JsonResponse::HTTP_BAD_REQUEST + ); + } + // Verify current code before removing $verifyResult = $this->enrollmentService->verifyCode($userId, $code); if (!$verifyResult['success']) { diff --git a/lib/Services/EnrollmentService.php b/lib/Services/EnrollmentService.php index 284a6e1..1601b11 100644 --- a/lib/Services/EnrollmentService.php +++ b/lib/Services/EnrollmentService.php @@ -128,7 +128,7 @@ class EnrollmentService } // Verify the code - if (!$this->verifyCode($enrollment['secret'], $code)) { + if (!$this->verifyTotpCode($enrollment['secret'], $code)) { return [ 'success' => false, 'error' => 'Invalid verification code', diff --git a/lib/Stores/EnrollmentStore.php b/lib/Stores/EnrollmentStore.php index 90ec43a..7aed787 100644 --- a/lib/Stores/EnrollmentStore.php +++ b/lib/Stores/EnrollmentStore.php @@ -116,9 +116,9 @@ class EnrollmentStore } /** - * Get unused recovery codes for a user - * - * @return array Unused recovery codes + * Get unused recovery codes for a user, keyed by their original index + * + * @return array Unused recovery codes */ public function getRecoveryCodes(string $tenantId, string $userId): array { @@ -130,14 +130,15 @@ class EnrollmentStore $codes = $enrollment['recovery_codes'] ?? []; $usedIndices = $enrollment['used_recovery_codes'] ?? []; - // Filter out used codes + // Filter out used codes, preserving original indices so callers + // can mark the correct code as used $result = []; foreach ($codes as $index => $code) { if (!in_array($index, $usedIndices, true)) { - $result[] = $code; + $result[$index] = $code; } } - + return $result; } diff --git a/src/views/UserSettingsSecurityPanel.vue b/src/views/UserSettingsSecurityPanel.vue index fd67d44..896bf2e 100644 --- a/src/views/UserSettingsSecurityPanel.vue +++ b/src/views/UserSettingsSecurityPanel.vue @@ -140,9 +140,11 @@ async function disableTotp() { disableError.value = '' try { - const response = await fetch(`${apiBase}/totp/enroll?code=${encodeURIComponent(disableCode.value)}`, { - method: 'DELETE', + const response = await fetch(`${apiBase}/totp/enroll/remove`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, credentials: 'include', + body: JSON.stringify({ code: disableCode.value }), }) const data = await response.json()