userIdentity->identifier(); if (!$userId) { return new JsonResponse( ['error' => 'User not authenticated', 'error_code' => 'unauthorized'], JsonResponse::HTTP_UNAUTHORIZED ); } $isEnrolled = $this->enrollmentService->isEnrolled($userId); return new JsonResponse([ 'enrolled' => $isEnrolled, 'provider' => 'totp', 'label' => 'TOTP Code', ]); } /** * Begin TOTP enrollment - generates secret and QR code */ #[AuthenticatedRoute('/totp/enroll', name: 'totp.enroll.begin', methods: ['POST'])] public function beginEnrollment(string $accountName = ''): JsonResponse { $userId = $this->userIdentity->identifier(); if (!$userId) { return new JsonResponse( ['error' => 'User not authenticated', 'error_code' => 'unauthorized'], JsonResponse::HTTP_UNAUTHORIZED ); } $config = []; if (!empty($accountName)) { $config['account_name'] = $accountName; } $result = $this->enrollmentService->beginEnrollment($userId, $config); if (!$result['success']) { return new JsonResponse( ['error' => $result['error'], 'error_code' => $result['error_code']], JsonResponse::HTTP_BAD_REQUEST ); } $enrollment = $result['enrollment'] ?? []; return new JsonResponse([ 'status' => 'pending', 'secret' => $enrollment['secret'] ?? '', 'provisioning_uri' => $enrollment['provisioning_uri'] ?? '', 'qr_code' => $enrollment['qr_code'] ?? '', 'recovery_codes' => $enrollment['recovery_codes'] ?? [], ]); } /** * Complete TOTP enrollment - verify the initial code */ #[AuthenticatedRoute('/totp/enroll/verify', name: 'totp.enroll.verify', methods: ['POST'])] public function completeEnrollment(string $code): JsonResponse { $userId = $this->userIdentity->identifier(); if (!$userId) { return new JsonResponse( ['error' => 'User not authenticated', 'error_code' => 'unauthorized'], JsonResponse::HTTP_UNAUTHORIZED ); } if (empty($code)) { return new JsonResponse( ['error' => 'Verification code is required', 'error_code' => 'invalid_request'], JsonResponse::HTTP_BAD_REQUEST ); } $result = $this->enrollmentService->completeEnrollment($userId, $code); if (!$result['success']) { return new JsonResponse( ['error' => $result['error'], 'error_code' => $result['error_code']], JsonResponse::HTTP_BAD_REQUEST ); } return new JsonResponse([ 'status' => 'enrolled', 'message' => 'TOTP successfully enabled for your account', ]); } /** * Remove TOTP enrollment (disable 2FA) */ #[AuthenticatedRoute('/totp/enroll', name: 'totp.enroll.remove', methods: ['DELETE'])] public function removeEnrollment(string $code): JsonResponse { $userId = $this->userIdentity->identifier(); if (!$userId) { return new JsonResponse( ['error' => 'User not authenticated', 'error_code' => 'unauthorized'], JsonResponse::HTTP_UNAUTHORIZED ); } // Verify current code before removing $verifyResult = $this->enrollmentService->verifyCode($userId, $code); if (!$verifyResult['success']) { return new JsonResponse( ['error' => 'Invalid verification code', 'error_code' => 'invalid_code'], JsonResponse::HTTP_BAD_REQUEST ); } $result = $this->enrollmentService->removeEnrollment($userId); if (!$result['success']) { return new JsonResponse( ['error' => $result['error'], 'error_code' => $result['error_code']], JsonResponse::HTTP_BAD_REQUEST ); } return new JsonResponse([ 'status' => 'removed', 'message' => 'TOTP has been disabled for your account', ]); } /** * Admin endpoint: Get TOTP enrollment status for any user */ #[AuthenticatedRoute('/admin/status/{uid}', name: 'totp.admin.status', methods: ['GET'])] public function adminStatus(string $uid): JsonResponse { // TODO: Add permission check for admin operations $isEnrolled = $this->enrollmentService->isEnrolled($uid); return new JsonResponse([ 'enrolled' => $isEnrolled, 'provider' => 'totp', ]); } /** * Admin endpoint: Remove TOTP enrollment for any user (no code verification required) */ #[AuthenticatedRoute('/admin/enrollment/{uid}', name: 'totp.admin.remove', methods: ['DELETE'])] public function adminRemoveEnrollment(string $uid): JsonResponse { // TODO: Add permission check for admin operations $result = $this->enrollmentService->removeEnrollment($uid); if (!$result['success']) { return new JsonResponse( ['error' => $result['error'], 'error_code' => $result['error_code']], JsonResponse::HTTP_BAD_REQUEST ); } return new JsonResponse([ 'status' => 'removed', 'message' => 'TOTP enrollment removed successfully', ]); } }