Files
intaleq_v2/app/Http/Middleware/HmacAuthMiddleware.php
2026-04-24 15:12:12 +03:00

102 lines
3.6 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use App\Services\PayloadCrypto;
/**
* وسيط التحقق من التوقيع الرقمي (HMAC Validation Middleware)
*
* الغرض من الملف:
* توفير طبقة أمان إضافية لجميع الطلبات لضمان عدم التلاعب بالبيانات أثناء انتقالها بين التطبيق والخادم.
*
* كيفية العمل:
* 1. يتطلب وجود "توقيع" (Signature) في رأس الطلب (Headers).
* 2. يقوم الخادم بإعادة حساب التوقيع باستخدام مفتاح سري (API Secret) ومقارنته بالتوقيع المرسل.
* 3. حماية البيانات: إذا كانت البيانات مشفرة، يقوم بفك تشفيرها باستخدام نفس المفتاح السري (API Secret).
* 4. يضمن أن البيانات لم تتغير في الطريق (Data Integrity).
*/
class HmacAuthMiddleware
{
private const ALGORITHM = 'sha256';
private PayloadCrypto $crypto;
public function __construct(PayloadCrypto $crypto)
{
$this->crypto = $crypto;
}
public function handle(Request $request, Closure $next)
{
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Timestamp');
$nonce = $request->header('X-Nonce');
$apiKey = $request->header('X-API-Key');
// All headers required
if (!$signature || !$timestamp || !$nonce || !$apiKey) {
return response()->json([
'status' => 'failure',
'message' => 'Missing security headers'
], 401);
}
// Reject if timestamp older than tolerance (replay protection)
$tolerance = config('intaleq.hmac_tolerance', 300);
if (abs(time() - (int) $timestamp) > $tolerance) {
return response()->json([
'status' => 'failure',
'message' => 'Request expired'
], 401);
}
// Nonce replay check (prevent duplicate requests)
$nonceKey = 'nonce:' . $nonce;
if (Cache::has($nonceKey)) {
return response()->json([
'status' => 'failure',
'message' => 'Duplicate request'
], 401);
}
Cache::put($nonceKey, true, $tolerance);
// Lookup api_secret by api_key
$user = DB::connection('primary')->table('passengers')
->where('api_key', $apiKey)->first(['api_secret']);
if (!$user) {
$user = DB::connection('primary')->table('driver')
->where('api_key', $apiKey)->first(['api_secret']);
}
if (!$user) {
return response()->json([
'status' => 'failure',
'message' => 'Invalid API key'
], 401);
}
// Compute expected signature
// METHOD:PATH:TIMESTAMP:NONCE:BODY
$method = strtoupper($request->method());
$path = $request->path(); // returns path without leading slash (e.g. v2/ride/create)
$body = $request->getContent();
$payload = "$method:$path:$timestamp:$nonce:$body";
$expected = hash_hmac(self::ALGORITHM, $payload, $user->api_secret);
if (!hash_equals($expected, $signature)) {
return response()->json([
'status' => 'failure',
'message' => 'Invalid signature'
], 401);
}
return $next($request);
}
}