feat: bot WhatsApp Business API — Palmas360 inicial
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Middleware de autenticación JWT HS256.
|
||||
*
|
||||
* Valida el header: Authorization: Bearer <token>
|
||||
* El secreto se toma de la variable de entorno JWT_SECRET.
|
||||
*/
|
||||
class Auth
|
||||
{
|
||||
/**
|
||||
* Verifica el Bearer token. Termina con 401 si es inválido.
|
||||
* Devuelve el payload decodificado si es válido.
|
||||
*/
|
||||
public static function requireBearer(): array
|
||||
{
|
||||
$header = $_SERVER['HTTP_AUTHORIZATION']
|
||||
?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
|
||||
?? '';
|
||||
|
||||
if (!str_starts_with($header, 'Bearer ')) {
|
||||
self::abort(401, 'Token de autorización requerido.');
|
||||
}
|
||||
|
||||
$token = substr($header, 7);
|
||||
return self::verifyJwt($token);
|
||||
}
|
||||
|
||||
// ─── JWT HS256 ────────────────────────────────────────────────────────────
|
||||
|
||||
private static function verifyJwt(string $token): array
|
||||
{
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
self::abort(401, 'Token malformado.');
|
||||
}
|
||||
|
||||
[$b64Header, $b64Payload, $b64Sig] = $parts;
|
||||
|
||||
// Verificar firma
|
||||
$secret = env('JWT_SECRET', '');
|
||||
if ($secret === '') {
|
||||
self::abort(500, 'JWT_SECRET no configurado.');
|
||||
}
|
||||
|
||||
$data = "$b64Header.$b64Payload";
|
||||
$expected = self::base64UrlEncode(
|
||||
hash_hmac('sha256', $data, $secret, true)
|
||||
);
|
||||
|
||||
// Comparación segura contra timing attacks
|
||||
if (!hash_equals($expected, $b64Sig)) {
|
||||
self::abort(401, 'Firma JWT inválida.');
|
||||
}
|
||||
|
||||
// Decodificar payload
|
||||
$payload = json_decode(self::base64UrlDecode($b64Payload), true);
|
||||
if (!is_array($payload)) {
|
||||
self::abort(401, 'Payload JWT inválido.');
|
||||
}
|
||||
|
||||
// Validar expiración si existe
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
self::abort(401, 'Token expirado.');
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private static function base64UrlEncode(string $data): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private static function base64UrlDecode(string $data): string
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
|
||||
private static function abort(int $code, string $message): never
|
||||
{
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['error' => $message], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user