Files
intaleq_v2/app/Http/Middleware/HmacAuthMiddleware.php
2026-04-22 23:16:23 +03:00

137 lines
4.7 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
/**
* وسيط التحقق من التوقيع الرقمي (HMAC Validation Middleware)
*
* الغرض من الملف:
* توفير طبقة أمان إضافية لجميع الطلبات لضمان عدم التلاعب بالبيانات أثناء انتقالها بين التطبيق والخادم.
*
* كيفية العمل:
* 1. يتطلب وجود "توقيع" (Signature) في رأس الطلب (Headers).
* 2. يقوم الخادم بإعادة حساب التوقيع باستخدام مفتاح سري (API Secret) ومقارنته بالتوقيع المرسل.
* 3. يحمي من هجمات "إعادة الإرسال" (Replay Attacks) عن طريق التحقق من الـ Nonce والـ Timestamp.
* 4. يضمن أن البيانات لم تتغير في الطريق (Data Integrity).
*/
class HmacAuthMiddleware
{
private const ALGORITHM = 'sha256';
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-Key');
$timestamp = $request->header('X-Timestamp');
$signature = $request->header('X-Signature');
$nonce = $request->header('X-Nonce');
// 1. Check required headers
if (!$apiKey || !$timestamp || !$signature) {
return response()->json([
'status' => 'failure',
'message' => 'Missing authentication headers'
], 401);
}
// 2. Validate timestamp (prevent replay attacks)
$tolerance = (int) config('intaleq.hmac_tolerance', 300);
$timeDiff = abs(time() - (int) $timestamp);
if ($timeDiff > $tolerance) {
return response()->json([
'status' => 'failure',
'message' => 'Request expired'
], 401);
}
// 3. Check nonce uniqueness (if provided)
if ($nonce) {
$nonceKey = "nonce:{$nonce}";
if (Cache::has($nonceKey)) {
return response()->json([
'status' => 'failure',
'message' => 'Duplicate request'
], 401);
}
// Store nonce for double the tolerance window
Cache::put($nonceKey, true, $tolerance * 2);
}
// 4. Lookup API secret from database
$credentials = $this->getApiCredentials($apiKey);
if (!$credentials) {
return response()->json([
'status' => 'failure',
'message' => 'Invalid API key'
], 401);
}
// 5. Reconstruct and verify HMAC signature
$payload = $request->getContent();
$message = "{$timestamp}|{$apiKey}|{$payload}";
$expectedSignature = hash_hmac(self::ALGORITHM, $message, $credentials->api_secret);
if (!hash_equals($expectedSignature, $signature)) {
return response()->json([
'status' => 'failure',
'message' => 'Invalid signature'
], 401);
}
// 6. Attach user info to request for controllers
$request->merge([
'_auth_user_id' => $credentials->user_id,
'_auth_user_type' => $credentials->user_type,
]);
return $next($request);
}
/**
* Get API credentials with Redis caching (5 min)
*/
private function getApiCredentials(string $apiKey): ?object
{
$cacheKey = "api_cred:{$apiKey}";
return Cache::remember($cacheKey, 300, function () use ($apiKey) {
// Check both driver and passenger tables for API keys
$driver = DB::connection('primary')
->table('driver')
->select('id as user_id', 'api_secret')
->selectRaw("'driver' as user_type")
->where('api_key', $apiKey)
->where('status', 'notDeleted')
->first();
if ($driver) return $driver;
$passenger = DB::connection('primary')
->table('passengers')
->select('id as user_id', 'api_secret')
->selectRaw("'passenger' as user_type")
->where('api_key', $apiKey)
->where('status', 'notDeleted')
->first();
if ($passenger) return $passenger;
// Check admin users
$admin = DB::connection('primary')
->table('adminUser')
->select('id as user_id', 'api_secret')
->selectRaw("'admin' as user_type")
->where('api_key', $apiKey)
->first();
return $admin;
});
}
}