* @category Class * @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License * @link www.splendidbear.org * @since 2026. 04. 26. */ #[AsController] class ApiAuthController extends AbstractController { public function __construct( private readonly EntityManagerInterface $em, private readonly UserPasswordHasherInterface $passwordHasher, private readonly Security $security, ) { } /** * POST /api/auth/login * * Request body (JSON): { "username": "...", "password": "..." } * * Responses: * 200 { "success": true, "requiresTwoFactor": false } * 200 { "success": true, "requiresTwoFactor": true } * 400 { "success": false, "error": "..." } * 401 { "success": false, "error": "..." } */ #[Route('/api/auth/login', name: 'MineSeekerBundle_api_auth_login', methods: ['POST'])] public function login(Request $request): JsonResponse { $data = $request->toArray(); $username = trim($data['username'] ?? ''); $password = $data['password'] ?? ''; if ($username === '' || $password === '') { return $this->json( ['success' => false, 'error' => 'Username and password are required.'], Response::HTTP_BAD_REQUEST ); } /** @var User|null $user */ $user = $this->em->getRepository(User::class)->findOneBy(['username' => $username]); if ($user === null || !$this->passwordHasher->isPasswordValid($user, $password)) { return $this->json( ['success' => false, 'error' => 'Invalid username or password.'], Response::HTTP_UNAUTHORIZED ); } if (!$user->isVerified) { return $this->json( ['success' => false, 'error' => 'Account not yet activated. Check your email.'], Response::HTTP_UNAUTHORIZED ); } // Log the user in via the Symfony security system. // If TOTP is enabled, scheb/2fa will place the session into // IS_AUTHENTICATED_2FA_IN_PROGRESS state, and the client must // complete 2FA by POSTing the code to /2fa_check. $this->security->login($user, 'form_login'); return $this->json([ 'success' => true, 'requiresTwoFactor' => $user->isTotpAuthenticationEnabled(), ]); } }