Update: 2026-08-03 11:52:03
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<?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') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized: Admin access required']);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?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] كان يتحقق من صلاحية التوكن فقط بدون التحقق
|
||||
// من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين
|
||||
|
||||
@@ -79,7 +79,15 @@ class RateLimiter
|
||||
// ── بناء معرّف المستخدم ────────────────────────────────────
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,15 @@ class Database
|
||||
PDO::ATTR_EMULATE_PREPARES => 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,
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
<?php
|
||||
//encrypt_decrypt.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';
|
||||
$siteUser = get_current_user();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
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
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
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 {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
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
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
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
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
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');
|
||||
|
||||
// إعدادات التقرير
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
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
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
//getFemalDriver.php
|
||||
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 {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
// $con_tracking -> يتصل بقاعدة بيانات التتبع (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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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 من اختبار ضغط قديم ونُسي مُطفأً، فصار تشخيص أي عطل
|
||||
# مستحيلاً: البحث في سجلّ الوصول يرجع صفراً دائماً فيبدو أن التطبيق لا
|
||||
# يستدعي المسار أصلاً، والحقيقة أن السجلّ فارغ لا الطلبات. أعِده مطفأً
|
||||
|
||||
Reference in New Issue
Block a user