From ec467b01c6027e2b136b2f4a7667a165456eca23 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Mon, 3 Aug 2026 11:52:03 +0300 Subject: [PATCH] Update: 2026-08-03 11:52:03 --- backend/Admin/rides/get_driver_live_pos.php | 4 ++ backend/Admin/rides/monitorRide.php | 4 ++ backend/auth/driver/drivers_pending_list.php | 5 +- backend/core/Auth/RateLimiter.php | 10 +++- backend/core/Database/Database.php | 10 +++- backend/core/helpers.php | 49 +++++++++++++++++++ backend/encrypt_decrypt.php | 12 ++++- backend/ride/location/getBalash.php | 4 ++ .../getCarsLocationByPassengerVan.php | 4 ++ backend/ride/location/getComfort.php | 4 ++ backend/ride/location/getDelivery.php | 4 ++ backend/ride/location/getDriverTimeOnline.php | 4 ++ backend/ride/location/getElectric.php | 4 ++ backend/ride/location/getFemalDriver.php | 4 ++ backend/ride/location/getLocationParents.php | 4 ++ backend/ride/location/save_behavior.php | 4 ++ backend/ride/rides/get_driver_location.php | 4 ++ backend/ride/rides/public_track_location.php | 4 ++ docker/.env.example | 6 ++- docker/nginx/default.conf | 13 +++++ 20 files changed, 152 insertions(+), 5 deletions(-) diff --git a/backend/Admin/rides/get_driver_live_pos.php b/backend/Admin/rides/get_driver_live_pos.php index 49173dd2..4837a0fd 100644 --- a/backend/Admin/rides/get_driver_live_pos.php +++ b/backend/Admin/rides/get_driver_live_pos.php @@ -6,6 +6,10 @@ require_once __DIR__ . '/../../connect.php'; // تأكد أن هذا الملف يحتوي على $con_tracking +// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ +// "Call to a member function prepare() on null". +$con_tracking = Database::get('tracking'); + header("Content-Type: application/json; charset=UTF-8"); diff --git a/backend/Admin/rides/monitorRide.php b/backend/Admin/rides/monitorRide.php index f50e164f..aface79d 100644 --- a/backend/Admin/rides/monitorRide.php +++ b/backend/Admin/rides/monitorRide.php @@ -1,6 +1,10 @@ 'Unauthorized: Admin access required']); diff --git a/backend/auth/driver/drivers_pending_list.php b/backend/auth/driver/drivers_pending_list.php index 2f443da6..37d32a51 100644 --- a/backend/auth/driver/drivers_pending_list.php +++ b/backend/auth/driver/drivers_pending_list.php @@ -1,5 +1,8 @@ false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_PERSISTENT => true, + // الاتصالات الدائمة أُوقفت: كانت تُنتج "Packets out of order. + // Expected 1 received 0" بتكرار في الإنتاج — وهو عرض معروف لإعادة + // استخدام اتصال دائم تُرك في حالة غير نظيفة، أو أغلقه MySQL عند + // wait_timeout بينما ما زال PHP يعدّه حياً. والأسوأ أن التحذير + // يُطبع قبل الرؤوس فيُفسد الاستجابة بـ "headers already sent". + // + // الكلفة اتصال جديد لكل طلب، وهي مقبولة هنا: MySQL على شبكة دوكر + // نفسها و max-connections=300. الاستقرار أولى من هذا التوفير. + PDO::ATTR_PERSISTENT => false, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci", PDO::ATTR_TIMEOUT => 10, ]; diff --git a/backend/core/helpers.php b/backend/core/helpers.php index b20b2a6e..a1cd0cba 100644 --- a/backend/core/helpers.php +++ b/backend/core/helpers.php @@ -310,3 +310,52 @@ function maskPhone(?string $phone): ?string $tail = substr($d, -2); return $head . str_repeat('*', $len - 5) . $tail; } + +/** + * عنوان العميل الحقيقي خلف الوسطاء. + * + * سلسلة الطلب: العميل ← nginx المضيف ← nginx الحاوية ← php-fpm، فيصل + * REMOTE_ADDR دائماً كعنوان الوسيط. الاعتماد عليه وحده جعل RateLimiter يضع + * كل المستخدمين في دلو واحد، فمحاولات دخول فاشلة من عميل واحد تحجب الجميع. + * + * X-Forwarded-For قابل للتزوير من العميل، لذا لا نثق به إلا إذا كان + * REMOTE_ADDR نفسه وسيطاً موثوقاً (شبكات خاصة/لوكال). خلاف ذلك نعيد + * REMOTE_ADDR كما هو. + */ +function clientIp(): string +{ + $remote = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; + + $isTrustedProxy = $remote !== 'unknown' && ( + filter_var( + $remote, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ) === false + ); + + if (!$isTrustedProxy) { + return $remote; + } + + $forwarded = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ''; + if ($forwarded === '') { + return $remote; + } + + // أول عنوان عام في السلسلة هو العميل؛ نتخطّى العناوين الخاصة التي + // أضافها الوسطاء. + foreach (explode(',', $forwarded) as $candidate) { + $candidate = trim($candidate); + if ($candidate === '') continue; + if (filter_var( + $candidate, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ) !== false) { + return $candidate; + } + } + + return $remote; +} diff --git a/backend/encrypt_decrypt.php b/backend/encrypt_decrypt.php index 6be193a5..895507e0 100644 --- a/backend/encrypt_decrypt.php +++ b/backend/encrypt_decrypt.php @@ -1,7 +1,17 @@ يتصل بقاعدة بيانات التتبع (car_locations) require_once __DIR__ . '/../../connect.php'; +// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ +// "Call to a member function prepare() on null". +$con_tracking = Database::get('tracking'); + try { $driver_id = filterRequest("driver_id"); diff --git a/backend/ride/location/save_behavior.php b/backend/ride/location/save_behavior.php index 2f155657..1456075c 100644 --- a/backend/ride/location/save_behavior.php +++ b/backend/ride/location/save_behavior.php @@ -4,6 +4,10 @@ // $con_tracking -> يتصل بقاعدة بيانات التتبع (driver_behavior, car_locations) require_once __DIR__ . '/../../connect.php'; +// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ +// "Call to a member function prepare() on null". +$con_tracking = Database::get('tracking'); + try { // استلام البيانات من Flutter $driver_id = filterRequest("driver_id"); diff --git a/backend/ride/rides/get_driver_location.php b/backend/ride/rides/get_driver_location.php index 220fc75d..caf63b22 100644 --- a/backend/ride/rides/get_driver_location.php +++ b/backend/ride/rides/get_driver_location.php @@ -3,6 +3,10 @@ // وكائن التشفير $encryptionHelper require_once __DIR__ . '/../../get_connect.php'; +// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ +// "Call to a member function prepare() on null". +$con_tracking = Database::get('tracking'); + // السماح بالوصول من أي دومين (لأن الرابط سيفتح في متصفح العميل) header("Content-Type: application/json; charset=UTF-8"); diff --git a/backend/ride/rides/public_track_location.php b/backend/ride/rides/public_track_location.php index 9f15ff1e..82f8653c 100644 --- a/backend/ride/rides/public_track_location.php +++ b/backend/ride/rides/public_track_location.php @@ -4,6 +4,10 @@ ob_start(); require_once __DIR__ . '/../../get_connect.php'; +// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ +// "Call to a member function prepare() on null". +$con_tracking = Database::get('tracking'); + // تنظيف *جميع* مستويات التخزين المؤقت (Loop) // هذا يضمن التخلص من أي مسافات أو أخطاء ظهرت من ملفات الـ include while (ob_get_level()) { diff --git a/docker/.env.example b/docker/.env.example index 5d7e7e67..627b3f63 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -7,7 +7,11 @@ PHP_VERSION=8.2 HTTP_PORT=8181 APP_DOMAIN=jordan-siro.intaleqapp.com APP_ISSUER=SiroApp -APP_DEBUG=true +# ⚠️ يجب أن تبقى false في الإنتاج. +# bootstrap.php يفعّل display_errors عندما تكون true، فتُطبع تحذيرات PHP +# داخل جسم الاستجابة: تكشف مسارات الملفات للعملاء، وتُفسد الردود بـ +# "headers already sent" لأن الإخراج يسبق الرؤوس (رُصد فعلاً في الإنتاج). +APP_DEBUG=false APP_COUNTRY=Jordan APP_CURRENCY=JOD diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf index 7947e9b1..dce5b261 100644 --- a/docker/nginx/default.conf +++ b/docker/nginx/default.conf @@ -10,6 +10,19 @@ server { index index.php index.html; client_max_body_size 25m; + # ‏عنوان العميل الحقيقي. + # ‏السلسلة هي: العميل ← nginx المضيف (CloudPanel) ← nginx الحاوية ← php-fpm، + # ‏فكان REMOTE_ADDR يصل إلى PHP دائماً كعنوان الوسيط (172.24.0.1). والنتيجة + # ‏أن RateLimiter يضع كل المستخدمين في دلو واحد: محاولات دخول فاشلة من عميل + # ‏واحد كانت تحجب الجميع بـ 429 (ظهر ذلك في السجل فعلاً). + # ‏نمرّر السلسلة إلى PHP ليقرأها عبر clientIp() في core/helpers.php. + set $siro_fwd $http_x_forwarded_for; + if ($siro_fwd = "") { + set $siro_fwd $remote_addr; + } + fastcgi_param HTTP_X_FORWARDED_FOR $siro_fwd; + fastcgi_param HTTP_X_REAL_IP $remote_addr; + # ‏كان access_log off من اختبار ضغط قديم ونُسي مُطفأً، فصار تشخيص أي عطل # ‏مستحيلاً: البحث في سجلّ الوصول يرجع صفراً دائماً فيبدو أن التطبيق لا # ‏يستدعي المسار أصلاً، والحقيقة أن السجلّ فارغ لا الطلبات. أعِده مطفأً