Update: 2026-08-03 11:52:03

This commit is contained in:
Hamza-Ayed
2026-08-03 11:52:03 +03:00
parent 4abfd30ddd
commit ec467b01c6
20 changed files with 152 additions and 5 deletions
@@ -6,6 +6,10 @@
require_once __DIR__ . '/../../connect.php'; // تأكد أن هذا الملف يحتوي على $con_tracking 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"); header("Content-Type: application/json; charset=UTF-8");
+4
View File
@@ -1,6 +1,10 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; require_once __DIR__ . '/../../connect.php';
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
if ($role !== 'admin' && $role !== 'super_admin') { if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403); http_response_code(403);
echo json_encode(['error' => 'Unauthorized: Admin access required']); echo json_encode(['error' => 'Unauthorized: Admin access required']);
+4 -1
View File
@@ -1,5 +1,8 @@
<?php <?php
require_once __DIR__ . '/../../../connect.php'; // المسار كان '/../../../connect.php' فيُحلّ من backend/auth/driver إلى
// /var/www/connect.php — مستوى واحد فوق جذر الباك إند — فتسقط الشاشة بخطأ
// قاتل رغم أن الاستجابة تُرجع 200.
require_once __DIR__ . '/../../connect.php';
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط بدون التحقق // 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط بدون التحقق
// من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين // من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين
+9 -1
View File
@@ -79,7 +79,15 @@ class RateLimiter
// ── بناء معرّف المستخدم ──────────────────────────────────── // ── بناء معرّف المستخدم ────────────────────────────────────
public static function identifier(?string $userId = null): string public static function identifier(?string $userId = null): string
{ {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; // كان REMOTE_ADDR وحده، وهو خلف دوكر دائماً عنوان الوسيط نفسه
// (172.24.0.1) — فيتشارك كل المستخدمين دلواً واحداً، ومحاولات دخول
// فاشلة من عميل واحد تحجب الجميع بـ 429 (ظهر ذلك في سجل الإنتاج).
// clientIp() تستخرج العميل الحقيقي من X-Forwarded-For ولا تثق به
// إلا إذا كان REMOTE_ADDR وسيطاً موثوقاً.
$ip = function_exists('clientIp')
? clientIp()
: ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
return $userId ? "{$ip}:{$userId}" : $ip; return $userId ? "{$ip}:{$userId}" : $ip;
} }
+9 -1
View File
@@ -73,7 +73,15 @@ class Database
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 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::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
PDO::ATTR_TIMEOUT => 10, PDO::ATTR_TIMEOUT => 10,
]; ];
+49
View File
@@ -310,3 +310,52 @@ function maskPhone(?string $phone): ?string
$tail = substr($d, -2); $tail = substr($d, -2);
return $head . str_repeat('*', $len - 5) . $tail; 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;
}
+11 -1
View File
@@ -1,7 +1,17 @@
<?php <?php
//encrypt_decrypt.php //encrypt_decrypt.php
// ⚠️ هذا الملف للتوافقية فقط. استخدم core/Security/EncryptionHelper.php للتشفير الجديد // ⚠️ هذا الملف للتوافقية فقط. استخدم core/Security/EncryptionHelper.php للتشفير الجديد
require_once realpath(__DIR__ . '/../vendor/autoload.php'); // vendor يقع داخل backend/ لا فوقه. المسار السابق '/../vendor/autoload.php'
// يشير إلى /var/www/vendor غير الموجود، و realpath() تُعيد false عندئذٍ،
// فيصبح الاستدعاء require_once('') ويسقط بخطأ قاتل يُحلّ من مجلد العمل
// الحالي — وهو ما ظهر في السجل كـ "Failed opening required ''".
$autoloadPath = __DIR__ . '/vendor/autoload.php';
if (!file_exists($autoloadPath)) {
$autoloadPath = __DIR__ . '/../vendor/autoload.php';
}
if (file_exists($autoloadPath)) {
require_once $autoloadPath;
}
require_once 'load_env.php'; require_once 'load_env.php';
$siteUser = get_current_user(); $siteUser = get_current_user();
+4
View File
@@ -1,5 +1,9 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
//getBalash.php //getBalash.php
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
@@ -1,6 +1,10 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat"); $southwestLat = filterRequest("southwestLat");
+4
View File
@@ -1,5 +1,9 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
//getComfort.php //getComfort.php
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
+4
View File
@@ -1,5 +1,9 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
//getDelivery.php //getDelivery.php
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
@@ -4,6 +4,10 @@
require_once __DIR__ . '/../../get_connect.php'; 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'); header('Content-Type: application/json; charset=utf-8');
// إعدادات التقرير // إعدادات التقرير
+4
View File
@@ -1,5 +1,9 @@
<?php <?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
//getElectric.php //getElectric.php
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
+4
View File
@@ -2,6 +2,10 @@
//getFemalDriver.php //getFemalDriver.php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
try { try {
// 1) قراءة والتحقق من الإحداثيات // 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat"); $southwestLat = filterRequest("southwestLat");
@@ -4,6 +4,10 @@
// $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations) // $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations)
require_once __DIR__ . '/../../connect.php'; require_once __DIR__ . '/../../connect.php';
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
try { try {
$driver_id = filterRequest("driver_id"); $driver_id = filterRequest("driver_id");
+4
View File
@@ -4,6 +4,10 @@
// $con_tracking -> يتصل بقاعدة بيانات التتبع (driver_behavior, car_locations) // $con_tracking -> يتصل بقاعدة بيانات التتبع (driver_behavior, car_locations)
require_once __DIR__ . '/../../connect.php'; require_once __DIR__ . '/../../connect.php';
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
try { try {
// استلام البيانات من Flutter // استلام البيانات من Flutter
$driver_id = filterRequest("driver_id"); $driver_id = filterRequest("driver_id");
@@ -3,6 +3,10 @@
// وكائن التشفير $encryptionHelper // وكائن التشفير $encryptionHelper
require_once __DIR__ . '/../../get_connect.php'; 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"); header("Content-Type: application/json; charset=UTF-8");
@@ -4,6 +4,10 @@ ob_start();
require_once __DIR__ . '/../../get_connect.php'; require_once __DIR__ . '/../../get_connect.php';
// $con_tracking لا يعرّفه connect.php — كان الاستدعاء يسقط بـ
// "Call to a member function prepare() on null".
$con_tracking = Database::get('tracking');
// تنظيف *جميع* مستويات التخزين المؤقت (Loop) // تنظيف *جميع* مستويات التخزين المؤقت (Loop)
// هذا يضمن التخلص من أي مسافات أو أخطاء ظهرت من ملفات الـ include // هذا يضمن التخلص من أي مسافات أو أخطاء ظهرت من ملفات الـ include
while (ob_get_level()) { while (ob_get_level()) {
+5 -1
View File
@@ -7,7 +7,11 @@ PHP_VERSION=8.2
HTTP_PORT=8181 HTTP_PORT=8181
APP_DOMAIN=jordan-siro.intaleqapp.com APP_DOMAIN=jordan-siro.intaleqapp.com
APP_ISSUER=SiroApp APP_ISSUER=SiroApp
APP_DEBUG=true # ⚠️ يجب أن تبقى false في الإنتاج.
# bootstrap.php يفعّل display_errors عندما تكون true، فتُطبع تحذيرات PHP
# داخل جسم الاستجابة: تكشف مسارات الملفات للعملاء، وتُفسد الردود بـ
# "headers already sent" لأن الإخراج يسبق الرؤوس (رُصد فعلاً في الإنتاج).
APP_DEBUG=false
APP_COUNTRY=Jordan APP_COUNTRY=Jordan
APP_CURRENCY=JOD APP_CURRENCY=JOD
+13
View File
@@ -10,6 +10,19 @@ server {
index index.php index.html; index index.php index.html;
client_max_body_size 25m; 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 من اختبار ضغط قديم ونُسي مُطفأً، فصار تشخيص أي عطل # ‏كان access_log off من اختبار ضغط قديم ونُسي مُطفأً، فصار تشخيص أي عطل
# ‏مستحيلاً: البحث في سجلّ الوصول يرجع صفراً دائماً فيبدو أن التطبيق لا # ‏مستحيلاً: البحث في سجلّ الوصول يرجع صفراً دائماً فيبدو أن التطبيق لا
# ‏يستدعي المسار أصلاً، والحقيقة أن السجلّ فارغ لا الطلبات. أعِده مطفأً # ‏يستدعي المسار أصلاً، والحقيقة أن السجلّ فارغ لا الطلبات. أعِده مطفأً