# Especificación: reCAPTCHA v3 Anti-Spam Protection ## Purpose Define la integración de Google reCAPTCHA v3 para proteger los formularios del sitio (Newsletter y Contact Form) contra spam automatizado, siguiendo Clean Architecture. ## Requirements ### Requirement: Configuración de reCAPTCHA El sistema DEBE permitir configurar reCAPTCHA v3 desde el panel de administración. #### Scenario: Schema JSON para configuración - **WHEN** se crea el schema de configuración - **THEN** DEBE ubicarse en `Schemas/recaptcha-settings.json` - **AND** `component_name` DEBE ser `recaptcha-settings` - **AND** DEBE incluir grupo VISIBILITY con `is_enabled` - **AND** DEBE incluir grupo BEHAVIOR con `site_key`, `secret_key`, `score_threshold` #### Scenario: Campos obligatorios del schema - **GIVEN** el schema `recaptcha-settings.json` - **WHEN** se define la estructura - **THEN** `is_enabled` DEBE ser tipo boolean con default true - **AND** `site_key` DEBE ser tipo text (clave pública) - **AND** `secret_key` DEBE ser tipo text (clave secreta, almacenada encriptada) - **AND** `score_threshold` DEBE ser tipo select con options: 0.3, 0.5, 0.7, 0.9 #### Scenario: Sincronización con BD - **WHEN** se sincroniza el schema - **THEN** ejecutar `wp roi-theme sync-component recaptcha-settings` - **AND** los datos DEBEN ir a `wp_roi_theme_component_settings` - **AND** `component_name` en BD DEBE ser `recaptcha-settings` --- ### Requirement: Contrato de Validación (Domain) El Domain DEBE definir la interfaz de validación de reCAPTCHA. #### Scenario: Ubicación de RecaptchaValidatorInterface - **WHEN** se crea la interfaz - **THEN** DEBE ubicarse en `Shared/Domain/Contracts/RecaptchaValidatorInterface.php` - **AND** namespace DEBE ser `ROITheme\Shared\Domain\Contracts` #### Scenario: Firma del método validate - **WHEN** se define RecaptchaValidatorInterface - **THEN** DEBE tener método `validate(string $token, string $action): RecaptchaResult` - **AND** `$token` es el token generado por reCAPTCHA frontend - **AND** `$action` es el nombre de la acción (newsletter_submit, contact_submit) - **AND** retorna objeto `RecaptchaResult` con score y success #### Scenario: Entidad RecaptchaResult - **WHEN** se define el resultado de validación - **THEN** DEBE existir `Shared/Domain/Entities/RecaptchaResult.php` - **AND** DEBE tener propiedades: `success` (bool), `score` (float), `action` (string), `errorCodes` (array) - **AND** DEBE tener método `isValid(float $threshold): bool` --- ### Requirement: Servicio de Aplicación La capa Application DEBE orquestar la validación de reCAPTCHA. #### Scenario: Ubicación del servicio - **WHEN** se crea el servicio de aplicación - **THEN** DEBE ubicarse en `Shared/Application/Services/RecaptchaValidationService.php` - **AND** namespace DEBE ser `ROITheme\Shared\Application\Services` #### Scenario: Inyección de dependencias - **WHEN** se implementa RecaptchaValidationService - **THEN** DEBE inyectar `RecaptchaValidatorInterface` via constructor - **AND** DEBE inyectar `ComponentSettingsRepositoryInterface` para obtener configuración - **AND** NO DEBE instanciar servicios directamente con `new` #### Scenario: Lógica de validación con threshold - **GIVEN** un token de reCAPTCHA y una acción - **WHEN** se llama a `validateSubmission(string $token, string $action): bool` - **THEN** DEBE obtener `score_threshold` de la configuración en BD - **AND** DEBE llamar a `RecaptchaValidatorInterface::validate()` - **AND** DEBE retornar `true` si `RecaptchaResult::isValid($threshold)` es true - **AND** DEBE retornar `false` si score está por debajo del threshold #### Scenario: Bypass cuando está deshabilitado - **GIVEN** `is_enabled` es false en configuración - **WHEN** se llama a `validateSubmission()` - **THEN** DEBE retornar `true` sin llamar a la API de Google - **AND** NO DEBE generar errores --- ### Requirement: Implementación de Infraestructura La capa Infrastructure DEBE implementar la comunicación con API de Google. #### Scenario: Ubicación del validador - **WHEN** se implementa el validador - **THEN** DEBE ubicarse en `Shared/Infrastructure/Services/GoogleRecaptchaValidator.php` - **AND** namespace DEBE ser `ROITheme\Shared\Infrastructure\Services` - **AND** DEBE implementar `RecaptchaValidatorInterface` #### Scenario: Llamada a API de Google - **WHEN** se valida un token - **THEN** DEBE hacer POST a `https://www.google.com/recaptcha/api/siteverify` - **AND** DEBE enviar `secret` (secret key) y `response` (token) - **AND** DEBE usar `wp_remote_post()` de WordPress - **AND** timeout DEBE ser máximo 5 segundos #### Scenario: Parseo de respuesta exitosa - **GIVEN** API de Google responde exitosamente - **WHEN** se parsea la respuesta - **THEN** DEBE extraer `success` (bool) - **AND** DEBE extraer `score` (float 0.0-1.0) - **AND** DEBE extraer `action` (string) - **AND** DEBE retornar `RecaptchaResult` con estos valores #### Scenario: Manejo de errores de API - **GIVEN** API de Google no responde o responde con error - **WHEN** se procesa la respuesta - **THEN** DEBE retornar `RecaptchaResult` con `success = false` - **AND** DEBE incluir códigos de error en `errorCodes` - **AND** NO DEBE lanzar excepciones no controladas --- ### Requirement: Integración Frontend Los Renderers DEBEN incluir el script de reCAPTCHA y generar tokens. #### Scenario: Script de reCAPTCHA en FooterRenderer - **WHEN** FooterRenderer genera HTML del newsletter - **AND** reCAPTCHA está habilitado - **THEN** DEBE incluir script: `` - **AND** DEBE agregar input hidden `recaptcha_token` al formulario - **AND** DEBE agregar JS para ejecutar `grecaptcha.execute()` al submit #### Scenario: Script de reCAPTCHA en ContactFormRenderer - **WHEN** ContactFormRenderer genera HTML del formulario - **AND** reCAPTCHA está habilitado - **THEN** DEBE incluir script de reCAPTCHA con site_key - **AND** DEBE agregar input hidden `recaptcha_token` - **AND** DEBE agregar JS para ejecutar `grecaptcha.execute()` al submit #### Scenario: JavaScript de ejecución de reCAPTCHA - **WHEN** usuario hace submit del formulario - **THEN** JS DEBE interceptar el submit - **AND** DEBE llamar `grecaptcha.execute(siteKey, {action: 'action_name'})` - **AND** DEBE esperar el token (Promise) - **AND** DEBE insertar token en input hidden - **AND** DEBE continuar con el submit del formulario #### Scenario: No cargar script si está deshabilitado - **GIVEN** reCAPTCHA `is_enabled` es false - **WHEN** se renderiza el formulario - **THEN** NO DEBE incluir script de reCAPTCHA - **AND** NO DEBE agregar input hidden de token - **AND** formulario DEBE funcionar normalmente --- ### Requirement: Integración Backend (AjaxHandlers) Los AjaxHandlers DEBEN validar el token de reCAPTCHA antes de procesar. #### Scenario: Validación en NewsletterAjaxHandler - **WHEN** se procesa suscripción de newsletter - **AND** reCAPTCHA está habilitado - **THEN** DEBE obtener `recaptcha_token` del POST - **AND** DEBE llamar a `RecaptchaValidationService::validateSubmission()` - **AND** si retorna false, DEBE responder con error JSON - **AND** si retorna true, DEBE continuar procesamiento normal #### Scenario: Validación en ContactFormAjaxHandler - **WHEN** se procesa envío de formulario de contacto - **AND** reCAPTCHA está habilitado - **THEN** DEBE obtener `recaptcha_token` del POST - **AND** DEBE llamar a `RecaptchaValidationService::validateSubmission()` - **AND** si retorna false, DEBE responder con error JSON - **AND** si retorna true, DEBE continuar procesamiento normal #### Scenario: Mensaje de error por reCAPTCHA fallido - **GIVEN** validación de reCAPTCHA falla - **WHEN** AjaxHandler responde - **THEN** DEBE retornar JSON con `success: false` - **AND** mensaje DEBE ser genérico: "No se pudo verificar que eres humano. Intenta de nuevo." - **AND** NO DEBE revelar detalles técnicos del score #### Scenario: Inyección de dependencias en AjaxHandlers - **WHEN** se modifican los AjaxHandlers - **THEN** DEBEN inyectar `RecaptchaValidationService` via constructor - **AND** NO DEBE instanciar servicios directamente - **AND** DEBE seguir principio de Inversión de Dependencias --- ### Requirement: Panel de Administración DEBE existir un FormBuilder para configurar reCAPTCHA. #### Scenario: Ubicación del FormBuilder - **WHEN** se crea el FormBuilder - **THEN** DEBE ubicarse en `Admin/RecaptchaSettings/Infrastructure/Ui/RecaptchaSettingsFormBuilder.php` - **AND** namespace DEBE ser `ROITheme\Admin\RecaptchaSettings\Infrastructure\Ui` #### Scenario: Registro en getComponents - **WHEN** se registra el FormBuilder - **THEN** DEBE registrarse en `getComponents()` con ID `recaptcha-settings` - **AND** DEBE aparecer en el menú del admin dashboard #### Scenario: Campos del formulario admin - **WHEN** se renderiza el formulario de configuración - **THEN** DEBE mostrar toggle para `is_enabled` - **AND** DEBE mostrar input text para `site_key` - **AND** DEBE mostrar input password para `secret_key` - **AND** DEBE mostrar select para `score_threshold` con opciones 0.3, 0.5, 0.7, 0.9 - **AND** DEBE seguir Design System: gradiente #0E2337 → #1e3a5f, borde #FF8600 #### Scenario: FieldMapper para mapeo de campos - **WHEN** se crea el componente admin - **THEN** DEBE existir `Admin/RecaptchaSettings/Infrastructure/FieldMapping/RecaptchaSettingsFieldMapper.php` - **AND** DEBE implementar `FieldMapperInterface` - **AND** DEBE registrarse en `FieldMapperRegistry` --- ### Requirement: Seguridad La implementación DEBE seguir mejores prácticas de seguridad. #### Scenario: Almacenamiento de secret key - **WHEN** se guarda el secret key - **THEN** DEBE almacenarse encriptado en BD - **AND** NO DEBE aparecer en código fuente - **AND** NO DEBE exponerse en frontend #### Scenario: Site key en frontend - **GIVEN** site key es público por diseño de Google - **WHEN** se incluye en frontend - **THEN** PUEDE incluirse en atributo de script - **AND** DEBE obtenerse de configuración en BD #### Scenario: Validación de token en backend - **WHEN** se recibe token de reCAPTCHA - **THEN** DEBE sanitizarse con `sanitize_text_field()` - **AND** DEBE validarse que no esté vacío - **AND** DEBE enviarse a API de Google para verificación real #### Scenario: No confiar solo en frontend - **GIVEN** tokens pueden ser fabricados - **WHEN** se valida reCAPTCHA - **THEN** SIEMPRE DEBE verificarse con API de Google en backend - **AND** NUNCA confiar en validación solo de frontend --- ### Requirement: Logging y Monitoreo El sistema DEBE registrar intentos de spam bloqueados. #### Scenario: Log de intentos bloqueados - **WHEN** reCAPTCHA bloquea un intento - **THEN** DEBE registrar en log de WordPress - **AND** DEBE incluir: timestamp, IP, action, score obtenido, threshold configurado - **AND** NO DEBE incluir datos personales del usuario #### Scenario: Log de errores de API - **WHEN** API de Google falla - **THEN** DEBE registrar error en log - **AND** DEBE incluir código de error y mensaje - **AND** DEBE permitir diagnóstico del problema --- ### Requirement: Fallback y Resiliencia El sistema DEBE manejar fallos graciosamente. #### Scenario: Fail-open cuando API no responde - **GIVEN** API de Google no responde (timeout) - **WHEN** se intenta validar - **THEN** DEBE permitir el envío del formulario (fail-open) - **AND** DEBE registrar el evento en log - **AND** NO DEBE bloquear usuarios legítimos por falla de terceros #### Scenario: Degradación cuando reCAPTCHA deshabilitado - **GIVEN** administrador deshabilita reCAPTCHA - **WHEN** se envía formulario - **THEN** DEBE procesarse normalmente - **AND** validaciones existentes (nonce, rate limit) DEBEN seguir activas - **AND** NO DEBE generar errores por ausencia de token --- ## Checklist de Implementación ### Archivos a Crear - [ ] `Schemas/recaptcha-settings.json` - [ ] `Shared/Domain/Contracts/RecaptchaValidatorInterface.php` - [ ] `Shared/Domain/Entities/RecaptchaResult.php` - [ ] `Shared/Application/Services/RecaptchaValidationService.php` - [ ] `Shared/Infrastructure/Services/GoogleRecaptchaValidator.php` - [ ] `Admin/RecaptchaSettings/Infrastructure/Ui/RecaptchaSettingsFormBuilder.php` - [ ] `Admin/RecaptchaSettings/Infrastructure/FieldMapping/RecaptchaSettingsFieldMapper.php` ### Archivos a Modificar - [ ] `Public/Footer/Infrastructure/Ui/FooterRenderer.php` - [ ] `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php` - [ ] `Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php` - [ ] `Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php` - [ ] `functions.php` (registro DI) - [ ] `Admin/Infrastructure/Ui/AdminDashboardRenderer.php` (registrar componente) - [ ] `Admin/Shared/Infrastructure/FieldMapping/FieldMapperRegistry.php` ### Validaciones - [ ] Schema tiene campos de visibilidad - [ ] Domain no tiene dependencias de Infrastructure - [ ] Application solo depende de interfaces de Domain - [ ] Todos los servicios inyectados via constructor - [ ] CSS generado via CSSGeneratorService (si aplica) - [ ] Secret key nunca expuesto en frontend --- ## Última actualización 2025-01-08