Implement ALTCHA captcha service with challenge generation and solution verification

- Added AltchaService class for managing ALTCHA captcha challenges and solutions.
- Created CaptchaController to handle API requests for generating challenges.
- Introduced CaptchaGuard for validating captcha solutions on public endpoints.
- Developed unit tests for AltchaService to ensure challenge creation and solution verification functionality.
- Implemented integration tests for the Captcha API endpoint and captcha bypass behavior when disabled.
- Added documentation for the Captcha API in the corresponding markdown file.
This commit is contained in:
hamed
2026-07-10 10:31:59 +03:30
parent 11efed4100
commit 10b0743d9a
43 changed files with 5586 additions and 1554 deletions
+10
View File
@@ -11,6 +11,7 @@ use App\Auth\Service\TokenService;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Secretary\Repository\DoctorSecretaryRepository;
use App\Shared\Captcha\CaptchaGuard;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use Doctrine\ORM\EntityManagerInterface;
@@ -40,6 +41,7 @@ class AuthController extends BaseController
private readonly UserActiveContextRepository $contextRepo,
private readonly UserPasswordHasherInterface $hasher,
private readonly EntityManagerInterface $em,
private readonly CaptchaGuard $captcha,
) {}
/**
@@ -140,6 +142,8 @@ class AuthController extends BaseController
return $this->error(ErrorCodes::ERR_RATE_LIMIT_001, ErrorCodes::message(ErrorCodes::ERR_RATE_LIMIT_001), 429);
}
$this->captcha->assertValid($request);
$data = json_decode($request->getContent(), true) ?? [];
$mobile = trim($data['mobile'] ?? '');
$domain = isset($data['domain']) ? substr(trim((string) $data['domain']), 0, 253) : null;
@@ -275,6 +279,8 @@ class AuthController extends BaseController
#[Route('/api/v1/user/register', methods: ['POST'])]
public function register(Request $request): JsonResponse
{
$this->captcha->assertValid($request);
$data = json_decode($request->getContent(), true) ?? [];
$grant = trim($data['grant'] ?? '');
$realName = trim($data['real_name'] ?? '');
@@ -375,6 +381,8 @@ class AuthController extends BaseController
return $resp;
}
$this->captcha->assertValid($request);
$data = json_decode($request->getContent(), true) ?? [];
$grant = trim($data['grant'] ?? '');
@@ -399,6 +407,8 @@ class AuthController extends BaseController
return $resp;
}
$this->captcha->assertValid($request);
$data = json_decode($request->getContent(), true) ?? [];
$grant = trim($data['grant'] ?? '');
$newPassword = trim($data['new_password'] ?? '');
@@ -10,6 +10,7 @@ use App\Clinic\Entity\Clinic;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Repository\DoctorRepository;
use App\Shared\Captcha\CaptchaGuard;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Sms\Service\SmsService;
@@ -35,11 +36,14 @@ class PreRegistrationController extends BaseController
private readonly SmsService $sms,
private readonly LoggerInterface $logger,
private readonly string $appUrl,
private readonly CaptchaGuard $captcha,
) {}
#[Route('/api/v1/pre-registration', methods: ['POST'])]
public function submit(Request $request): JsonResponse
{
$this->captcha->assertValid($request);
$data = json_decode($request->getContent(), true) ?? [];
$type = trim($data['type'] ?? '');
$name = trim($data['name'] ?? '');