- renombrar openspec/ a _openspec/ (carpeta auxiliar) - mover specs de features a changes/ - crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura - migrar _planificacion/ con design-system y roi-theme-template - agregar especificacion recaptcha anti-spam (proposal, tasks, spec) - corregir rutas y referencias en todas las specs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
326 lines
13 KiB
Markdown
326 lines
13 KiB
Markdown
# 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: `<script src="https://www.google.com/recaptcha/api.js?render={site_key}"></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
|