87 lines
2.2 KiB
PHP
87 lines
2.2 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Http\Middleware;
|
||
|
|
|
||
|
|
use Closure;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\Cache;
|
||
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||
|
|
|
||
|
|
class ValidateRequests
|
||
|
|
{
|
||
|
|
public function handle(Request $request, Closure $next)
|
||
|
|
{
|
||
|
|
//HMAC
|
||
|
|
$clientId = $request->header('X-Client-Id');
|
||
|
|
$timestamp = $request->header('X-Timestamp');
|
||
|
|
$nonce = $request->header('X-Nonce');
|
||
|
|
$signature = $request->header('X-Signature');
|
||
|
|
|
||
|
|
if (!$clientId || !$timestamp || !$nonce || !$signature) {
|
||
|
|
abort(401, 'Missing HMAC headers');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($clientId !== config('services.node.client_id')) {
|
||
|
|
abort(401, 'Invalid client id');
|
||
|
|
}
|
||
|
|
|
||
|
|
/** ✅ اینجا SECRET واقعاً استفاده میشود */
|
||
|
|
$secret = config('services.node.secret');
|
||
|
|
if (!$secret) {
|
||
|
|
abort(500, 'Node shared secret not configured');
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
$data = json_decode($request->getContent(), true);
|
||
|
|
ksort($data);
|
||
|
|
|
||
|
|
$normalized = json_encode(
|
||
|
|
$data,
|
||
|
|
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
|
||
|
|
);
|
||
|
|
|
||
|
|
$bodyHash = hash('sha256', $normalized);
|
||
|
|
|
||
|
|
|
||
|
|
// $bodyHash = hash('sha256', $request->getContent() ?? '');
|
||
|
|
|
||
|
|
$canonical = implode("\n", [
|
||
|
|
strtoupper($request->method()),
|
||
|
|
'/' . ltrim($request->path(), '/'),
|
||
|
|
$bodyHash,
|
||
|
|
$timestamp,
|
||
|
|
$nonce,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$expectedSignature = hash_hmac(
|
||
|
|
'sha256',
|
||
|
|
$canonical,
|
||
|
|
$secret
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!hash_equals($expectedSignature, $signature)) {
|
||
|
|
abort(401, 'Invalid HMAC signature');
|
||
|
|
}
|
||
|
|
|
||
|
|
// return $next($request);
|
||
|
|
|
||
|
|
//ANTI REPLAY
|
||
|
|
$timestamp = (int)$request->header('X-Timestamp');
|
||
|
|
$nonce = $request->header('X-Nonce');
|
||
|
|
|
||
|
|
if (abs(time() - $timestamp) > 99999999999999999) {
|
||
|
|
abort(401, 'Expired request');
|
||
|
|
}
|
||
|
|
|
||
|
|
$key = "node_nonce:{$nonce}";
|
||
|
|
if (Cache::has($key)) {
|
||
|
|
abort(401, 'Replay attack detected');
|
||
|
|
}
|
||
|
|
|
||
|
|
Cache::put($key, true, 60);
|
||
|
|
|
||
|
|
return $next($request);
|
||
|
|
}
|
||
|
|
}
|