feat(api): implement recaptcha v3 anti-spam protection
- Add RecaptchaValidatorInterface and RecaptchaResult entity in Domain - Create RecaptchaValidationService in Application layer - Implement GoogleRecaptchaValidator for API integration - Add recaptcha-settings schema and admin FormBuilder - Integrate reCAPTCHA validation in NewsletterAjaxHandler - Integrate reCAPTCHA validation in ContactFormAjaxHandler - Update FooterRenderer and ContactFormRenderer with reCAPTCHA scripts - Configure DIContainer with RecaptchaValidationService injection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
namespace ROITheme\Public\Footer\Infrastructure\Api\WordPress;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
|
||||
use ROITheme\Shared\Application\Services\RecaptchaValidationService;
|
||||
|
||||
/**
|
||||
* NewsletterAjaxHandler - Procesa suscripciones al newsletter
|
||||
@@ -12,6 +13,7 @@ use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
|
||||
*
|
||||
* SEGURIDAD:
|
||||
* - Verifica nonce
|
||||
* - reCAPTCHA v3 para proteccion anti-spam
|
||||
* - Webhook URL nunca se expone al cliente
|
||||
* - Rate limiting basico
|
||||
* - Sanitizacion de inputs
|
||||
@@ -24,7 +26,8 @@ final class NewsletterAjaxHandler
|
||||
private const COMPONENT_NAME = 'footer';
|
||||
|
||||
public function __construct(
|
||||
private ComponentSettingsRepositoryInterface $settingsRepository
|
||||
private ComponentSettingsRepositoryInterface $settingsRepository,
|
||||
private ?RecaptchaValidationService $recaptchaService = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -50,7 +53,15 @@ final class NewsletterAjaxHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Rate limiting (1 suscripcion por IP cada 60 segundos)
|
||||
// 2. Validar reCAPTCHA (si esta habilitado)
|
||||
if (!$this->validateRecaptcha()) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Verificacion de seguridad fallida. Por favor intenta de nuevo.', 'roi-theme')
|
||||
], 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Rate limiting (1 suscripcion por IP cada 60 segundos)
|
||||
if (!$this->checkRateLimit()) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Por favor espera un momento antes de intentar de nuevo.', 'roi-theme')
|
||||
@@ -58,7 +69,7 @@ final class NewsletterAjaxHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Validar y sanitizar campos
|
||||
// 4. Validar y sanitizar campos
|
||||
$email = sanitize_email($_POST['email'] ?? '');
|
||||
$name = sanitize_text_field($_POST['name'] ?? '');
|
||||
$whatsapp = sanitize_text_field($_POST['whatsapp'] ?? '');
|
||||
@@ -70,7 +81,7 @@ final class NewsletterAjaxHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Obtener configuracion del componente
|
||||
// 5. Obtener configuracion del componente
|
||||
$settings = $this->settingsRepository->getComponentSettings(self::COMPONENT_NAME);
|
||||
|
||||
if (empty($settings)) {
|
||||
@@ -94,7 +105,7 @@ final class NewsletterAjaxHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Preparar payload
|
||||
// 6. Preparar payload
|
||||
$payload = [
|
||||
'email' => $email,
|
||||
'name' => $name,
|
||||
@@ -111,7 +122,7 @@ final class NewsletterAjaxHandler
|
||||
// Debug: Log payload enviado
|
||||
error_log('ROI Theme Newsletter: Enviando a webhook - ' . wp_json_encode($payload));
|
||||
|
||||
// 6. Enviar a webhook
|
||||
// 7. Enviar a webhook
|
||||
$result = $this->sendToWebhook($webhookUrl, $payload);
|
||||
|
||||
if ($result['success']) {
|
||||
@@ -200,4 +211,31 @@ final class NewsletterAjaxHandler
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar reCAPTCHA token
|
||||
*
|
||||
* @return bool True si pasa la validacion o si reCAPTCHA no esta habilitado
|
||||
*/
|
||||
private function validateRecaptcha(): bool
|
||||
{
|
||||
// Si el servicio no esta inyectado, permitir
|
||||
if ($this->recaptchaService === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si reCAPTCHA no esta habilitado, permitir
|
||||
if (!$this->recaptchaService->isRecaptchaEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obtener token del POST
|
||||
$token = sanitize_text_field($_POST['recaptcha_token'] ?? '');
|
||||
|
||||
// Obtener accion configurada para newsletter
|
||||
$action = $this->recaptchaService->getNewsletterAction();
|
||||
|
||||
// Validar con el servicio
|
||||
return $this->recaptchaService->validateSubmission($token, $action);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user