49 lines
1.4 KiB
PHP
49 lines
1.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class VerifyWhmcsRequest
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
if (! config('hosting.whmcs.enabled', false)) {
|
|
abort(503, 'WHMCS integration is disabled.');
|
|
}
|
|
|
|
$secret = config('hosting.whmcs.api_secret');
|
|
if (empty($secret)) {
|
|
abort(500, 'WHMCS API secret is not configured.');
|
|
}
|
|
|
|
$allowedIps = config('hosting.whmcs.allowed_ips', []);
|
|
if ($allowedIps !== [] && ! in_array($request->ip(), $allowedIps, true)) {
|
|
abort(403, 'IP not allowed.');
|
|
}
|
|
|
|
$timestamp = (int) $request->header('X-Whmcs-Timestamp', 0);
|
|
$signature = (string) $request->header('X-Whmcs-Signature', '');
|
|
$window = (int) config('hosting.whmcs.replay_window_seconds', 300);
|
|
|
|
if ($timestamp === 0 || $signature === '') {
|
|
abort(401, 'Missing WHMCS signature headers.');
|
|
}
|
|
|
|
if (abs(time() - $timestamp) > $window) {
|
|
abort(401, 'Request timestamp expired.');
|
|
}
|
|
|
|
$payload = $timestamp.'.'.$request->getContent();
|
|
$expected = hash_hmac('sha256', $payload, $secret);
|
|
|
|
if (! hash_equals($expected, $signature)) {
|
|
abort(401, 'Invalid WHMCS signature.');
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|