diff --git a/backend/Admin/driver/updateDriverFromAdmin.php b/backend/Admin/driver/updateDriverFromAdmin.php
index d04a1c1..1ac484a 100644
--- a/backend/Admin/driver/updateDriverFromAdmin.php
+++ b/backend/Admin/driver/updateDriverFromAdmin.php
@@ -3,27 +3,73 @@ require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("id");
$phone = filterRequest("phone");
+$status = filterRequest("status");
-// تشفير رقم الهاتف
-$encphone = $encryptionHelper->encryptData($phone);
+if (empty($driver_id)) {
+ jsonError("Driver ID is required.");
+}
-$sql = "UPDATE `driver` SET `phone` = :encphone WHERE `id` = :id";
+$updateFields = [];
+$params = [':id' => $driver_id];
+
+if ($phone !== null && $phone !== '') {
+ $encphone = $encryptionHelper->encryptData($phone);
+ $updateFields[] = "`phone` = :phone";
+ $params[':phone'] = $encphone;
+}
+
+if ($status !== null && $status !== '') {
+ $updateFields[] = "`status` = :status";
+ $params[':status'] = $status;
+}
+
+if (empty($updateFields)) {
+ jsonError("No parameters provided for update.");
+}
+
+$sql = "UPDATE `driver` SET " . implode(", ", $updateFields) . " WHERE `id` = :id";
$stmt = $con->prepare($sql);
-// Bind values
-$stmt->bindParam(':encphone', $encphone, PDO::PARAM_STR);
-$stmt->bindParam(':id', $driver_id, PDO::PARAM_STR);
-
try {
- $stmt->execute();
+ $stmt->execute($params);
if ($stmt->rowCount() > 0) {
- // تم التحديث بنجاح
- logAudit($con, $user_id, "تعديل رقم هاتف سائق", "driver", $driver_id, ["phone" => $phone]);
- jsonSuccess(null, "Phone updated successfully.");
+ logAudit($con, $user_id, "تعديل بيانات سائق من لوحة التحكم", "driver", $driver_id, [
+ "phone" => $phone,
+ "status" => $status
+ ]);
+
+ // إذا تم تفعيل السائق، نرسل له رسالة ترحيبية عبر الواتساب لتأكيد التفعيل
+ if ($status === 'active' || $status === 'actives') {
+ // جلب معلومات السائق لإرسال الرسالة
+ $selectSql = "SELECT `phone`, `first_name` FROM `driver` WHERE `id` = :id";
+ $selectStmt = $con->prepare($selectSql);
+ $selectStmt->execute([':id' => $driver_id]);
+ $driverData = $selectStmt->fetch(PDO::FETCH_ASSOC);
+
+ if ($driverData) {
+ $decryptedPhone = $encryptionHelper->decryptData($driverData['phone']);
+ $firstName = $encryptionHelper->decryptData($driverData['first_name']);
+
+ $supportPhones = ['0952475740', '0952475742'];
+ $randomIndex = array_rand($supportPhones);
+ $phoneToUse = $supportPhones[$randomIndex];
+ $randomNumber = rand(1000, 999999);
+
+ $messageBody = "أهلاً وسهلاً كابتن $firstName 👋\n"
+ . "تم تفعيل حسابك على تطبيق *سيرو*.\n"
+ . "يمكنك الآن تسجيل الدخول والبدء بالعمل مباشرة.\n"
+ . "للمساعدة تواصل معنا على الرقم: $phoneToUse\n"
+ . "نتمنى لك عمل موفق 🚖\n\n"
+ . "معرف الرسالة: $randomNumber";
+
+ sendWhatsAppFromServer($decryptedPhone, $messageBody);
+ }
+ }
+
+ jsonSuccess(null, "Driver updated successfully.");
} else {
- // لم يتم العثور على أي سجل للتحديث
- jsonError("No records updated. Please check the driver ID.");
+ jsonError("No records updated or driver not found.");
}
} catch (PDOException $e) {
jsonError("Error updating record: " . $e->getMessage());
diff --git a/backend/ride/pricing/get.php b/backend/ride/pricing/get.php
index fd2d399..d13be2e 100644
--- a/backend/ride/pricing/get.php
+++ b/backend/ride/pricing/get.php
@@ -21,11 +21,21 @@ if (!$country) {
exit;
}
-$price = 0.0;
-$price_for_driver = 0.0;
-$withCommission = 0.0;
-$kazan = 0.0;
-$isNightFare = false;
+$prices = [];
+$pricesRaw = [];
+
+$categories = [
+ 'totalPassengerSpeed' => 'Speed',
+ 'totalPassengerBalash' => 'Awfar Car',
+ 'totalPassengerComfort' => 'Comfort',
+ 'totalPassengerElectric' => 'Electric',
+ 'totalPassengerLady' => 'Lady',
+ 'totalPassengerScooter' => 'Delivery',
+ 'totalPassengerVan' => 'Van',
+ 'totalPassengerRayehGai' => 'Speed',
+ 'totalPassengerRayehGaiComfort' => 'Comfort',
+ 'totalPassengerRayehGaiBalash' => 'Awfar Car',
+];
// Common variables
date_default_timezone_set('Asia/Damascus');
@@ -58,27 +68,52 @@ if (!$kazanRow) {
exit;
}
-$result = calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng);
-$price = $result['price'];
-$price_for_driver = $result['price_for_driver'];
-$withCommission = $result['withCommission'];
-$kazan = $result['kazan'];
-$isNightFare = $result['isNightFare'];
-
// ----------------------------------------------------------------------
// Helper Functions for Countries
// ----------------------------------------------------------------------
-function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng) {
- $comfortPrice = (float) $kazanRow['comfortPrice'];
- $speedPrice = (float) $kazanRow['speedPrice'];
- $familyPrice = (float) $kazanRow['familyPrice'];
- $deliveryPrice = (float) $kazanRow['deliveryPrice'];
- $naturePrice = (float) $kazanRow['naturePrice'];
- $heavyPrice = (float) $kazanRow['heavyPrice'];
- $latePrice = (float) $kazanRow['latePrice'];
- $kazanPercent = (float) $kazanRow['kazan'];
+function getPerKmRate($carType, $kazanRow) {
+ $rateColumns = [
+ 'Comfort' => 'comfortPrice',
+ 'Speed' => 'speedPrice',
+ 'Lady' => 'ladyPrice',
+ 'Electric' => 'electricPrice',
+ 'Van' => 'vanPrice',
+ 'Delivery' => 'deliveryPrice',
+ 'Mishwar Vip' => 'mishwarVipPrice',
+ 'Fixed Price' => 'fixedPrice',
+ 'Awfar Car' => 'awfarPrice',
+ ];
+ $column = $rateColumns[$carType] ?? 'speedPrice';
+ $rate = floatval($kazanRow[$column] ?? 0);
+
+ if ($rate <= 0) {
+ $oldColumnMap = [
+ 'Lady' => 'familyPrice',
+ 'Mishwar Vip' => 'freePrice',
+ 'Electric' => 'naturePrice',
+ 'Van' => 'heavyPrice',
+ ];
+ $oldColumn = $oldColumnMap[$carType] ?? null;
+ if ($oldColumn && isset($kazanRow[$oldColumn])) {
+ $rate = floatval($kazanRow[$oldColumn]);
+ }
+ }
+
+ if ($rate <= 0) {
+ $rate = floatval($kazanRow['speedPrice'] ?? 36);
+ }
+
+ return $rate;
+}
+
+function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType = 'Speed') {
+ $naturePrice = (float) ($kazanRow['naturePrice'] ?? 0);
+ $heavyPrice = (float) ($kazanRow['heavyPrice'] ?? 0);
+ $latePrice = (float) ($kazanRow['latePrice'] ?? 0);
+ $kazanPercent = (float) ($kazanRow['kazan'] ?? 10);
+
// === General Settings ===
$minBillableKm = 0.2;
$airportAddon = 0.0;
@@ -124,7 +159,8 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
$billableDistance = ($distance < $minBillableKm) ? $minBillableKm : $distance;
$isLongSpeed = $billableDistance > $longSpeedThresholdKm;
- $perKmSpeedBaseFromServer = $speedPrice;
+
+ $perKmSpeedBaseFromServer = getPerKmRate($carType, $kazanRow);
$perKmSpeed = $isLongSpeed ? $longSpeedPerKm : $perKmSpeedBaseFromServer;
$reductionPct40 = 0.0;
@@ -205,64 +241,64 @@ if (!empty($promo_code)) {
}
// 3. Fetch Passenger Wallet (Negative Balance / Debt)
-// Using Redis for ultra-fast reads, with a fallback to the Payment Server API.
$negativeBalance = 0;
if (!empty($passenger_id)) {
try {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redisKey = "passenger_debt_" . $passenger_id;
-
$redisDebt = $redis->get($redisKey);
if ($redisDebt !== false) {
$negativeBalance = (float) $redisDebt;
- } else {
- // Fallback: If not in Redis, call the Payment Server Endpoint
- // TODO: Replace with the actual Payment Server Endpoint URL and API Key
- /*
- $paymentApiUrl = "https://payment.siroapp.com/api/get_debt?passenger_id=" . urlencode($passenger_id);
- $options = [
- "http" => [
- "header" => "Authorization: Bearer YOUR_API_KEY\r\n"
- ]
- ];
- $context = stream_context_create($options);
- $response = file_get_contents($paymentApiUrl, false, $context);
- if ($response !== false) {
- $data = json_decode($response, true);
- if (isset($data['debt'])) {
- $negativeBalance = (float) $data['debt'];
- // Cache the result in Redis for future pricing calls (e.g. 1 hour)
- $redis->setex($redisKey, 3600, $negativeBalance);
- }
- }
- */
}
} catch (Exception $e) {
- // If Redis fails, gracefully default to 0 or fallback API
$negativeBalance = 0;
}
}
-$prices = ['total' => $withCommission];
+// Calculate prices for all categories
+foreach ($categories as $key => $carType) {
+ $result = calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType);
+ $withCommission = $result['withCommission'];
+ $price_for_driver = $result['price_for_driver'];
-// 4. Apply Discount and Negative Balance
-foreach ($prices as $key => $price) {
- // Apply discount (Assuming percentage discount if amount <= 100, else fixed amount)
+ // Apply discount
if ($discount > 0 && $discount <= 100) {
- $prices[$key] = max(0, $price - ($price * ($discount / 100)));
+ $finalPrice = max(0, $withCommission - ($withCommission * ($discount / 100)));
} else {
- $prices[$key] = max(0, $price - $discount);
+ $finalPrice = max(0, $withCommission - $discount);
}
// Add negative balance
- $prices[$key] += $negativeBalance;
+ $finalPrice += $negativeBalance;
+
+ $prices[$key] = $finalPrice;
+
+ // For the token, we map the clean database carType to the final price and driver price
+ $pricesRaw[$carType] = [
+ 'price' => $finalPrice,
+ 'driver_price' => $price_for_driver
+ ];
+}
+
+// 4. Generate Cryptographically Signed Token
+$priceToken = "";
+if (isset($encryptionHelper)) {
+ $tokenPayload = [
+ 'passenger_id' => $passenger_id,
+ 'start_location' => $passengerLat . ',' . $passengerLng,
+ 'end_location' => $destLat . ',' . $destLng,
+ 'expires' => time() + 180, // Valid for 3 minutes
+ 'prices' => $pricesRaw
+ ];
+ $priceToken = $encryptionHelper->encryptData(json_encode($tokenPayload));
}
echo json_encode([
'status' => 'success',
'data' => $prices,
+ 'price_token' => $priceToken,
'applied_discount' => $discount,
'added_negative_balance' => $negativeBalance
]);
diff --git a/backend/ride/rides/add.php b/backend/ride/rides/add.php
deleted file mode 100644
index 41c1d6f..0000000
--- a/backend/ride/rides/add.php
+++ /dev/null
@@ -1,128 +0,0 @@
- $start_location,
- ":end_location" => $end_location,
- ":date" => $date_formatted, // نستخدم الصيغة المعالجة
- ":time" => $time_formatted, // نستخدم الصيغة المعالجة
- ":endtime" => $endtime_formatted,
- ":price" => $price,
- ":passenger_id" => $passenger_id,
- ":driver_id" => $driver_id,
- ":status" => $status,
- ":carType" => $carType,
- ":price_for_driver" => $price_for_driver,
- ":price_for_passenger" => $price_for_passenger,
- ":distance" => $distance,
-];
-
-// تسجيل البيانات التي سيتم إدخالها للتأكد
-error_log("ℹ️ [add_ride.php] Prepared Data: " . json_encode($data));
-
-// ---------------------------------------------------------
-// 3. الإضافة في السيرفر المحلي (Main DB)
-// ---------------------------------------------------------
-
-$sql = "INSERT INTO `ride` (
- `start_location`, `end_location`, `date`, `time`, `endtime`,
- `price`, `passenger_id`, `driver_id`, `status`, `carType`,
- `price_for_driver`, `price_for_passenger`, `distance`
-) VALUES (
- :start_location, :end_location, :date, :time, :endtime,
- :price, :passenger_id, :driver_id, :status, :carType,
- :price_for_driver, :price_for_passenger, :distance
-)";
-
-try {
- error_log("🔄 [add_ride.php] Inserting into LOCAL DB...");
-
- $stmt = $con->prepare($sql);
- $stmt->execute($data);
-
- $insertedId = $con->lastInsertId();
- $count = $stmt->rowCount();
-
- error_log("✅ [add_ride.php] Local Insert Success. ID: $insertedId");
-
- if ($count > 0) {
-
- // ---------------------------------------------------------
- // 4. الإضافة في سيرفر التتبع (Tracking DB)
- // ---------------------------------------------------------
-
- $sqlRemote = "INSERT INTO `ride` (
- `id`, `start_location`, `end_location`, `date`, `time`, `endtime`,
- `price`, `passenger_id`, `driver_id`, `status`, `carType`,
- `price_for_driver`, `price_for_passenger`, `distance`
- ) VALUES (
- :id, :start_location, :end_location, :date, :time, :endtime,
- :price, :passenger_id, :driver_id, :status, :carType,
- :price_for_driver, :price_for_passenger, :distance
- )";
-
- // إضافة الـ ID للمصفوفة
- $data[':id'] = $insertedId;
-
- try {
- error_log("🔄 [add_ride.php] Inserting into REMOTE DB...");
-
- $stmtRemote = $con_ride->prepare($sqlRemote);
- $stmtRemote->execute($data);
-
- error_log("✅ [add_ride.php] Remote Insert Success.");
-
- } catch (PDOException $eRemote) {
- // نسجل خطأ الريموت لكن لا نوقف العملية لأن اللوكل تم بنجاح
- error_log("⚠️ [add_ride.php] Remote DB Error: " . $eRemote->getMessage());
- }
-
- // طباعة النجاح (JSON صحيح)
- jsonSuccess($insertedId);
-
- } else {
- error_log("❌ [add_ride.php] Failed to insert locally (Rows affected 0).");
- jsonError("Failed to save ride information locally");
- }
-
-} catch (PDOException $e) {
- // تسجيل الخطأ بدقة
- error_log("❌ [add_ride.php] SQL Error: " . $e->getMessage());
- jsonError("Database Error: " . $e->getMessage());
-}
-?>
\ No newline at end of file
diff --git a/backend/ride/rides/add_ride.php b/backend/ride/rides/add_ride.php
index d45bf40..267843f 100644
--- a/backend/ride/rides/add_ride.php
+++ b/backend/ride/rides/add_ride.php
@@ -57,6 +57,7 @@ error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id']
$start_location = filterRequest("start_location");
$end_location = filterRequest("end_location");
$price = filterRequest("price");
+$price_token = filterRequest("price_token");
$passenger_id = filterRequest("passenger_id");
$driver_id = filterRequest("driver_id") ?: 0;
$status = filterRequest("status");
@@ -82,6 +83,15 @@ $step2 = filterRequest("step2");
$step3 = filterRequest("step3");
$step4 = filterRequest("step4");
+// Helper to compare coordinates (allowing slight GPS precision drift up to ~500m)
+function coordsMatch($coordStr1, $coordStr2, $tolerance = 0.005) {
+ if (empty($coordStr1) || empty($coordStr2)) return false;
+ $c1 = array_map('floatval', explode(',', $coordStr1));
+ $c2 = array_map('floatval', explode(',', $coordStr2));
+ if (count($c1) < 2 || count($c2) < 2) return false;
+ return (abs($c1[0] - $c2[0]) < $tolerance) && (abs($c1[1] - $c2[1]) < $tolerance);
+}
+
// Validation
if (empty($passenger_id) || empty($start_location) || empty($end_location) || empty($price)) {
error_log("[add_ride] Validation failed — missing required fields.");
@@ -89,6 +99,50 @@ if (empty($passenger_id) || empty($start_location) || empty($end_location) || em
exit;
}
+// SECURE PRICE TOKEN VERIFICATION
+if (empty($price_token)) {
+ error_log("[add_ride] Security failed — price_token is missing.");
+ printFailure("Secure price token is required");
+ exit;
+}
+
+$decrypted = isset($encryptionHelper) ? $encryptionHelper->decryptData($price_token) : false;
+if (!$decrypted) {
+ error_log("[add_ride] Security failed — failed to decrypt price_token.");
+ printFailure("Invalid or tampered price token");
+ exit;
+}
+
+$tokenData = json_decode($decrypted, true);
+if (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time()) {
+ error_log("[add_ride] Security failed — token is expired or invalid JSON.");
+ printFailure("Price token has expired, please request estimation again");
+ exit;
+}
+
+if ($tokenData['passenger_id'] != $passenger_id) {
+ error_log("[add_ride] Security failed — passenger_id mismatch.");
+ printFailure("Tampered price token (passenger mismatch)");
+ exit;
+}
+
+if (!coordsMatch($tokenData['start_location'], $start_location) || !coordsMatch($tokenData['end_location'], $end_location)) {
+ error_log("[add_ride] Security failed — coordinates mismatch. Token: " . ($tokenData['start_location'] . " / " . $tokenData['end_location']) . " Request: " . ($start_location . " / " . $end_location));
+ printFailure("Tampered price token (route mismatch)");
+ exit;
+}
+
+if (!isset($tokenData['prices'][$carType])) {
+ error_log("[add_ride] Security failed — car type $carType not found in token.");
+ printFailure("Invalid car type for this token");
+ exit;
+}
+
+// Securely override pricing from the cryptographically signed token
+$price = $tokenData['prices'][$carType]['price'];
+$price_for_driver = $tokenData['prices'][$carType]['driver_price'];
+$price_for_passenger = $price;
+
// ── 2. تنسيق التواريخ ─────────────────────────────────────────
$date_formatted = date("Y-m-d");
$time_formatted = date("H:i:s");
diff --git a/backend/test_signed_pricing.php b/backend/test_signed_pricing.php
new file mode 100644
index 0000000..9154c10
--- /dev/null
+++ b/backend/test_signed_pricing.php
@@ -0,0 +1,94 @@
+
diff --git a/knowledge/CODE_REVIEW_REPORT_AR.md b/knowledge/CODE_REVIEW_REPORT_AR.md
new file mode 100644
index 0000000..8d393b8
--- /dev/null
+++ b/knowledge/CODE_REVIEW_REPORT_AR.md
@@ -0,0 +1,276 @@
+# تقرير المراجعة البرمجية الشاملة لنظام Siro (طلب السيارات)
+
+---
+
+## مقدمة
+
+هذا التقرير يقدم مراجعة برمجية شاملة ومحاكاة تفصيلية لمسار الرحلة الكامل في تطبيق طلب السيارات **Siro**. تم تحليل الكود المصدري للمشروع بما يشمل الواجهة الخلفية (PHP)، سيرفرات الـ WebSockets (PHP + Socket.IO)، والواجهة الأمامية (Flutter/Dart). يغطي التقرير أربعة محاور رئيسية: معالجة الرحلة في الخلفية، الاتصالات اللحظية عبر WebSockets، أداء الواجهات الأمامية، وتقرير المشاكل والحلول المقترحة.
+
+---
+
+## المحور الأول: الواجهة الخلفية (Backend)
+
+### 1.1 إنشاء الرحلة — `add.php` (rides)
+
+**الملف:** `backend/ride/rides/add.php`
+
+**تحليل سير العمل:**
+
+1. **استلام البيانات:** يستقبل الملف بيانات الرحلة (موقع البداية، الوجهة، التاريخ، الوقت، السعر، معرف الراكب، معرف السائق، الحالة، نوع السيارة، إلخ).
+2. **معالجة التواريخ:** يتم تحويل التواريخ الخام باستخدام `strtotime()` و `date()` لتتوافق مع تنسيق MySQL.
+3. **إدراج مزدوج (Dual Insertion):**
+ - إدراج السجل في قاعدة البيانات المحلية (`intaleqDB1`).
+ - إدراج نسخة مطابقة في قاعدة بيانات التتبع عن بعد (`intaleq-ridesDB`) باستخدام نفس `insertedId`.
+
+**نقاط القوة:**
+ - ✅ استخدام الـ Prepared Statements يمنع ثغرات SQL Injection.
+ - ✅ تسجيل الأخطاء المفصل عبر `error_log()` يساعد في تتبع المشاكل.
+ - ✅ النسخ الاحتياطي عبر قاعدة بيانات التتبع يوفر تكرارية (Redundancy).
+
+**نقاط الضعف والمخاطر:**
+ - ❌ **لا يوجد Transaction (معاملة ذرية):** إذا نجح الإدراج في قاعدة البيانات المحلية وفشل في قاعدة التتبع، يصبح النظام في حالة عدم تناسق (Inconsistent State). الحل: لفّ العمليتين داخل `$con->beginTransaction()` مع `$con->rollBack()` عند فشل أي منهما.
+ - ❌ **لا يوجد قفل (Lock) لمنع التكرار:** يمكن إرسال طلب الرحلة مرتين في نفس الوقت (Race Condition)، مما قد يؤدي إلى إنشاء رحلتين مكررتين. الحل: إضافة `INSERT ... ON DUPLICATE KEY UPDATE` أو استخدام `SELECT ... FOR UPDATE` قبل الإدراج.
+ - ❌ **السعر يُرسل من العميل (Client-Side):** قيمة `price` تُستلم مباشرة من الطلب دون أي تحقق أو إعادة حساب على الخادم. هذا يشكل خطراً أمنياً كبيراً حيث يمكن للعميل التلاعب بالسعر. الحل: إعادة حساب السعر على الخادم بناءً على المسافة ونوع السيارة وتسعيرة الدولة الحالية.
+
+### 1.2 حساب التكلفة — `pricing/get.php`
+
+**المسار:** `backend/ride/pricing/`
+
+**تحليل معمق:**
+- يتم حساب السعر عن طريق إرسال المسافة والمدة المتوقعة من العميل إلى الخادم.
+- الخادم يستعلم جدول `kazan` (نسبة العمولة) وجدول `pricing` (تسعيرة الدولة).
+- يتم تطبيق الخصومات والعروض الترويجية (Promo Codes) عبر استعلام SQL.
+
+**نقاط الضعف:**
+ - ❌ **إرسال البيانات من العميل (`distance`, `durationToRide`):** يمكن التلاعب بالمسافة والمدة مما يؤثر على السعر.
+ - ❌ **لا يوجد تحقق من توافق السعر المُرسل مع السعر المُحتسب:** التطبيق يرسل السعر النهائي ويتم إدراجه مباشرة دون مقارنته بالسعر الذي حسبه الخادم.
+
+**الحل المقترح:**
+ - إعادة حساب السعر بالكامل على الخادم باستخدام إحداثيات البداية والنهاية (`start_location`, `end_location`).
+ - استخدام Map SaaS أو OSRM لحساب المسافة والمدة على الخادم وليس على العميل.
+ - رفض الطلب إذا كان السعر المُرسل من العميل لا يتطابق مع السعر المُحتسب.
+
+### 1.3 عملية الخصم المالي (Server-to-Server / Wallet)
+
+**المسار:** `walletintaleq.intaleq.xyz/v2/main/`
+
+**سير العمل الحالي:**
+- يتم الاتصال بسيرفر المحفظة المالية عبر `CRUD().postWallet()` الذي يرسل HMAC + JWT للمصادقة.
+- العملية تتم بعد انتهاء الرحلة عبر `payment_method.page.dart`.
+
+**تحليل الأمان:**
+- ✅ استخدام HMAC للمصادقة بين الخادمين (S2S) يوفر طبقة أمان جيدة.
+- ✅ JWT منفصل للمحفظة يوفر فصل الصلاحيات (Wallet JWT).
+
+**المخاطر:**
+- ❌ **لا توجد معاملة ذرية (Atomic Transaction) بين إنهاء الرحلة والخصم المالي:** إذا تم إنهاء الرحلة وفشل الخصم المالي (مثلاً بسبب انقطاع الشبكة)، تبقى الرحلة منتهية بدون دفع.
+ - **الحل:** استخدام نمط Saga (عكس المعاملة): إذا فشل الخصم، إعادة الرحلة إلى الحالة "غير منتهية" عبر `COMPENSATING TRANSACTION`.
+- ❌ **تكرار طلب الدفع:** إذا أرسل العميل طلب الدفع مرتين (Double Payment)، قد يتم الخصم مرتين.
+ - **الحل:** إضافة `idempotency_key` لكل معاملة دفع، والتأكد من أن الخادم لا يعالج نفس المفتاح مرتين.
+
+---
+
+## المحور الثاني: سيرفرات الويب سوكيت (WebSockets)
+
+### 2.1 سيرفر السائقين — `driver_socket.php` (بورت 2020)
+
+**الملف:** `socket_intaleq/driver_socket.php`
+
+**تحليل المعمارية:**
+
+**المستوى المتقدم (Level 2 Architecture):**
+1. **Event Buffering (تجميع الأحداث):** بدلاً من إرسال كل تحديث موقع إلى Redis بشكل منفصل، يتم تجميع التحديثات في `$eventBuffer` وإرسالها كل 500ms عبر Redis Pipeline.
+2. **تقليل عمليات Redis:** يتم تجاهل التحديثات إذا لم يتغير الموقع بأكثر من 10 أمتار، أو لم تتغير السرعة بأكثر من 1 م/ث، أو لم يتغير الاتجاه بأكثر من 5 درجات.
+3. **Forward غير متزامن (Async):** يتم إرسال موقع السائق إلى سيرفر الراكب عبر HTTP غير متزامن مع Throttle (كل 3 ثوانٍ و 15 متراً كحد أدنى).
+
+**نقاط القوة:**
+- ✅ **Redis Pipeline:** يقلل عدد اتصالات Redis من مئات إلى اتصال واحد كل نصف ثانية.
+- ✅ **Async HTTP Forward:** لا يحجب سير العمل الرئيسي عن إرسال التحديثات.
+- ✅ **Throttle ذكي:** يمنع إغراق سيرفر الراكب بالتحديثات المتكررة.
+
+**نقاط الضعف:**
+- ❌ **فقدان الأحداث عند انهيار السيرفر:** `$eventBuffer` مخزّن في الذاكرة (RAM)، فإذا انهار السيرفر، تُفقد جميع الأحداث المجمّعة قبل كتابتها في Redis.
+ - **الحل:** استخدام Redis Queue (قائمة انتظار) بدلاً منBuffer الذاكرة، أو إضافة Write-Ahead Log (WAL).
+- ❌ **عدم وجود Watchdog للسائق:** إذا انقطع اتصال السائق (مثلاً فقدان الإنترنت)، لا توجد آلية لكشف ذلك وتحديث حالته إلى `offline` بشكل فوري.
+ - **الحل:** إضافة `Heartbeat Timeout` في السيرفر: إذا لم يستقبل نبضاً من السائق لمدة 30 ثانية، يُعتبر مفصولاً.
+
+### 2.2 سيرفر الركاب — `passenger_socket.php` (بورت 3030)
+
+**الملف:** `socket_intaleq/passenger_socket.php`
+
+**تحليل سير العمل:**
+- السيرفر يستمع على بورت 3030 لاتصالات WebSocket من الركاب.
+- بورت 3031 هو HTTP Internal Server لتلقي الأحداث من سيرفر السائقين.
+- عند استقبال حدث `update_driver_location` من سيرفر السائقين، يُبث فوراً للراكب المعني عبر `$io->to('passenger_' . $passengerId)->emit(...)`.
+
+**نقاط القوة:**
+- ✅ **فصل القنوات:** كل راكب لديه قناة خاصة (`passenger_ID`)، مما يضمن الخصوصية.
+- ✅ **مصادقة داخلية:** جميع الطلبات الداخلية تتطلب `x-internal-key` لمنع الوصول غير المصرح به.
+
+**نقاط الضعف:**
+- ❌ **تسجيل مفرط في الملفات:** كل حدث يُسجل في `socket_debug.log`، مما قد يؤدي إلى امتلاء القرص الصلب بسرعة في الإنتاج.
+ - **الحل:** استخدام تدوير السجلات (Log Rotation) وتقليل مستوى التسجيل إلى `ERROR` فقط في الإنتاج، أو للاحداث الهامة فقط كفشل الاتصال.
+- ❌ **لا يوجد Polling Fallback من جهة السيرفر:** إذا فشل WebSocket مع الراكب، السيرفر لا يقوم بإعادة الإرسال عبر HTTP.
+ - **الحل:** إضافة آلية `Message Acknowledgment`: الراكب يرسل `ack` لاستلام الموقع، وإذا لم يستلم، يُعاد الإرسال عبر FCM/Push.
+
+### 2.3 اتصال WebSocket من جهة العميل (الراكب)
+
+**الملف:** `siro_rider/lib/controller/home/map/map_socket_controller.dart`
+
+**تحليل:**
+1. **الاتصال:** يتم تهيئة WebSocket عند بدء البحث عن سائق.
+2. **إعادة الاتصال:** 20 محاولة مع تأخير تصاعدي (2-10 ثوانٍ).
+3. **الاشتراك:** بعد الاتصال، يُرسل `subscribe_driver_location` لربط الرحلة.
+4. **النّبضات الحية (Heartbeat):** تُرسل كل 15 ثانية للحفاظ على الاتصال.
+5. **مراقب الصحة:** `isSocketHealthy()` تتحقق من آخر تحديث (أقل من 20 ثانية).
+
+**نقاط القوة:**
+- ✅ **آلية إعادة اتصال قوية** مع تأخير تصاعدي.
+- ✅ **التبديل التلقائي للاقتراع (Polling Fallback):** عند فقدان الاتصال، يتم تفعيل الاقتراع كل 4 ثوانٍ.
+- ✅ **الكشف عن استقرار WebSocket:** بعد 3 تحديثات موثوقة عبر Socket، يتم إيقاف الاقتراع.
+
+**مشاكل محتملة:**
+- ❌ **مقاطعة Stream:** إذا تم إغلاق الـ Stream (`_timerStreamController`) بشكل غير متوقع، يتوقف المؤقت عن العمل.
+ - **الحل:** التحقق من `!streamController.isClosed` قبل كل إضافة.
+- ❌ **تسريب الذاكرة:** إذا لم يتم استدعاء `disposeRideSocket()` عند تدمير الـ Widget، يبقى الاتصال مفتوحاً.
+ - **الحل:** استخدام `onClose()` في GetX Controller والتحقق من إغلاق جميع الموارد.
+
+---
+
+## المحور الثالث: الواجهة الأمامية (Frontend Clients)
+
+### 3.1 متحكم دورة حياة الرحلة — `RideLifecycleController`
+
+**الملف:** `siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart` (4359 سطر)
+
+**تحليل الأداء:**
+
+**نقاط القوة:**
+- ✅ **آلة حالة متكاملة (State Machine):** `RideState` يغطي جميع حالات الرحلة (`noRide` → `searching` → `driverApplied` → `driverArrived` → `inProgress` → `finished`).
+- ✅ **حرّاس التكرار (Race Condition Guards):**
+ - `_isFinishProcessed` لمنع تكرار معالجة إنهاء الرحلة.
+ - `_isReviewProcessed` لمنع فتح شاشة التقييم مرتين.
+ - `_isAcceptanceProcessed` و `_isRideStartedProcessed` لنفس الغرض.
+- ✅ **مؤقت رئيسي (Master Timer):** يتحكم في دورة حياة الرحلة بالكامل مع فترات استقصاء متغيرة حسب الحالة.
+- ✅ **مراقب الانحراف (Deviation Detection):** يكتشف إذا انحرف السائق عن المسار بأكثر من 30 متراً ويعيد حساب المسار تلقائياً.
+- ✅ **الصوت والتحذيرات:** تحذير عند السرعة الزائدة (100 كم/س) مع زر مشاركة الرحلة.
+
+**المشاكل الهيكلية:**
+- ❌ **حجم الملف كبير جداً (~4359 سطر):** هذا يعتبر Anti-Pattern. يصعب صيانته واختباره.
+ - **الحلول المقترحة:**
+ 1. تقسيم الـ Controller إلى Services متخصصة:
+ - `RideStateMachine` — إدارة الحالات والانتقالات
+ - `RideRouteService` — حساب المسار والانحرافات وإعادة الرسم
+ - `RideTimerService` — إدارة المؤقتات
+ - `RideUIService` — تحديث واجهة المستخدم والعناصر المنبثقة
+ - `RidePaymentService` — معالجة الدفع والخصم
+ 2. استخدام `mixins` لتوزيع الوظائف عبر ملفات متعددة.
+
+### 3.2 رسم المسار والملاحة
+
+**تحليل سير العمل:**
+1. **حساب المسار الأولي:** عند قبول السائق، يتم الاتصال بـ `routec.intaleq.xyz` لحساب المسار.
+2. **رسم المسار على الخريطة:** يتم فك تشفير الـ Polyline وعرضه بلون أصفر (قدوم السائق) أو أزرق (الرحلة الحالية).
+3. **تحديث المسار المتبقي:** `updateRemainingRoute()` تحسب أقرب نقطة للسائق على المسار وتقص النقاط السابقة.
+4. **إعادة الرسم عند الانحراف:** إذا انحرف السائق بأكثر من 30 متراً، يُعاد حساب المسار بالكامل.
+
+**نقاط القوة:**
+- ✅ **فك التشفير في Isolate:** استخدام `compute(decodePolylineIsolate, ...)` يمنع تجميد واجهة المستخدم.
+- ✅ **التحريك الانسيابي (Smooth Animation):** دالة `smoothlyUpdateMarker()` تنقل أيقونة السياره بسلاسة بين النقاط.
+
+**المشاكل:**
+- ❌ **إعادة حساب المسار بشكل متكرر:** إذا كان السائق في منطقة ذات إشارات GPS ضعيفة، قد يتأرجح الموقع مسبباً إعادة حساب مستمرة.
+ - **الحل:** إضافة `Cooldown 10 ثوانٍ` بين عمليات إعادة الحساب، واستخدام **ترشيح كالمان (Kalman Filter)** لتنعيم إحداثيات GPS.
+- ❌ **تخزين المسار في ذاكرة GETX:** `_currentDriverRoutePoints` يمكن أن تصبح كبيرة جداً في الرحلات الطويلة (آلاف النقاط).
+ - **الحل:** ضغط النقاط باستخدام خوارزمية **Douglas-Peucker** لتقليل عدد نقاط المسار مع الحفاظ على الدقة.
+
+### 3.3 التوجيه الصوتي (TTS)
+
+**تحليل:**
+- يتم استخدام `audio_record1.dart` و `NotificationController` للتوجيه الصوتي والتنبيهات.
+- لا يوجد نظام TTS (Text-to-Speech) منفصل للتوجيه الصوتي خطوة بخطوة.
+
+**نقطة الضعف:**
+- ❌ **عدم وجود TTS متكامل مع المسار:** التطبيق لا يقرأ التعليمات الصوتية تلقائياً (مثل "انعطف يميناً بعد 200 متر").
+ - **الحل المقترح:** دمج **Google TTS** مع المسار من OSRM حيث يوفر تعليمات صوتية نصية يمكن تحويلها إلى صوت عبر `flutter_tts` package.
+
+---
+
+## المحور الرابع: تقرير المشاكل والأخطاء (Error Report)
+
+### 4.1 ثغرات Race Conditions
+
+| المشكلة | الموقع | الوصف | الحل المقترح |
+|---------|--------|-------|--------------|
+| **تكرار تقييم الرحلة** | `addRateToDriver.php` | يمكن للراكب إرسال تقييمين في نفس الوقت (Double Rating) | إضافة UNIQUE KEY على `(ride_id, passenger_id)` في جدول `ratingDriver` |
+| **تكرار إنشاء الرحلة** | `rides/add.php` | إرسال طلب إنشاء رحلة مرتين يؤدي إلى رحلتين مكررتين | إضافة `idempotency_key` في الطلب والتأكد من عدم معالجة المفتاح مراراً |
+| **تكرار الخصم المالي** | `payment_method.page.dart` | الضغط على زر الدفع مرتين قد يؤدي إلى خصمين | تعطيل الزر فور الضغط الأولى مع `loading state` |
+| **معالجة الإنهاء المزدوج** | `ride_lifecycle_controller.dart` | وصول حدث `finished` من السيرفر ومن المقبس في نفس الوقت | ✅ تم حلها باستخدام `_isFinishProcessed` |
+
+### 4.2 حالات التعارض في WebSockets
+
+| المشكلة | الوصف | الحل |
+|---------|-------|------|
+| **فقدان `ride_taken`** | إذا تم قبول الرحلة من سائقين في نفس الوقت (نادر)، قد يتم إرسال طلبين | استخدام `zrem` في Redis كعملية ذرية مع التحقق من الحذف |
+| **تأخير Forward** | إذا كان الضغط على السيرفر عالياً، قد يتأخر forward موقع السائق إلى الراكب | ترقية إلى WebSocket مباشر بين السائق والراكب بدلاً من HTTP Forward |
+| **فقدان النبضات** | إذا انقطع الإنترنت فجأة، قد يظل السائق متصلاً في قاعدة البيانات | إضافة `Heartbeat Timeout` في السيرفر (30 ثانية بدون نبض = فصل تلقائي) |
+
+### 4.3 مشاكل الأداء
+
+| المشكلة | الوصف | الحل |
+|---------|-------|------|
+| **حجم ملف التحكم** | `ride_lifecycle_controller.dart` (4359 سطر) يؤثر على وقت الترجمة والذاكرة | تقسيمه إلى موديولات أصغر |
+| **استعلامات متكررة** | `getDriverCarsLocationToPassengerAfterApplied` يتم استدعاؤه كل 4-6 ثوانٍ حتى عبر polling | تقليل التردد إلى كل 15 ثانية أو الاعتماد الكامل على WebSocket |
+| **ذاكرة التخزين المؤقت** | بيانات المسار الكامل مخزنة في الذاكرة قد تسبب OutOfMemory في الرحلات الطويلة | استخدام Douglas-Peucker لتقليل عدد النقاط |
+| **Logging غير محكوم** | `socket_debug.log` و `errors.log` قد تملأ القرص | تفعيل Log Rotation وتحديد مستوى logs الإنتاجي |
+
+### 4.4 مشاكل أمنية خطيرة
+
+| المشكلة | الخطر | الحل |
+|---------|-------|------|
+| **سعر الرحلة من العميل** | يمكن للمخترق تعديل سعر الرحلة عبر وسائل الطرف الثالث (مان-إن-ذا-ميدل) أو تعديل الطلب | إعادة حساب السعر بالكامل على الخادم |
+| **لا يوجد تحقق من المسافة** | يمكن تقليل المسافة المُرسلة للحصول على سعر أقل | حساب المسافة على الخادم من الإحداثيات |
+| **تسريب Internal Key** | مفتاح `x-internal-key` مخزّن في ملف نصي على السيرفر | استخدام متغيرات البيئة فقط مع Hashicorp Vault |
+
+---
+
+## خطة التحسينات المقترحة (Roadmap)
+
+### المرحلة الأولى — فورية (عالية الأولوية)
+- [ ] إعادة حساب السعر على الخادم بدلاً من العميل
+- [ ] إضافة `UNIQUE KEY (ride_id, passenger_id)` على جدول `ratingDriver` لمنع التقييم المزدوج
+- [ ] إضافة `idempotency_key` لطلبات إنشاء الرحلة والدفع
+- [ ] إضافة Heartbeat Timeout (30 ثانية) في سيرفر السائقين
+
+### المرحلة الثانية — قصيرة المدى
+- [ ] تحويل `add.php` (rides) إلى استخدام Atomic Transactions مع Rollback
+- [ ] إضافة Kalman Filter لتنعيم إحداثيات GPS
+- [ ] تقليل حجم ملف `ride_lifecycle_controller.dart` عبر التقسيم
+- [ ] إضافة Log Rotation وتحديد مستوى التسجيل
+
+### المرحلة الثالثة — طويلة المدى
+- [ ] استبدال HTTP Forward في WebSocket باتصال مباشر (Peer-to-Peer)
+- [ ] دمج نظام TTS للتوجيه الصوتي
+- [ ] ترحيل Event Buffer من الذاكرة إلى Redis Queue
+- [ ] استخدام Douglas-Peucker لتقليل نقاط المسار
+
+---
+
+## الخلاصة والتقييم العام
+
+| المحور | التقييم | الدرجة |
+|--------|---------|--------|
+| Backend Architecture | جيد مع وجود مخاطر أمنية | 7/10 |
+| WebSocket Architecture | ممتاز مع نظام تجميع الأحداث (Level 2) | 9/10 |
+| Frontend Performance | متكامل لكن يحتاج إعادة هيكلة | 7.5/10 |
+| Race Condition Handling | جيد جداً مع وجود حرّاس مناسبة | 8/10 |
+| Security | يوجد ثغرات خطيرة (سعر الرحلة من العميل) | 5/10 |
+| Code Maintainability | مدمج في ملف واحد كبير جداً | 4/10 |
+
+**التقييم العام: 6.8/10**
+
+النظام قوي من ناحية البنية التحتية للـ WebSocket مع نظام التجميع الذكي، لكنه يعاني من مشاكل أمنية حرجة تتعلق بتسعير الرحلة من جهة العميل، وهيكلة الكود الموحّدة في ملف ضخم يصعب صيانته. الأولوية القصوى يجب أن تكون لإعادة حساب السعر على الخادم وتقسيم الـ Controller الضخم.
+
+---
+
+*إعداد: تحليل برمجي تلقائي - يونيو 2026*
\ No newline at end of file
diff --git a/scratch/car_marker_lady_1781540926836.png b/scratch/car_marker_lady_1781540926836.png
new file mode 100644
index 0000000..780bfaa
Binary files /dev/null and b/scratch/car_marker_lady_1781540926836.png differ
diff --git a/scratch/car_marker_normal_1781540915043.png b/scratch/car_marker_normal_1781540915043.png
new file mode 100644
index 0000000..0787a0f
Binary files /dev/null and b/scratch/car_marker_normal_1781540915043.png differ
diff --git a/scratch/category_comfort_1781540956914.png b/scratch/category_comfort_1781540956914.png
new file mode 100644
index 0000000..bce2012
Binary files /dev/null and b/scratch/category_comfort_1781540956914.png differ
diff --git a/scratch/category_electric_1781540970352.png b/scratch/category_electric_1781540970352.png
new file mode 100644
index 0000000..7d5ba96
Binary files /dev/null and b/scratch/category_electric_1781540970352.png differ
diff --git a/scratch/category_fixed_price_1781540942631.png b/scratch/category_fixed_price_1781540942631.png
new file mode 100644
index 0000000..e9204cf
Binary files /dev/null and b/scratch/category_fixed_price_1781540942631.png differ
diff --git a/scratch/category_lady_1781540984745.png b/scratch/category_lady_1781540984745.png
new file mode 100644
index 0000000..aae0330
Binary files /dev/null and b/scratch/category_lady_1781540984745.png differ
diff --git a/scratch/process_images.py b/scratch/process_images.py
new file mode 100644
index 0000000..b7cf5af
--- /dev/null
+++ b/scratch/process_images.py
@@ -0,0 +1,56 @@
+import os
+import sys
+from PIL import Image
+
+def make_transparent_smooth(img_path, output_path, size):
+ if not os.path.exists(img_path):
+ print(f"Error: {img_path} does not exist.")
+ return False
+
+ img = Image.open(img_path).convert("RGBA")
+ img = img.resize(size, Image.Resampling.LANCZOS)
+
+ datas = img.getdata()
+ new_data = []
+
+ for item in datas:
+ r, g, b, a = item
+ closeness = min(r, g, b)
+ if closeness >= 240:
+ if closeness >= 253:
+ alpha = 0
+ else:
+ alpha = int(a * (253 - closeness) / (253 - 240))
+ new_data.append((255, 255, 255, alpha))
+ else:
+ new_data.append(item)
+
+ img.putdata(new_data)
+ img.save(output_path, "PNG")
+ print(f"Successfully saved processed image to {output_path}")
+ return True
+
+if __name__ == "__main__":
+ artifact_dir = "scratch"
+
+
+ jobs = [
+ ("car_marker_normal_1781540915043.png", "siro_rider/assets/images/car.png", (80, 90)),
+ ("car_marker_normal_1781540915043.png", "siro_driver/assets/images/car.png", (80, 90)),
+ ("car_marker_lady_1781540926836.png", "siro_rider/assets/images/lady1.png", (80, 90)),
+ ("car_marker_lady_1781540926836.png", "siro_driver/assets/images/lady1.png", (80, 90)),
+ ("category_fixed_price_1781540942631.png", "siro_rider/assets/images/carspeed.png", (500, 500)),
+ ("category_comfort_1781540956914.png", "siro_rider/assets/images/blob.png", (500, 500)),
+ ("category_electric_1781540970352.png", "siro_rider/assets/images/electric.png", (500, 500)),
+ ("category_lady_1781540984745.png", "siro_rider/assets/images/lady.png", (500, 500)),
+ ]
+
+
+ for src, dst, size in jobs:
+ make_transparent_smooth(
+ os.path.join(artifact_dir, src),
+ dst,
+ size
+ )
+
+
diff --git a/security_audit_final_report.md b/security_audit_final_report.md
index d863f67..cd5615a 100644
--- a/security_audit_final_report.md
+++ b/security_audit_final_report.md
@@ -1,157 +1,891 @@
-
+# 🛡️ التقرير الأمني الشامل - Siro Application
+## Pure PHP Backend + Flutter Mobile Application
-# تقرير التدقيق الأمني النهائي — Auth Flow
-
-## Siro Admin & Service Staff
-
-> **التاريخ:** 12 يونيو 2026
-> **الحالة: مكتمل ✅**
+**تاريخ التقرير:** 15 يونيو 2026
+**المُقيّم:** Senior Penetration Tester / Security Architect
+**النسخة:** v1.0
+**التصنيف:** خاص وسري
---
-## 1. الملفات التي تمت مراجعتها وتدقيقها
+## 📊 ملخص التقرير
-### تطبيق siro_admin (Flutter)
-
-| الملف | الحالة |
-|-------|--------|
-| `siro_admin/lib/views/auth/login_page.dart` | ✅ صحيح |
-| `siro_admin/lib/views/auth/register_page.dart` | ✅ صحيح |
-| `siro_admin/lib/controller/auth/otp_helper.dart` | ✅ صحيح |
-| `siro_admin/lib/controller/auth/register_controller.dart` | ✅ صحيح |
-
-### تطبيق siro_service (Flutter)
-
-| الملف | الحالة |
-|-------|--------|
-| `siro_service/lib/controller/login_controller.dart` | ✅ صحيح |
-
-### الباك إند (PHP)
-
-| الملف | الحالة |
-|-------|--------|
-| `backend/Admin/auth/login.php` | ✅ مؤمَّن بالكامل |
-| `backend/Admin/auth/verify_login.php` | ✅ مؤمَّن بالكامل |
-| `backend/Admin/auth/register.php` | ✅ مؤمَّن بالكامل |
-| `backend/serviceapp/login.php` | ✅ مؤمَّن بالكامل |
-| `backend/serviceapp/register.php` | ✅ مؤمَّن بالكامل |
-| `backend/Admin/Staff/add.php` | ✅ مؤمَّن بالكامل |
-| `backend/Admin/Staff/activate.php` | ✅ مؤمَّن بالكامل |
-| `backend/Admin/Staff/pending.php` | ✅ صحيح (قراءة فقط) |
-| `backend/Admin/jwtService.php` | ✅ مؤمَّن بالكامل (إزالة الدعم للـ plain text) |
-| `backend/core/Auth/JwtService.php` | ✅ صحيح (الخدمة الأساسية) |
-| `backend/core/Auth/RateLimiter.php` | ✅ صحيح |
+| البيان | القيمة |
+|--------|--------|
+| **إجمالي الثغرات المكتشفة** | 17 |
+| **🔴 حرجة (Critical)** | 5 |
+| **🟠 عالية (High)** | 6 |
+| **🟡 متوسطة (Medium)** | 4 |
+| **🟢 منخفضة (Low)** | 2 |
+| **درجة الأمان الإجمالية** | **2.5 / 10** 🔴 |
+| **نقاط القوة** | استخدام Prepared Statements في معظم الاستعلامات، وجود Rate Limiting، استخدام JWT مع JTI |
+| **نقاط الضعف الرئيسية** | حقن SQL في `functions.php`، مفاتيح مشفرة Hardcoded، IV ثابت في AES-CBC، كود Debug معروض |
---
-## 2. تدفق البيانات — هل هو صحيح؟
-
-### 📱 من التطبيق إلى الباك إند
-
-| الطبقة | هل الاستقبال صحيح؟ | ماذا يستقبل؟ |
-|--------|-------------------|---------------|
-| **Flutter → filterRequest()** | ✅ | جميع الحوادث (name, phone, password, email, fingerprint) تستخدم `filterRequest()` الذي ينظف البيانات من SQL Injection و XSS |
-| **تشفير PII قبل التخزين** | ✅ | الاسم والهاتف والإيميل والبصمة يتم تشفيرها عبر `encryptData()` |
-| **Fingerprint Hash** | ✅ | SHA-256 للبصمة للبحث السريع دون تخزين البصمة كما هي |
-| **Password** | ✅ | `password_hash(PASSWORD_DEFAULT)` مع `password_verify()` |
-| **Flash Messages** | ✅ | كل الاستجابات عبر `jsonSuccess()` و `jsonError()` الموحدة |
-| **JWT** | ✅ | Firebase JWT مع HS256 وجميع الـ Claims (iss, aud, user_id, role, jti, fingerprint) |
-
-### 🔄 البيانات المرسلة إلى التطبيق
-
-| المعلومة | هل التسريب صحيح؟ |
-|----------|------------------|
-| رقم الهاتف | ✅ يُقنّع (`07XX****XXX`) في استجابة OTP |
-| JWT | ✅ يُرسل بشكل آمن ويُخزَّن في `flutter_secure_storage` (siro_service) أو `GetStorage` (siro_admin) |
-| كلمة المرور | ❌ لا تُرسل أبداً في الاستجابة |
-| الاسم | ✅ يُرسل بعد فك التشفير للعرض فقط |
-| الـ JTI | ✅ يُدار في Redis لمنع إعادة الاستخدام |
+## 🔴 الثغرات الحرجة (Critical)
---
-## 3. التقييم الأمني لكل مسار
+### [C-01] — حقن SQL في دالة `findBestDrivers`
-### مسار المشرف — siro_admin
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | SQL Injection |
+| **الخطورة** | 🔴 حرجة |
+| **درجة CVSS** | 9.8/10 |
+| **CWE** | CWE-89 |
+| **الملف المتأثر** | `backend/functions.php` |
+| **السطر المتأثر** | 98-128 |
-```
-تسجيل ← register.php
- ├── ✅ Whitelist (AUTHORIZED_ADMIN_PHONES)
- ├── ✅ التحقق من التكرار (phone, fp_hash)
- ├── ✅ UUID آمن (bin2hex(random_bytes(16))) ← تم الإصلاح
- ├── ✅ تشفير كامل (name, phone, fingerprint)
- ├── ✅ password_hash
- └── ✅ status = pending
+**📌 وصف الثغرة:**
+يتم استخدام متغير `$carType` مباشرة في استعلام SQL بدون Prepared Statement أو تعقيم. على الرغم من وجود `switch` يحدد القيم، إلا أن القيمة الافتراضية في `default` والأهم أن `$carType` يتم تمريرها من الـ Request مباشرة عبر `add_ride.php` بدون فلترة قبل الوصول إلى هذه الدالة. المهاجم يمكنه تجاوز switch statement عبر إضافة parameters في الـ POST.
-تسجيل دخول ← login.php
- ├── ✅ Rate Limiting (5/دقيقة) ← تم الإصلاح
- ├── ✅ بحث بالبصمة → الهاتف
- ├── ✅ فحص الحالة (pending/suspended/rejected/active)
- ├── ✅ password_verify
- ├── ✅ is_renewal=1 → JWT مباشر + إلغاء القديم
- └── ✅ OTP 6-digits ← تم الإصلاح
- └── verify_login.php
- ├── ✅ Rate Limiting (3/5 دقائق) ← تم الإصلاح
- ├── ✅ OTP مشفر في DB
- ├── ✅ صلاحية 10 دقائق
- └── ✅ استخدام لمرة واحدة (حذف بعد التحقق)
-
-إضافة موظف ← add.php
- ├── ✅ JWT Authentication ← تم الإصلاح
- ├── ✅ role check (super_admin || admin) ← تم الإصلاح
- ├── ✅ UUID آمن
- └── ✅ تشفير البيانات
-
-تفعيل حساب ← activate.php
- ├── ✅ JWT Authentication ← تم الإصلاح
- ├── ✅ role check (super_admin || admin) ← تم الإصلاح
- └── ✅ تحديث status فقط للحسابات المعلقة
+**🔍 الكود الضعيف:**
+```php
+$carType = trim($carType);
+switch ($carType) {
+ case 'Comfort':
+ $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 2017 ";
+ break;
+ case 'Lady':
+ $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
+ $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND d.gender = '$femaleHash' ";
+ break;
+ // ... باقي الحالات
+ default:
+ $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 2000 ";
+ break;
+}
```
-### مسار خدمة العملاء — siro_service
-
+**💥 Proof of Concept (PoC):**
```
-تسجيل ← register.php (أو add.php)
- ├── ✅ Whitelist (AUTHORIZED_SERVICE_PHONES)
- ├── ✅ التحقق من التكرار
- ├── ✅ تشفير كامل
- ├── ✅ password_hash
- └── ✅ status = pending
+POST /siro/ride/rides/add_ride.php HTTP/1.1
+Host: api.siromove.com
+Content-Type: application/x-www-form-urlencoded
+Authorization: Bearer
-تسجيل دخول ← serviceapp/login.php
- ├── ✅ Rate Limiting (5/دقيقة) ← تم الإصلاح
- ├── ✅ بحث بالبصمة → الإيميل
- ├── ✅ فحص الحالة (pending/suspended/approved)
- ├── ✅ password_verify
- ├── ✅ إعادة استخدام التوكن الحالي من Redis
- ├── ✅ إلغاء التوكن القديم وتوليد جديد
- ├── ✅ HMAC Key للمصادقة الثنائية بين التطبيق والخادم
- └── ✅ OTP كخطوة تأكيد عبر /auth/otp/verify.php
+passenger_id=123&start_location=35.0,35.0&end_location=36.0,36.0&price=10&price_token=&carType=Comfort' OR '1'='1' -- -&start_name=test&end_name=test&distance=10&duration_text=5min
+```
+
+هذا سيجعل الـ SQL كالتالي:
+```sql
+AND cr.vehicle_category_id = 1 AND CAST(TRIM(cr.year) AS UNSIGNED) > 2017
+OR '1'='1' -- -
+```
+
+**🎯 تأثير الاستغلال:**
+- تسريب جميع بيانات السائقين (tokens, IDs, locations, genders)
+- استغلال الثغرة لتعديل أو حذف بيانات
+- اختراق كامل لقاعدة البيانات
+
+**✅ الحل المقترح:**
+```php
+$carType = trim($carType);
+$allowedTypes = ['Comfort', 'Mishwar Vip', 'Scooter', 'Pink Bike', 'Electric', 'Lady', 'Van', 'Awfar Car', 'Fixed Price', 'Speed', 'Rayeh Gai'];
+
+if (!in_array($carType, $allowedTypes, true)) {
+ $carType = 'Speed'; // default آمن
+}
+
+// أو استخدام switch مع prepared statement
+switch ($carType) {
+ case 'Lady':
+ $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
+ $sql .= " AND cr.vehicle_category_id = ? AND d.gender = ? ";
+ $params[] = $CAT_CAR;
+ $params[] = $femaleHash;
+ break;
+ // ... باقي الحالات مع bind parameters
+}
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-community/attacks/SQL_Injection
+- CWE: https://cwe.mitre.org/data/definitions/89.html
+
+---
+
+### [C-02] — Hardcoded Internal Key Paths مع Exposure كامل للنظام
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Security Misconfiguration / Hardcoded Secrets |
+| **الخطورة** | 🔴 حرجة |
+| **درجة CVSS** | 9.3/10 |
+| **CWE** | CWE-798, CWE-312 |
+| **الملف المتأثر** | جميع الملفات التالية |
+| **السطر المتأثر** | متعدد |
+
+**📌 وصف الثغرة:**
+المفاتيح الداخلية ومساراتها ثابتة في الكود المصدر (Hardcoded). هذا يعني أن أي شخص لديه access للكود يمكنه الوصول للمفاتيح. بالإضافة لذلك، المسار `/home/siro-api/.internal_socket_key` و `/home/siro-api/.secret_key` و `/home/siro-api/.enckey` كلها ثابتة ومكتشفة. لو تم اختراق السيرفر بأي طريقة، كل المفاتيح مكشوفة.
+
+**🔍 الكود الضعيف:**
+```php
+// في files.php:
+$INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+
+// في JwtService.php:
+$this->secretKey = trim(file_get_contents('/home/siro-api/.secret_key'));
+
+// في encrypt_decrypt.php:
+$key = trim(file_get_contents('/home/siro-api/.enckey'));
+```
+
+**💥 Proof of Concept (PoC):**
+```bash
+# إذا تم اختراق السيرفر بأي طريقة (مثل RFI, LFI, SSRF)
+curl http://target-server/home/siro-api/.secret_key
+curl http://target-server/home/siro-api/.enckey
+
+# أو عبر path traversal في upload
+POST /siro/uploadImagePortrate.php HTTP/1.1
+# ... محاولة قراءة المفاتيح من المجلد
+```
+
+**🎯 تأثير الاستغلال:**
+- توقيع JWT tokens مزيفة
+- فك تشفير جميع البيانات المشفرة في قاعدة البيانات
+- التصيد كأي مستخدم في النظام
+- الدخول للسيرفرات الداخلية
+
+**✅ الحل المقترح:**
+```php
+// استخدام getenv بدلاً من file_get_contents
+$this->secretKey = getenv('JWT_SECRET_KEY') ?: '';
+
+// مع تخزين المفتاح في .env فقط وليس في الكود
+// وتأكد من أن ملف .env خارج الـ DocumentRoot
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure
+- CWE: https://cwe.mitre.org/data/definitions/798.html
+
+---
+
+### [C-03] — ثغرة في `upload_audio.php` - رفع ملفات بدون مصادقة
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Unrestricted File Upload / Missing Authentication |
+| **الخطورة** | 🔴 حرجة |
+| **درجة CVSS** | 9.0/10 |
+| **CWE** | CWE-434, CWE-306 |
+| **الملف المتأثر** | `backend/upload_audio.php` |
+| **السطر المتأثر** | 1-68 (الكامل) |
+
+**📌 وصف الثغرة:**
+ملف `upload_audio.php` لا يقوم بأي تحقق من JWT أو أي Authentication. لا يوجد Rate Limiting. كما أن اسم الملف النهائي يتم بناؤه من اسم الملف الأصلي بدون تعقيم كافٍ. الامتداد لا يتم التحقق منه أمنياً.
+
+**🔍 الكود الضعيف:**
+```php
+$audio_name = $audio_file['name'];
+$audio_extension = pathinfo($audio_name, PATHINFO_EXTENSION);
+
+// التحقق من الامتداد فقط - بدون التحقق الحقيقي
+if (!in_array($audio_extension, array('m4a', 'mp3', 'wav'))) {
+ echo json_encode(array('status' => 'The audio file is not a valid format.'));
+ exit;
+}
+
+// اسم الملف النهائي: الاسم الأصلي + الامتداد
+$new_filename = $audio_name . '.' . $audio_extension;
+$target_file = $target_dir . $new_filename;
+```
+
+**💥 Proof of Concept (PoC):**
+```bash
+# رفع ملف PHP
+curl -X POST https://api.siromove.com/siro/upload_audio.php \
+ -F "audio=@shell.php;filename=shell.php" \
+ -F "passengerId=1"
+
+# أو path traversal في اسم الملف
+curl -X POST https://api.siromove.com/siro/upload_audio.php \
+ -F "audio=@test.php;filename=../../shell.php"
+```
+
+**🎯 تأثير الاستغلال:**
+- رفع Web Shell والتحكم الكامل بالسيرفر
+- تنفيذ أوامر عشوائية
+- الوصول لقاعدة البيانات وجميع المفاتيح
+
+**✅ الحل المقترح:**
+```php
+// إضافة Authentication
+require_once __DIR__ . '/connect.php';
+
+// إضافة Rate Limiting
+$limiter = new RateLimiter($redis);
+$limiter->enforce(RateLimiter::identifier(), 'upload');
+
+// استخدام أسماء ملفات عشوائية
+$new_filename = bin2hex(random_bytes(16)) . '.' . $audio_extension;
+
+// تحقق إضافي من MIME type باستخدام finfo
+$finfo = new finfo(FILEINFO_MIME_TYPE);
+$mimeType = $finfo->file($audio_file['tmp_name']);
+$allowedMimes = ['audio/mp4', 'audio/mpeg', 'audio/wav', 'audio/x-m4a', 'audio/ogg'];
+if (!in_array($mimeType, $allowedMimes, true)) {
+ exit(json_encode(['status' => 'Invalid file type']));
+}
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
+- CWE: https://cwe.mitre.org/data/definitions/434.html
+
+---
+
+### [C-04] — Information Disclosure عبر Debug Mode وملفات Logs
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Information Disclosure / Security Misconfiguration |
+| **الخطورة** | 🔴 حرجة |
+| **درجة CVSS** | 8.6/10 |
+| **CWE** | CWE-200, CWE-532 |
+| **الملف المتأثر** | `backend/core/bootstrap.php` |
+| **السطر المتأثر** | 11-19 |
+
+**📌 وصف الثغرة:**
+في ملف `bootstrap.php`، الـ debug mode مضبوط على `true` في الإنتاج. هذا يعرض أخطاء PHP التفصيلية للمستخدمين، والتي قد تحتوي على مسارات الملفات، أسماء قواعد البيانات، وهيكلية الكود.
+
+**🔍 الكود الضعيف:**
+```php
+$debugMode = true; // ← يجب أن تكون false في الإنتاج!
+
+if ($debugMode) {
+ error_reporting(E_ALL);
+ ini_set('display_errors', '1');
+} else {
+ error_reporting(0);
+ ini_set('display_errors', '0');
+}
+```
+
+**🎯 تأثير الاستغلال:**
+- تسريب هيكلية قاعدة البيانات
+- تسريب مسارات الملفات الكاملة
+- تسريب معلومات الـ DB credentials في حال حدوث خطأ
+- تسهيل هجمات أكثر تقدماً
+
+**✅ الحل المقترح:**
+```php
+// استخدام متغير بيئة لتحديد الـ debug mode
+$debugMode = getenv('APP_DEBUG') === 'true';
+
+if ($debugMode) {
+ error_reporting(E_ALL);
+ ini_set('display_errors', '1');
+} else {
+ error_reporting(0);
+ ini_set('display_errors', '0');
+ ini_set('log_errors', '1');
+}
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-project-top-ten/2017/A6_2017-Security_Misconfiguration
+- CWE: https://cwe.mitre.org/data/definitions/200.html
+
+---
+
+### [C-05] — ثغرة في `encrypt_decrypt.php` - IV ثابت مع AES-256-CBC
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Cryptographic Vulnerability |
+| **الخطورة** | 🔴 حرجة |
+| **درجة CVSS** | 8.0/10 |
+| **CWE** | CWE-329, CWE-330 |
+| **الملف المتأثر** | `backend/encrypt_decrypt.php` |
+| **السطر المتأثر** | 10-11, 44, 56, 73-92 |
+
+**📌 وصف الثغرة:**
+يستخدم الكود IV (Initialization Vector) ثابت لجميع عمليات التشفير في نمط CBC. هذا يضعف التشفير بشكل كبير ويجعله عرضة لهجمات مثل Chosen Plaintext Attack (CPA). كما أن المفتاح يتم جلبه من ملف خارج الـ DocumentRoot ولكن اسم الملف ثابت معروف.
+
+**🔍 الكود الضعيف:**
+```php
+$key = trim(file_get_contents('/home/siro-api/.enckey'));
+$iv = getenv('initializationVector'); // 16 bytes — IV ثابت
+
+public function encryptData($plainText) {
+ $plainText = mb_convert_encoding($plainText, 'UTF-8');
+ $paddedText = $this->addPadding($plainText);
+ $encrypted = openssl_encrypt($paddedText, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
+ return base64_encode($encrypted);
+}
+```
+
+**🎯 تأثير الاستغلال:**
+- فك تشفير البيانات إذا تم الحصول على زوج plaintext/ciphertext
+- هجمات الـ ciphertext manipulation
+- إعادة استخدام IV يسمح بكشف الأنماط في البيانات المشفرة
+
+**✅ الحل المقترح:**
+```php
+// استخدام AES-256-GCM بدلاً من CBC (موجود بالفعل في EncryptionHelper.php الجديد)
+// تأكد من أن جميع التطبيقات تستخدم EncryptionHelper الجديد
+
+public function encryptData(string $plainText): string
+{
+ $iv = random_bytes(12); // IV جديد لكل عملية تشفير
+ $tag = '';
+ $encrypted = openssl_encrypt($plainText, 'aes-256-gcm', $this->key, OPENSSL_RAW_DATA, $iv, $tag);
+ return base64_encode($iv . $tag . $encrypted);
+}
+```
+
+**📚 المراجع:**
+- OWASP: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
+- CWE: https://cwe.mitre.org/data/definitions/329.html
+
+---
+
+## 🟠 الثغرات عالية الخطورة (High)
+
+---
+
+### [H-01] — SSRF (Server-Side Request Forgery)
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | SSRF |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.5/10 |
+| **CWE** | CWE-918 |
+| **الملف المتأثر** | `backend/functions.php` |
+| **السطر المتأثر** | 23-42, 44-157, 159-211, 217-253, 261-307, 334-355 |
+
+**📌 وصف الثغرة:**
+يتم استخدام `curl` مع عناوين IP ثابتة داخلية `http://188.68.36.205:2021` و `http://188.68.36.205:3031`. هذا يسمح بإعادة توجيه الهجمات إلى السيرفرات الداخلية. في حال تم اختراق أي endpoint يمكن إعادة توجيه الـ requests لفحص الشبكة الداخلية.
+
+**🔍 الكود الضعيف:**
+```php
+$url = "http://188.68.36.205:2021";
+$postData = ['action' => $action, ...$data];
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, $url);
+curl_setopt($ch, CURLOPT_POST, 1);
+curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
+curl_exec($ch);
+```
+
+**🎯 تأثير الاستغلال:**
+- فحص ports داخلية
+- مهاجمة خدمات داخلية (Redis, MySQL, etc.)
+- تجاوز الجدران النارية
+
+**✅ الحل المقترح:**
+```php
+// إضافة allowlist لعناوين URL
+$allowedUrls = ['http://188.68.36.205:2021', 'http://188.68.36.205:3031'];
+if (!in_array($url, $allowedUrls, true)) {
+ error_log("[SSRF_BLOCKED] Attempted connection to: $url");
+ return false;
+}
+
+// استخدام DNS بدلاً من IP مباشر
+// وإضافة firewall rules لتقييد الوصول
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
+- CWE: https://cwe.mitre.org/data/definitions/918.html
+
+---
+
+### [H-02] — HMAC Debug Information Disclosure في JwtService
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Information Disclosure |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.5/10 |
+| **CWE** | CWE-209 |
+| **الملف المتأثر** | `backend/core/Auth/JwtService.php` |
+| **السطر المتأثر** | 268-271 |
+
+**📌 وصف الثغرة:**
+عند فشل التحقق من HMAC، يتم إرجاع معلومات حساسة جداً للمستخدم مثل الـ derived secret والمفاتيح (ولو بشكل جزئي). هذا يسمح للمهاجم بجمع معلومات عن نظام التوقيع.
+
+**🔍 الكود الضعيف:**
+```php
+if (!hash_equals($expectedHmac, $hmacHeader)) {
+ $debugMsg = "User: $userId | Expected: $expectedHmac | Got: $hmacHeader | DerivedSecret: $userSecret | MasterSecret(4): " . substr($this->hmacSecret, 0, 4) . "...";
+ http_response_code(403);
+ echo json_encode(['error' => 'HMAC_DEBUG', 'debug' => $debugMsg]);
+ exit;
+}
+```
+
+**💥 Proof of Concept (PoC):**
+```
+POST /any-endpoint HTTP/1.1
+X-HMAC-Auth: invalid_hmac
+X-Timestamp: 123456
+X-Nonce: test
+
+Response:
+{"error":"HMAC_DEBUG","debug":"User: ... | MasterSecret(4): S3cr..."}
+```
+
+**🎯 تأثير الاستغلال:**
+- تسريب أول 4 أحرف من المفتاح الرئيسي (Master Secret)
+- تسريب الـ Derived Secret
+- تسهيل هجمات Brute Force على HMAC secret
+
+**✅ الحل المقترح:**
+```php
+if (!hash_equals($expectedHmac, $hmacHeader)) {
+ error_log("[SECURITY] HMAC mismatch | User: $userId");
+ http_response_code(403);
+ echo json_encode(['error' => 'Request verification failed']);
+ exit;
+}
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-community/attacks/Information_disclosure
+- CWE: https://cwe.mitre.org/data/definitions/209.html
+
+---
+
+### [H-03] — CORS Misconfiguration في Admin Login
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | CORS Misconfiguration |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.3/10 |
+| **CWE** | CWE-942 |
+| **الملف المتأثر** | `backend/loginAdmin.php` |
+| **السطر المتأثر** | 9 |
+
+**📌 وصف الثغرة:**
+يسمح Admin login بأي Origin عبر `*` fallback. هذا يسمح لأي موقع ضابط بعمل Cross-Origin requests وإرسال بيانات المصادقة.
+
+**🔍 الكود الضعيف:**
+```php
+header("Access-Control-Allow-Origin: " . (getenv('ALLOWED_ORIGIN') ?: '*'));
+```
+
+**🎯 تأثير الاستغلال:**
+- هجمات CSRF
+- تسريب بيانات المصادقة من تطبيقات ضارة
+- استهداف المسؤولين عبر Social Engineering
+
+**✅ الحل المقترح:**
+```php
+$allowedOrigin = getenv('ALLOWED_ORIGIN') ?: 'https://admin.siromove.com';
+header("Access-Control-Allow-Origin: " . $allowedOrigin);
+header("Access-Control-Allow-Credentials: true");
+```
+
+**📚 المراجع:**
+- OWASP: https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny
+- CWE: https://cwe.mitre.org/data/definitions/942.html
+
+---
+
+### [H-04] — Weak Encryption في Flutter (IV ثابت مع AES-CBC)
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Cryptographic Vulnerability |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.4/10 |
+| **CWE** | CWE-329 |
+| **الملف المتأثر** | `siro_rider/lib/controller/functions/encrypt_decrypt.dart` |
+| **السطر المتأثر** | 43-53 |
+
+**📌 وصف الثغرة:**
+يستخدم Flutter AES-CBC مع IV ثابت يتم جلبه من الـ Env في التطبيق. هذا يسمح بفك التشفير إذا تم الحصول على الـ key (والذي هو أيضاً في الـ Env).
+
+**🔍 الكود الضعيف:**
+```dart
+final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
+final encrypted = encrypter.encrypt(plainText, iv: iv);
+return encrypted.base64;
+```
+
+**🎯 تأثير الاستغلال:**
+- فك تشفير البيانات إذا تم الوصول للتطبيق (Root/Jailbreak)
+- هجمات الـ Chosen Plaintext
+- كشف الأنماط في البيانات
+
+**✅ الحل المقترح:**
+```dart
+// استخدام GCM mode بدلاً من CBC مع IV عشوائي
+import 'dart:math';
+final iv = encrypt.IV.fromLength(12); // GCM needs 12 bytes IV
+final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
+final encrypted = encrypter.encrypt(plainText, iv: iv);
+```
+
+**📚 المراجع:**
+- OWASP MASTG: https://mas.owasp.org/MASTG/iOS/0x06d-Testing-Data-Storage/
+- CWE: https://cwe.mitre.org/data/definitions/329.html
+
+---
+
+### [H-05] — Server-Side Pricing Logic يمكن التحايل عليه
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Business Logic Vulnerability |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.2/10 |
+| **CWE** | CWE-841 |
+| **الملف المتأثر** | `backend/ride/rides/add_ride.php` |
+| **السطر المتأثر** | 57-68, 96-144 |
+
+**📌 وصف الثغرة:**
+على الرغم من وجود `price_token` للتوقيع على الأسعار، إلا أن بعض الحقول الأخرى مثل `distance` و `duration_text` لا يتم التحقق منها في الـ token، مما يسمح بالتلاعب ببيانات الرحلة.
+
+**🔍 الكود الضعيف:**
+```php
+$distance = filterRequest("distance");
+// ...
+$payload[11] = (string) $distance;
+$payload[15] = (string) $duration_text;
+```
+
+**🎯 تأثير الاستغلال:**
+- التلاعب بمسافة الرحلة لعرض سعر أقل
+- التلاعب بمدة الرحلة
+- خداع السائقين ببيانات رحلة مغايرة للواقع
+
+**✅ الحل المقترح:**
+```php
+// تضمين distance و duration في الـ price_token أيضاً
+if ($tokenData['distance'] != $distance || $tokenData['duration'] != $duration_text) {
+ printFailure("Tampered ride data");
+ exit;
+}
```
---
-## 4. الملخص النهائي
+### [H-06] — Predictable Token Generation في `loginFirstTimeDriver.php`
-| البند | النتيجة |
-|-------|---------|
-| **عدد الملفات المدققة** | 19 ملفاً |
-| **الثغرات المكتشفة** | 8 |
-| **الثغرات المُصلحة** | 8 ✅ (100%) |
-| **الثغرات المتبقية** | 0 |
-| **صلاحية الوصول (Authorization)** | مضمونة لـ add.php و activate.php |
-| **سلامة البيانات (Encryption)** | مضمونة — PII مشفر في DB |
-| **الحماية من Brute Force** | مضمونة — Rate Limiting على كل نقاط الدخول |
-| **إدارة الجلسات (JWT)** | مضمونة — مع Revocation عبر Redis |
-| **المصادقة متعددة العوامل** | مضمونة — Fingerprint + Password + OTP |
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Weak Authentication |
+| **الخطورة** | 🟠 عالية |
+| **درجة CVSS** | 7.0/10 |
+| **CWE** | CWE-307, CWE-798 |
+| **الملف المتأثر** | `backend/loginFirstTimeDriver.php` |
+| **السطر المتأثر** | 40-48 |
-### ✅ الخلاصة
+**📌 وصف الثغرة:**
+يتم استخدام كلمة مرور ثابتة `passwordnewpassenger` من متغيرات البيئة لكل السائقين الجدد. هذا يسمح لأي شخص يعرف هذه القيمة بتوليد توكن تسجيل صالح.
-النظام الآن مؤمَّن بالكامل في جميع مسارات المصادقة:
+**🔍 الكود الضعيف:**
+```php
+$passwordnewpassenger = getenv('passwordnewpassenger');
-1. **من التطبيق إلى الباك إند**: البيانات تصل عبر `filterRequest()` ومنظّفة من الاختراقات
-2. **في الباك إند**: كل عمليات التحقق تتم بشكل آمن (password_hash, تشفير البيانات, Rate Limiting, JWT)
-3. **من الباك إند إلى التطبيق**: البيانات ترسل بشكل آمن (JWT, HMAC, phone masked)
-4. **الصلاحيات**: لا يمكن لأي مستخدم غير مصرح له إضافة موظفين أو تفعيل حسابات
-5. **التوكنات**: يتم إلغاء التوكن القديم قبل إصدار جديد + Blacklist عبر Redis
+if (!password_verify($password, $passwordnewpassenger)) {
+ jsonError('Invalid credentials.', 401);
+}
+```
-
\ No newline at end of file
+**🎯 تأثير الاستغلال:**
+- إنشاء توكن تسجيل وهمي لأي سائق
+- بدء عملية تسجيل لحسابات متعددة
+- استنزاف موارد الـ OTP والإيميل
+
+**✅ الحل المقترح:**
+```php
+// استخدام كلمة مرور فريدة لكل طلب تسجيل (One-Time Token)
+$registrationToken = bin2hex(random_bytes(32));
+// تخزينها في Redis مع وقت صلاحية وربطها بمعرّف السائق
+$redis->setex("reg_token:{$id}", 600, $registrationToken);
+
+// التحقق من الـ token
+if (!hash_equals($redis->get("reg_token:{$id}"), $password)) {
+ jsonError('Invalid registration token', 401);
+}
+```
+
+---
+
+## 🟡 الثغرات المتوسطة (Medium)
+
+---
+
+### [M-01] — ملفات Logs بصلاحيات 777
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Security Misconfiguration |
+| **الخطورة** | 🟡 متوسطة |
+| **درجة CVSS** | 6.5/10 |
+| **CWE** | CWE-276 |
+| **الملف المتأثر** | `backend/core/helpers.php` |
+| **السطر المتأثر** | 158-159 |
+
+**📌 وصف الثغرة:**
+يتم إنشاء مجلد logs بصلاحيات 777 (قابلة للقراءة والكتابة والتنفيذ للجميع). هذا يسمح لأي مستخدم على السيرفر بقراءة وتعديل ملفات الـ logs.
+
+**🔍 الكود الضعيف:**
+```php
+@mkdir($logDir, 0777, true);
+```
+
+**🎯 تأثير الاستغلال:**
+- قراءة معلومات حساسة من الـ logs (User IDs, Tokens, IPs)
+- تعديل الـ logs لإخفاء آثار الاختراق
+- حذف الـ logs
+
+**✅ الحل المقترح:**
+```php
+@mkdir($logDir, 0750, true); // Owner: rwx, Group: rx, Others: ---
+```
+
+**📚 المراجع:**
+- CWE: https://cwe.mitre.org/data/definitions/276.html
+
+---
+
+### [M-02] — SQL Injection محتمل في `findBestDrivers` (Lady Case)
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | SQL Injection (Potential) |
+| **الخطورة** | 🟡 متوسطة |
+| **درجة CVSS** | 6.0/10 |
+| **CWE** | CWE-89 |
+| **الملف المتأثر** | `backend/functions.php` |
+| **السطر المتأثر** | 113-114 |
+
+**📌 وصف الثغرة:**
+في حالة Lady، يتم إدراج `$femaleHash` مباشرة في الـ SQL. هذه القيمة مشفرة SHA-256 ولكنها لا تزال تُستخدم بدون Prepared Statement.
+
+**🔍 الكود الضعيف:**
+```php
+case 'Lady':
+ $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
+ $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND d.gender = '$femaleHash' ";
+ break;
+```
+
+**✅ الحل المقترح:**
+```php
+case 'Lady':
+ $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
+ $sql .= " AND cr.vehicle_category_id = ? AND d.gender = ? ";
+ $params[] = $CAT_CAR;
+ $params[] = $femaleHash;
+ break;
+```
+
+---
+
+### [M-03] — عدم التحقق من SSL في cURL (Man-in-the-Middle)
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Insufficient Transport Layer Security |
+| **الخطورة** | 🟡 متوسطة |
+| **درجة CVSS** | 5.9/10 |
+| **CWE** | CWE-295 |
+| **الملف المتأثر** | `backend/functions.php` |
+| **السطر المتأثر** | 33-42, 51-61, 170-180, 193-210, 232-252, 342-354 |
+
+**📌 وصف الثغرة:**
+جميع استدعاءات cURL الداخلية إلى `http://188.68.36.205:2021` لا تستخدم SSL. هذا يسمح لأي شخص في نفس الشبكة باعتراض الاتصالات.
+
+**✅ الحل المقترح:**
+```php
+// استخدام HTTPS بدلاً من HTTP
+$url = "https://188.68.36.205:2021";
+
+// مع التحقق من SSL certificate
+curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
+```
+
+---
+
+### [M-04] — عدم وجود فحص للـ Token Expiry في `loginJwtDriver.php`
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Business Logic |
+| **الخطورة** | 🟡 متوسطة |
+| **درجة CVSS** | 5.5/10 |
+| **CWE** | CWE-613 |
+| **الملف المتأثر** | `backend/loginJwtDriver.php` |
+| **السطر المتأثر** | 63-68 |
+
+**📌 وصف الثغرة:**
+عند فك تشفير `decPhone` و `decNat`، إذا فشلت عملية فك التشفير لا يتم تسجيل خطأ مناسب ولا يتم التحقق من وقت صلاحية بيانات الجلسة.
+
+**✅ الحل المقترح:**
+```php
+if (empty($decPhone) || empty($decNat)) {
+ securityLog("LoginDriver failed: decryption returned null", [
+ 'driver_id' => $driver['id'],
+ 'phone_len' => strlen($driver['phone'] ?? 0),
+ 'nat_len' => strlen($driver['national_number'] ?? 0)
+ ]);
+ unauthorizedDriver();
+}
+```
+
+---
+
+## 🟢 الثغرات المنخفضة (Low)
+
+---
+
+### [L-01] — الـ Headers الأمنية غير كافية
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Security Misconfiguration |
+| **الخطورة** | 🟢 منخفضة |
+| **درجة CVSS** | 3.5/10 |
+| **CWE** | CWE-693 |
+| **الملف المتأثر** | `backend/core/bootstrap.php` |
+| **السطر المتأثر** | 29-36 |
+
+**📌 وصف الثغرة:**
+بعض الـ Security Headers الأساسية مفقودة أو غير كافية:
+- `Content-Security-Policy` غير موجود
+- `Referrer-Policy` غير موجود
+- `Permissions-Policy` غير موجود
+- `X-XSS-Protection` غير موجود (للمتصفحات القديمة)
+
+**✅ الحل المقترح:**
+```php
+header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'");
+header("Referrer-Policy: strict-origin-when-cross-origin");
+header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
+header("X-XSS-Protection: 1; mode=block");
+```
+
+---
+
+### [L-02] — Email Header Injection في `helpers.php`
+
+| المعلومة | التفصيل |
+|---------|---------|
+| **الفئة** | Email Header Injection |
+| **الخطورة** | 🟢 منخفضة |
+| **درجة CVSS** | 3.1/10 |
+| **CWE** | CWE-93 |
+| **الملف المتأثر** | `backend/core/helpers.php` |
+| **السطر المتأثر** | 77-81 |
+
+**🔍 الكود الضعيف:**
+```php
+function sendEmail(string $from, string $to, string $title, string $body): void
+{
+ $header = "From: $from\nCC: $from";
+ mail($to, $title, $body, $header);
+}
+```
+
+**✅ الحل المقترح:**
+```php
+function sendEmail(string $from, string $to, string $title, string $body): void
+{
+ // تعقيم المدخلات لمنع injection
+ $from = str_replace(["\r", "\n", "\r\n"], '', $from);
+ $to = str_replace(["\r", "\n", "\r\n"], '', $to);
+ $title = str_replace(["\r", "\n", "\r\n"], '', $title);
+
+ $header = "From: $from\r\n";
+ $header .= "Reply-To: $from\r\n";
+ $header .= "MIME-Version: 1.0\r\n";
+ $header .= "Content-Type: text/html; charset=UTF-8\r\n";
+
+ mail($to, $title, $body, $header);
+}
+```
+
+---
+
+## 📊 جدول ملخص الثغرات
+
+| # | الثغرة | الملف | الخطورة | CVSS |
+|---|--------|-------|---------|------|
+| C-01 | SQL Injection في findBestDrivers | `functions.php:98-128` | 🔴 حرجة | 9.8 |
+| C-02 | Hardcoded Secrets Paths | `متعدد` | 🔴 حرجة | 9.3 |
+| C-03 | File Upload بدون Auth | `upload_audio.php` | 🔴 حرجة | 9.0 |
+| C-04 | Debug Mode مفعل | `bootstrap.php:11` | 🔴 حرجة | 8.6 |
+| C-05 | IV ثابت في AES-CBC | `encrypt_decrypt.php` | 🔴 حرجة | 8.0 |
+| H-01 | SSRF | `functions.php` | 🟠 عالية | 7.5 |
+| H-02 | HMAC Debug Disclosure | `JwtService.php:268` | 🟠 عالية | 7.5 |
+| H-03 | CORS Misconfiguration | `loginAdmin.php:9` | 🟠 عالية | 7.3 |
+| H-04 | Weak Encryption Flutter | `encrypt_decrypt.dart` | 🟠 عالية | 7.4 |
+| H-05 | Business Logic Pricing | `add_ride.php` | 🟠 عالية | 7.2 |
+| H-06 | Predictable Reg Token | `loginFirstTimeDriver.php` | 🟠 عالية | 7.0 |
+| M-01 | Logs Permissions 777 | `helpers.php:158` | 🟡 متوسطة | 6.5 |
+| M-02 | SQL Injection (Lady case) | `functions.php:114` | 🟡 متوسطة | 6.0 |
+| M-03 | No SSL Verification | `functions.php` | 🟡 متوسطة | 5.9 |
+| M-04 | No Token Expiry Check | `loginJwtDriver.php` | 🟡 متوسطة | 5.5 |
+| L-01 | Missing Security Headers | `bootstrap.php` | 🟢 منخفضة | 3.5 |
+| L-02 | Email Header Injection | `helpers.php:77` | 🟢 منخفضة | 3.1 |
+
+---
+
+## 🎯 خارطة طريق الإصلاح (Remediation Roadmap)
+
+### المرحلة الأولى: فورية (خلال 24-48 ساعة)
+- [ ] **C-01**: إصلاح SQL Injection في `findBestDrivers` باستخدام Prepared Statements
+- [ ] **C-02**: نقل جميع المفاتيح إلى متغيرات البيئة فقط وإزالة الـ file_get_contents من الكود
+- [ ] **C-03**: إضافة JWT Authentication إلى `upload_audio.php` وتعقيم اسم الملف
+- [ ] **C-04**: تعطيل `display_errors` في الإنتاج
+
+### المرحلة الثانية: قصيرة المدى (خلال أسبوع)
+- [ ] **C-05**: الترحيل الكامل إلى AES-256-GCM والتأكد من عدم استخدام IV ثابت
+- [ ] **H-01**: إضافة allowlist لعناوين URL في cURL
+- [ ] **H-02**: إزالة معلومات الـ Debug من استجابات الـ API
+- [ ] **H-03**: تقييد CORS للدومينات المسموحة فقط
+
+### المرحلة الثالثة: متوسطة المدى (خلال شهر)
+- [ ] **H-04**: تحديث Flutter Encryption لاستخدام GCM مع IV عشوائي
+- [ ] **H-05**: تضمين جميع حقول الرحلة في الـ price_token
+- [ ] **H-06**: تطبيق One-Time Registration Tokens
+- [ ] **M-01**: تصحيح صلاحيات مجلد الـ logs
+
+### المرحلة الرابعة: استراتيجية (شهر - 3 أشهر)
+- [ ] **M-02**: تحويل جميع استعلامات SQL إلى Prepared Statements
+- [ ] **M-03**: تفعيل SSL Verification في جميع اتصالات cURL
+- [ ] **M-04**: إضافة Logging مناسب لجميع حالات الفشل
+- [ ] **L-01 + L-02**: إضافة Security Headers وتأمين دالة الإيميل
+
+---
+
+## 🏆 نقاط القوة والنقاط الإيجابية
+
+على الرغم من النتيجة المنخفضة، هناك بعض الممارسات الجيدة في الكود:
+
+1. ✅ **استخدام Prepared Statements** في معظم استعلامات SQL (عدا الـ findBestDrivers)
+2. ✅ **وجود Rate Limiting** عبر Redis
+3. ✅ **JWT مع JTI و Blacklist** لدعم إلغاء التوكنات
+4. ✅ **استخدام `hash_equals`** لمقارنة آمنة (Timing Attack Safe)
+5. ✅ **فصل قواعد البيانات** (Main, Ride, Tracking) لتقليل الضرر
+6. ✅ **تطبيق فحص بصمة الجهاز (Fingerprint)** لمنع سرقة الجلسات
+7. ✅ **استخدام `random_bytes`** لتوليد قيم عشوائية آمنة
+8. ✅ **التحقق من MIME type الحقيقي** في رفع الصور عبر `finfo`
+
+---
+
+## 🔚 الخلاصة
+
+**درجة الأمان الإجمالية: 2.5/10** 🔴
+
+النظام يحتوي على ثغرات حرجة تتطلب تدخلاً فورياً. الخطر الأكبر يكمن في:
+1. إمكانية حقن SQL والوصول لقاعدة البيانات
+2. وجود المفاتيح في مسارات معروفة داخل الكود
+3. إمكانية رفع ملفات خبيثة بدون مصادقة
+4. استخدام IV ثابت في التشفير
+
+**التوصية الفورية:** وقف أي deploy جديد حتى يتم إصلاح الثغرات الحرجة في المرحلة الأولى.
\ No newline at end of file
diff --git a/siro_driver/assets/images/car.png b/siro_driver/assets/images/car.png
index 5932743..93c2e27 100755
Binary files a/siro_driver/assets/images/car.png and b/siro_driver/assets/images/car.png differ
diff --git a/siro_driver/assets/images/lady1.png b/siro_driver/assets/images/lady1.png
index 2797b75..37cc24b 100755
Binary files a/siro_driver/assets/images/lady1.png and b/siro_driver/assets/images/lady1.png differ
diff --git a/siro_rider/assets/images/blob.png b/siro_rider/assets/images/blob.png
index e45e42c..4eb58f9 100644
Binary files a/siro_rider/assets/images/blob.png and b/siro_rider/assets/images/blob.png differ
diff --git a/siro_rider/assets/images/car.png b/siro_rider/assets/images/car.png
index b078ecc..93c2e27 100644
Binary files a/siro_rider/assets/images/car.png and b/siro_rider/assets/images/car.png differ
diff --git a/siro_rider/assets/images/carspeed.png b/siro_rider/assets/images/carspeed.png
index b8b2d37..429a2f4 100644
Binary files a/siro_rider/assets/images/carspeed.png and b/siro_rider/assets/images/carspeed.png differ
diff --git a/siro_rider/assets/images/electric.png b/siro_rider/assets/images/electric.png
index b2889b8..5b8bab4 100644
Binary files a/siro_rider/assets/images/electric.png and b/siro_rider/assets/images/electric.png differ
diff --git a/siro_rider/assets/images/lady.png b/siro_rider/assets/images/lady.png
index 76277fe..8da073f 100644
Binary files a/siro_rider/assets/images/lady.png and b/siro_rider/assets/images/lady.png differ
diff --git a/siro_rider/assets/images/lady1.png b/siro_rider/assets/images/lady1.png
index 2797b75..37cc24b 100644
Binary files a/siro_rider/assets/images/lady1.png and b/siro_rider/assets/images/lady1.png differ
diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
index 53b4abe..34a60ed 100644
--- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
+++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
@@ -142,6 +142,7 @@ class RideLifecycleController extends GetxController {
String totalPassengerRayehGai = '0';
String totalPassengerRayehGaiComfort = '0';
String totalPassengerRayehGaiBalash = '0';
+ String priceToken = '';
double latePrice = 0;
double fuelPrice = 0;
@@ -1455,6 +1456,7 @@ class RideLifecycleController extends GetxController {
"step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "",
"step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
"step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
+ "price_token": priceToken,
};
Log.print(' 📦 Payload: $payload');
@@ -1910,6 +1912,9 @@ class RideLifecycleController extends GetxController {
totalPassengerRayehGaiBalash =
data['totalPassengerRayehGaiBalash']?.toString() ?? '0';
+ // Save price_token from server response
+ priceToken = response['price_token']?.toString() ?? '';
+
totalPassenger = totalPassengerSpeed;
totalCostPassenger = totalPassenger;
}