feat(whatsapp): validar firma de Meta en el webhook y restaurar el menú del módulo

- POST /webhooks exige X-Hub-Signature-256 (HMAC-SHA256 del cuerpo crudo);
  falla cerrado si app_secret no está configurado
- nueva clave app_secret en whatsapp_system_config, editable desde Configuración Bot
- menú lateral de WhatsApp visible de nuevo (Correo/IMAP sigue en Chat/Bot)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-26 23:02:39 -05:00
co-authored by Claude Opus 5
parent 5e0c0367d9
commit e3ad0a423a
6 changed files with 133 additions and 3 deletions
+22
View File
@@ -0,0 +1,22 @@
<?php
// ponytail: check suelto, corre con `php tests/whatsapp_signature_check.php` sin phpunit ni vendor.
// Verifica el contrato de firma que aplica WhatsappWebhookController::signatureValid().
$secret = 'secreto-de-prueba';
$payload = '{"entry":[{"changes":[{"field":"messages"}]}]}';
$firmar = fn(string $body, string $sec) => 'sha256=' . hash_hmac('sha256', $body, $sec);
$valida = function (string $header, string $body, string $sec): bool {
if ($sec === '' || ! str_starts_with($header, 'sha256=')) return false;
return hash_equals(hash_hmac('sha256', $body, $sec), substr($header, 7));
};
assert($valida($firmar($payload, $secret), $payload, $secret) === true, 'firma correcta debe pasar');
assert($valida($firmar($payload, $secret), $payload . ' ', $secret) === false, 'cuerpo alterado debe fallar');
assert($valida($firmar($payload, 'otro'), $payload, $secret) === false, 'secreto distinto debe fallar');
assert($valida($firmar($payload, $secret), $payload, '') === false, 'sin secreto debe fallar cerrado');
assert($valida('', $payload, $secret) === false, 'sin cabecera debe fallar');
assert($valida(hash_hmac('sha256', $payload, $secret), $payload, $secret) === false, 'sin prefijo sha256= debe fallar');
echo "OK: 6 casos de firma\n";