/home/suroeste/public_html/payments.transportessuroeste.com/src/Middleware
Edit: /home/suroeste/public_html/payments.transportessuroeste.com/src/Middleware/AuthMiddleware.php (5521B)
db = Database::getInstance();
}
/**
* Autenticar petición
*/
public function authenticate(): array
{
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (empty($apiKey)) {
LogService::security('auth_failure', 'API Key no proporcionada');
throw new AuthenticationException('API Key requerida');
}
// Validar timestamp si se envía (previene replay attacks)
if (!empty($timestamp) && !$this->validateTimestamp($timestamp)) {
LogService::security('auth_failure', 'Timestamp inválido', [
'api_key' => substr($apiKey, 0, 10) . '...'
]);
throw new AuthenticationException('Timestamp inválido o expirado');
}
// Buscar cliente - soporta key directa o hasheada
$apiKeyHash = hash('sha256', $apiKey);
$client = $this->db->fetchOne(
"SELECT * FROM api_clients WHERE (api_key = :key OR api_key = :key_hash) AND is_active = 1",
['key' => $apiKey, 'key_hash' => $apiKeyHash]
);
if (!$client) {
LogService::security('auth_failure', 'API Key inválida', [
'api_key' => substr($apiKey, 0, 10) . '...'
]);
throw new AuthenticationException('Credenciales inválidas');
}
// Validar IP si hay whitelist
if (!empty($client['allowed_ips'])) {
$allowedIps = json_decode($client['allowed_ips'], true) ?? [];
$clientIp = LogService::getClientIp();
// Siempre permitir localhost
$localhostIps = ['127.0.0.1', '::1', 'localhost'];
$allAllowedIps = array_merge($allowedIps, $localhostIps);
if (!in_array($clientIp, $allAllowedIps)) {
LogService::security('auth_failure', 'IP no autorizada', [
'client' => $client['client_name'],
'ip' => $clientIp
]);
throw new SecurityException('IP no autorizada: ' . $clientIp);
}
}
// Validar firma HMAC si se envía
if (!empty($signature) && !empty($timestamp)) {
if (!$this->validateSignature($client, $signature, $timestamp)) {
LogService::security('auth_failure', 'Firma inválida', [
'client' => $client['client_name']
]);
throw new AuthenticationException('Firma inválida');
}
}
// Actualizar último acceso
try {
$this->db->update(
'api_clients',
['last_access_at' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $client['id']]
);
} catch (\Exception $e) {
// No fallar por esto
}
LogService::info('auth', 'Cliente autenticado', ['client' => $client['client_name']]);
return [
'id' => $client['id'],
'uuid' => $client['client_uuid'],
'name' => $client['client_name'],
'environment' => $client['environment'],
'rate_limit' => $client['rate_limit'],
'webhook_url' => $client['webhook_url'],
'webhook_secret' => $client['webhook_secret']
];
}
/**
* Validar timestamp (máximo 5 minutos de diferencia)
*/
private function validateTimestamp(string $timestamp): bool
{
$ts = (int) $timestamp;
$now = time();
$maxDiff = 300; // 5 minutos
return abs($now - $ts) <= $maxDiff;
}
/**
* Validar firma HMAC
*/
private function validateSignature(array $client, string $signature, string $timestamp): bool
{
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$body = file_get_contents('php://input');
$dataToSign = "{$method}\n{$uri}\n{$timestamp}\n{$body}";
$secret = $client['webhook_secret'] ?? $client['api_secret_hash'] ?? '';
$expectedSignature = hash_hmac('sha512', $dataToSign, $secret);
return hash_equals($expectedSignature, $signature);
}
/**
* Generar nuevas credenciales para un cliente
*/
public static function generateCredentials(string $prefix = 'ts'): array
{
$apiKey = $prefix . '_' . bin2hex(random_bytes(24));
$apiSecret = bin2hex(random_bytes(32));
$webhookSecret = bin2hex(random_bytes(32));
return [
'api_key' => $apiKey,
'api_key_hash' => hash('sha256', $apiKey),
'api_secret' => $apiSecret,
'api_secret_hash' => password_hash($apiSecret, defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT),
'webhook_secret' => $webhookSecret
];
}
}