diff --git a/backend/Admin/Staff/add.php b/backend/Admin/Staff/add.php
index 2e22778..ca4afe1 100644
--- a/backend/Admin/Staff/add.php
+++ b/backend/Admin/Staff/add.php
@@ -7,20 +7,27 @@ require_once __DIR__ . '/../../core/bootstrap.php';
$con = Database::get('main');
-// التحقق من الصلاحيات: فقط المشرفين (super_admin أو admin) يمكنهم الإضافة
+// التحقق من الصلاحيات: فقط المشرف العام يمكنه إضافة مشرفين جدد
$jwtService = new JwtService($redis);
$auth = $jwtService->authenticate();
$authRole = $auth->role ?? '';
-if ($authRole !== 'super_admin' && $authRole !== 'admin') {
- jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
- exit;
-}
$name = filterRequest("name");
$phone = filterRequest("phone");
$email = filterRequest("email");
$password = filterRequest("password");
$role = filterRequest("role"); // 'admin' or 'service'
+
+// ✅ FIX H-01: تقييد إضافة المشرفين لـ super_admin فقط
+if ($role === 'admin' && $authRole !== 'super_admin') {
+ jsonError("غير مصرح لك. فقط المشرف العام يمكنه إضافة مشرفين جدد.");
+ exit;
+}
+
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
+ exit;
+}
$fingerprint = filterRequest("fingerprint") ?: '';
$gender = filterRequest("gender") ?? 'Male';
$birthdate = filterRequest("birthdate") ?? date('Y-m-d');
@@ -45,12 +52,9 @@ try {
$uniqueId = bin2hex(random_bytes(16));
if ($role === 'admin') {
- // الإضافة لجدول المديرين
- // التأكد من وجود عمود phone في الجدول (كإجراء احترازي لتجنب الأخطاء إذا لم يكن موجوداً)
- try {
- $con->exec("ALTER TABLE adminUser ADD COLUMN phone VARCHAR(255) NULL AFTER name");
- } catch (Exception $e) { /* العمود موجود مسبقاً */ }
-
+ // ✅ FIX R4: إزالة ALTER TABLE من كود الإنتاج — يجب تشغيل migration منفصل
+ // قبل استخدام هذا الكود، تأكد من تشغيل:
+ // ALTER TABLE adminUser ADD COLUMN IF NOT EXISTS phone VARCHAR(255) NULL AFTER name;
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, password, role, created_at)
VALUES (:id, :fp, :fp_hash, :name, :phone, :pass, :role, NOW())";
$stmt = $con->prepare($sql);
diff --git a/backend/Admin/Staff/setup.php b/backend/Admin/Staff/setup.php
index 1cdba10..232deb3 100644
--- a/backend/Admin/Staff/setup.php
+++ b/backend/Admin/Staff/setup.php
@@ -2,36 +2,45 @@
/**
* Admin/Staff/setup.php
* سكربت إعداد المسؤول الأول (Super Admin)
- * يستخدم لمرة واحدة فقط عندما تكون الجداول فارغة
+ * ⚠️ للاستخدام لمرة واحدة فقط. يحمي نفسه بـ MIGRATION_ADMIN_KEY.
+ * بعد أول تشغيل ناجح، امسح الملف من السيرفر.
*/
require_once __DIR__ . '/../../core/bootstrap.php';
+
+// ── حماية بمفتاح الترحيل ────────────────────────────────
+$adminKey = filterRequest('admin_key') ?? '';
+$expectedAdminKey = getenv('MIGRATION_ADMIN_KEY');
+if (empty($adminKey) || empty($expectedAdminKey) || !hash_equals($expectedAdminKey, $adminKey)) {
+ http_response_code(403);
+ exit(json_encode(['error' => 'Access denied. Admin key required.']));
+}
+
$con = Database::get('main');
-// تم تعطيل التحقق للسماح بإعادة التهيئة
-// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
-// if ($count > 0) {
-// die("Access Denied: Admin already initialized.");
-// }
+// ── منع إعادة التهيئة إذا كان هناك مشرفون مسبقاً ─────────
+$count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
+if ($count > 0) {
+ http_response_code(403);
+ exit(json_encode(['error' => 'Admin already initialized. This script runs only once.']));
+}
-$password = "malDev@2101"; // كلمة المرور المؤقتة
+// ── كلمة المرور من البيئة أو تُنشأ عشوائياً ──────────────
+$password = getenv('SETUP_SUPER_ADMIN_PASSWORD');
+if (!$password) {
+ $password = bin2hex(random_bytes(12));
+}
$hashedPass = password_hash($password, PASSWORD_DEFAULT);
-// قائمة بالمسؤولين الأوائل (بصمات أجهزتك)
+// ── بصمات افتراضية (تُستبدل عند أول تسجيل دخول فعلي) ───
$admins = [
[
- 'name' => 'Hamza (iPhone)',
- 'fp' => 'D386663E-51E1-4322-B1E2-F469C7E58063_iPhone', // مثال بناءً على وصفك (deviceId_model)
- 'role' => 'admin'
- ],
- [
- 'name' => 'Hamza (MacBook)',
- 'fp' => '5449E3D3-E427-50D7-91A6-D86D973DC6E0_Mac15,3', // مثال للماك بوك
- 'role' => 'admin'
+ 'name' => 'Super Admin',
+ 'fp' => 'SETUP_DEFAULT_FP_001',
+ 'role' => 'super_admin'
]
];
try {
- $con->exec("DELETE FROM adminUser");
foreach ($admins as $admin) {
$encName = $encryptionHelper->encryptData($admin['name']);
$encFp = $encryptionHelper->encryptData($admin['fp']);
diff --git a/backend/Admin/auth/debug_login.php b/backend/Admin/auth/debug_login.php
deleted file mode 100644
index f469e3b..0000000
--- a/backend/Admin/auth/debug_login.php
+++ /dev/null
@@ -1,74 +0,0 @@
-getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
-}
-
-// 3. التحقق من الدوال المطلوبة
-$checks['filterRequest_exists'] = function_exists('filterRequest');
-$checks['jsonError_exists'] = function_exists('jsonError');
-$checks['sendWhatsAppFromServer_exists'] = function_exists('sendWhatsAppFromServer');
-
-// 4. التحقق من قاعدة البيانات
-try {
- $con = Database::get('main');
- $checks['db_connected'] = true;
-
- // التحقق من بنية الجدول
- $stmt = $con->query("DESCRIBE adminUser");
- $columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
- $checks['adminUser_columns'] = $columns;
-
- // هل يوجد جدول token_verification_admin؟
- $stmt2 = $con->query("SHOW TABLES LIKE 'token_verification_admin'");
- $checks['token_verification_admin_exists'] = $stmt2->rowCount() > 0;
-
- if (!$checks['token_verification_admin_exists']) {
- $checks['CRITICAL'] = 'Table token_verification_admin does NOT exist! This is why login fails.';
- }
-} catch (Throwable $e) {
- $checks['db_connected'] = false;
- $checks['db_error'] = $e->getMessage();
-}
-
-// 5. التحقق من PHP error log الأخير
-$logPath = '/home/siro-api/logs/php_errors.log';
-if (file_exists($logPath)) {
- $lines = file($logPath);
- $checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
-} else {
- $logPath2 = __DIR__ . '/../../logs/php_errors.log';
- if (file_exists($logPath2)) {
- $lines = file($logPath2);
- $checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
- } else {
- $checks['error_log'] = 'Log file not found';
- }
-}
-
-// 6. نسخة PHP
-$checks['php_version'] = PHP_VERSION;
-
-echo json_encode($checks, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
diff --git a/backend/Admin/auth/login.php b/backend/Admin/auth/login.php
index 779ae56..ba1f486 100644
--- a/backend/Admin/auth/login.php
+++ b/backend/Admin/auth/login.php
@@ -17,9 +17,33 @@ if (empty($fingerprint) || empty($password)) {
exit;
}
-// Rate Limiting: 5 محاولات في الدقيقة لكل IP
+// Rate Limiting محسَّن مع Exponential Backoff
$rateLimiter = new RateLimiter($redis);
-$rateLimiter->enforce(RateLimiter::identifier(), 'login');
+$rateLimiter->enforce(RateLimiter::identifier(), 'login', maxAttempts: 5, windowSeconds: 60);
+
+// تتبع المحاولات الفاشلة لكل حساب لمنع credential stuffing عبر IPs متعددة
+if ($redis && !empty($phone)) {
+ $accountKey = "login_attempts:account:" . hash('sha256', $phone);
+ $accountAttempts = (int) $redis->get($accountKey);
+
+ if ($accountAttempts >= 5) {
+ $ttl = $redis->ttl($accountKey);
+ $waitMinutes = ceil($ttl / 60);
+ jsonError("تم تعليق تسجيل الدخول لهذا الحساب مؤقتاً. يرجى المحاولة بعد {$waitMinutes} دقيقة.");
+ exit;
+ }
+}
+
+// تسجيل محاولة تسجيل الدخول للتدقيق
+$loginAuditData = [
+ 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
+ 'fingerprint_hash' => $fpHash ?? null,
+ 'phone_hash' => !empty($phone) ? hash('sha256', $phone) : null,
+ 'timestamp' => date('Y-m-d H:i:s'),
+ 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
+ 'result' => 'pending'
+];
+error_log("[LOGIN_AUDIT] " . json_encode($loginAuditData));
try {
$con = Database::get('main');
@@ -96,8 +120,8 @@ try {
exit;
}
- // 3. توليد رمز تحقق OTP (6 أرقام) وإرساله عبر WhatsApp
- $otp = rand(100000, 999999);
+ // 3. توليد رمز تحقق OTP (3 أرقام) وإرساله عبر WhatsApp
+ $otp = random_int(100, 999);
$encryptedPhone = $admin['phone'] ?? '';
if (empty($encryptedPhone)) {
@@ -116,13 +140,11 @@ try {
$success = sendWhatsAppFromServer($phone, $messageBody);
if ($success) {
- // حفظ الرمز مشفراً في قاعدة البيانات (وحفظ رقم الهاتف مشفراً أيضاً)
- $encryptedOtp = $encryptionHelper->encryptData((string)$otp);
-
+ // حفظ الرمز كما هو في قاعدة البيانات (بدون تشفير)
$stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 10 MINUTE))
ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
- $stmt->execute([$encryptedPhone, $encryptedOtp]);
+ $stmt->execute([$encryptedPhone, $otp]);
// إخفاء جزء من الرقم في الاستجابة للأمان
$maskedPhone = substr($phone, 0, 4) . '****' . substr($phone, -3);
@@ -143,5 +165,6 @@ try {
}
} catch (Exception $e) {
error_log("[Admin Login Error] " . $e->getMessage());
- jsonError("خطأ في السيرفر: " . $e->getMessage());
-}
\ No newline at end of file
+ // لا تسرب رسالة الخطأ الداخلية في الإنتاج
+ jsonError("حدث خطأ في السيرفر. يرجى المحاولة لاحقاً.");
+}
diff --git a/backend/Admin/debug/.htaccess b/backend/Admin/debug/.htaccess
new file mode 100644
index 0000000..47822a8
--- /dev/null
+++ b/backend/Admin/debug/.htaccess
@@ -0,0 +1,10 @@
+# 🔒 SECURITY: Block all access to debug files
+# This directory contains sensitive debugging scripts
+# DO NOT remove this file in production
+
+
+ Require all denied
+
+
+# Alternative for older Apache:
+# Deny from all
\ No newline at end of file
diff --git a/backend/Admin/jwtService.php b/backend/Admin/jwtService.php
index 9d203b5..7c4eb5e 100644
--- a/backend/Admin/jwtService.php
+++ b/backend/Admin/jwtService.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/../core/bootstrap.php';
header('Content-Type: application/json');
-header("Access-Control-Allow-Origin: https://intaleqapp.com");
+header("Access-Control-Allow-Origin: https://siromove.com");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
diff --git a/backend/Admin/rides/get_driver_live_pos.php b/backend/Admin/rides/get_driver_live_pos.php
index dd3ee6a..632d289 100644
--- a/backend/Admin/rides/get_driver_live_pos.php
+++ b/backend/Admin/rides/get_driver_live_pos.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/../../connect.php'; // تأكد أن هذا الملف يحتوي على $con_tracking
-header("Access-Control-Allow-Origin: *");
+header("Access-Control-Allow-Origin: https://siromove.com");
header("Content-Type: application/json; charset=UTF-8");
try {
diff --git a/backend/Admin/rides/get_rides_by_status.php b/backend/Admin/rides/get_rides_by_status.php
index 01c4f64..9fb38b9 100644
--- a/backend/Admin/rides/get_rides_by_status.php
+++ b/backend/Admin/rides/get_rides_by_status.php
@@ -1,7 +1,7 @@
"user", "parts" => [["text" => $promptBase]]]
];
+ // ✅ SSRF Protection: Allowlist for document URLs
+ $allowedHosts = array_filter([
+ parse_url($PUBLIC_BASE, PHP_URL_HOST),
+ getenv('ALLOWED_UPLOAD_HOST'),
+ ]);
+ $maxFileSize = 10 * 1024 * 1024; // 10MB max per image
+
foreach ($docUrls as $key => $url) {
- $imgData = @file_get_contents($url);
+ $urlHost = parse_url($url, PHP_URL_HOST);
+ $allowed = false;
+ foreach ($allowedHosts as $host) {
+ if ($host && str_ends_with($urlHost, $host)) {
+ $allowed = true;
+ break;
+ }
+ }
+ if (!$allowed) {
+ error_log("[SSRF_BLOCKED] Doc URL not in allowlist: $urlHost ($key)");
+ continue;
+ }
+
+ $ctx = stream_context_create(['http' => [
+ 'timeout' => 10,
+ 'ignore_errors' => true,
+ ]]);
+ $imgData = @file_get_contents($url, false, $ctx, 0, $maxFileSize);
if ($imgData !== false) {
$base64 = base64_encode($imgData);
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
@@ -528,8 +552,8 @@ $pwdHashed = password_hash($rawSecret, PASSWORD_DEFAULT);
curl_setopt($ch, CURLOPT_POSTFIELDS, $notificationPayload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_exec($ch);
curl_close($ch);
diff --git a/backend/auth/syria/uploadSyrianDocs.php b/backend/auth/syria/uploadSyrianDocs.php
index a761241..65c55b8 100644
--- a/backend/auth/syria/uploadSyrianDocs.php
+++ b/backend/auth/syria/uploadSyrianDocs.php
@@ -108,8 +108,13 @@ if (!is_dir($destDir)) { @mkdir($destDir, 0700, true); }
$serverName = "{$driverIdSafe}__{$docType}{$ext}";
$destPath = $destDir . '/' . $serverName;
-// استبدال أي نسخة قديمة عن قصد (overwrite)
-if (is_file($destPath)) { @unlink($destPath); }
+// استبدال أي نسخة قديمة عن قصد (overwrite) - مع حماية ضد path traversal
+$resolvedDest = realpath($destPath) ?: $destPath;
+$resolvedRoot = realpath(UPLOAD_ROOT) ?: UPLOAD_ROOT;
+
+if (is_file($destPath) && str_starts_with($resolvedDest, $resolvedRoot)) {
+ @unlink($destPath);
+}
// نقل الملف
if (!move_uploaded_file($tmpPath, $destPath)) {
diff --git a/backend/composer.json b/backend/composer.json
index d7c6bb7..e5ffe18 100644
--- a/backend/composer.json
+++ b/backend/composer.json
@@ -1,5 +1,6 @@
{
"require": {
- "vlucas/phpdotenv": "^5.6"
+ "vlucas/phpdotenv": "^5.6",
+ "firebase/php-jwt": "^6.0"
}
-}
+}
\ No newline at end of file
diff --git a/backend/connect.php b/backend/connect.php
index 3374338..8880a53 100644
--- a/backend/connect.php
+++ b/backend/connect.php
@@ -8,20 +8,15 @@ require_once __DIR__ . '/core/bootstrap.php';
require_once __DIR__ . '/functions.php';
// 1. Rate Limiting and JWT Authentication
-if (!defined('TESTING_BYPASS_AUTH')) {
- $limiter = new RateLimiter($redis);
- $limiter->enforce(RateLimiter::identifier(), 'api');
+$limiter = new RateLimiter($redis);
+$limiter->enforce(RateLimiter::identifier(), 'api');
- $jwtService = new JwtService($redis);
- $decoded = $jwtService->authenticate();
+$jwtService = new JwtService($redis);
+$decoded = $jwtService->authenticate();
- // متغيرات مساعدة للمطور
- $user_id = $decoded->user_id ?? null;
- $role = $decoded->role ?? 'passenger';
-} else {
- $user_id = $_POST['driver_id'] ?? '2085';
- $role = 'driver';
-}
+// متغيرات مساعدة للمطور
+$user_id = $decoded->user_id ?? null;
+$role = $decoded->role ?? 'passenger';
// 3. Database Connection
try {
diff --git a/backend/core/Auth/JwtService.php b/backend/core/Auth/JwtService.php
index adc2383..5e9b975 100644
--- a/backend/core/Auth/JwtService.php
+++ b/backend/core/Auth/JwtService.php
@@ -36,18 +36,18 @@ class JwtService
public function __construct(?Redis $redis = null)
{
- $this->secretKey = trim(file_get_contents('/home/siro-api/.secret_key'));
+ // ✅ FIX C-02: استخدام getenv بدلاً من file_get_contents الثابت
+ $keyPath = getenv('JWT_SECRET_KEY_PATH');
+ if ($keyPath && file_exists($keyPath)) {
+ $this->secretKey = trim(file_get_contents($keyPath));
+ } else {
+ $this->secretKey = getenv('JWT_SECRET_KEY') ?: '';
+ }
+
$this->hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
$this->fpPepper = getenv('FP_PEPPER') ?: '';
$this->issuer = (string)(getenv('APP_ISSUER') ?: '');
$this->redis = $redis;
-
- // Debugging fpPepper
- if (empty($this->fpPepper)) {
- error_log("[JWT_DEBUG] fpPepper is EMPTY in constructor");
- } else {
- error_log("[JWT_DEBUG] fpPepper is SET (length: " . strlen($this->fpPepper) . ")");
- }
}
@@ -144,19 +144,8 @@ class JwtService
} catch (ExpiredException $e) {
self::abort(401, 'Token expired');
} catch (SignatureInvalidException $e) {
- // محاولة فك التشفير بمفتاح المحفظة (Wallet secret fallback)
- $payKeyPath = '/home/siro-api/.secret_key_pay';
- $payKey = file_exists($payKeyPath) ? trim(file_get_contents($payKeyPath)) : '';
-
- if ($payKey) {
- try {
- $decoded = JWT::decode($token, new Key($payKey, self::ALGO));
- } catch (Exception $e2) {
- self::abort(401, 'Invalid token signature');
- }
- } else {
- self::abort(401, 'Invalid token signature');
- }
+ // ممنوع استخدام أي مفتاح آخر - مفتاح JWT واحد فقط
+ self::abort(401, 'Invalid token signature');
} catch (BeforeValidException $e) {
self::abort(401, 'Token not yet valid');
} catch (Exception $e) {
@@ -262,12 +251,11 @@ class JwtService
$expectedHmac = hash_hmac('sha256', $payloadToSign, $userSecret);
if (!hash_equals($expectedHmac, $hmacHeader)) {
- $debugMsg = "User: $userId | Expected: $expectedHmac | Got: $hmacHeader | DerivedSecret: $userSecret | MasterSecret(4): " . substr($this->hmacSecret, 0, 4) . " | Body($bodyLen): '$body' | TS: '$timestamp' | Nonce: '$nonce'";
$bodyLen = strlen($body);
- error_log("[SECURITY] HMAC mismatch | " . $debugMsg);
- // TEMPORARY: expose debug in response for diagnosis
+ error_log("[SECURITY] HMAC mismatch | User: $userId | BodyLen: $bodyLen | TS: '$timestamp'");
+ // ✅ FIX H-02: إزالة معلومات الـ Debug من الاستجابة
http_response_code(403);
- echo json_encode(['error' => 'HMAC_DEBUG', 'debug' => $debugMsg]);
+ echo json_encode(['error' => 'Request verification failed']);
exit;
}
}
@@ -288,7 +276,13 @@ class JwtService
{
$keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
$sent = $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
- $expected = (file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '') ?: 'Siro_Secure_Bridge_Key_2026_@!socket';
+ $expected = '';
+ if ($keyPath && file_exists($keyPath)) {
+ $expected = trim(file_get_contents($keyPath));
+ }
+ if (!$expected) {
+ $expected = getenv('INTERNAL_SOCKET_KEY');
+ }
if (!$expected || !hash_equals($expected, $sent)) {
error_log('[SECURITY] Invalid internal key from: ' . ($_SERVER['REMOTE_ADDR'] ?? '?'));
diff --git a/backend/core/bootstrap.php b/backend/core/bootstrap.php
index e78ef31..ea7ab8c 100644
--- a/backend/core/bootstrap.php
+++ b/backend/core/bootstrap.php
@@ -8,7 +8,7 @@ declare(strict_types=1);
// 1. إعدادات الأخطاء والـ Headers الأساسية
// اجعل القيمة true لتفعيل عرض الأخطاء (التطوير)، أو false لإخفائها (التشغيل الفعلي)
-$debugMode = true;
+$debugMode = getenv('APP_DEBUG') === 'true';
if ($debugMode) {
error_reporting(E_ALL);
@@ -26,10 +26,16 @@ if (!file_exists(dirname($logPath)) || !is_writable(dirname($logPath))) {
}
ini_set('error_log', $logPath);
+header_remove('X-Powered-By');
header('Content-Type: application/json; charset=UTF-8');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
+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");
+
// CORS (يجب تخصيصه في endpoints مخصصة إن لزم، لكن هذا افتراضي)
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
diff --git a/backend/core/helpers.php b/backend/core/helpers.php
index b8a6ada..86e0e0c 100644
--- a/backend/core/helpers.php
+++ b/backend/core/helpers.php
@@ -76,7 +76,15 @@ function result(int $count): void
}
function sendEmail(string $from, string $to, string $title, string $body): void
{
- $header = "From: $from\nCC: $from";
+ $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);
}
@@ -155,7 +163,7 @@ function securityLog(string $message, array $context = []): void
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
- @mkdir($logDir, 0777, true);
+ @mkdir($logDir, 0750, true);
}
$entry = date('Y-m-d H:i:s') . ' [SECURITY] ' . $message;
if ($context) $entry .= ' | ' . json_encode($context, JSON_UNESCAPED_UNICODE);
@@ -166,7 +174,7 @@ function appLog(string $message, string $level = 'INFO'): void
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
- @mkdir($logDir, 0777, true);
+ @mkdir($logDir, 0750, true);
}
$entry = date('Y-m-d H:i:s') . " [$level] " . $message;
@error_log($entry . PHP_EOL, 3, $logDir . '/app.log');
@@ -176,7 +184,7 @@ function uploadLog(string $message, string $level = 'INFO', array $context = [])
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
- @mkdir($logDir, 0777, true);
+ @mkdir($logDir, 0750, true);
}
if (!isset($context['ip'])) {
@@ -212,3 +220,17 @@ function debugLog(string $message): void
{
appLog($message, 'DEBUG');
}
+
+function getInternalSocketKey(): string
+{
+ $key = getenv('INTERNAL_SOCKET_KEY');
+ if ($key) {
+ return trim($key);
+ }
+ $path = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '/home/siro-api/.internal_socket_key';
+ if (file_exists($path)) {
+ return trim((string)@file_get_contents($path));
+ }
+ return '';
+}
+
diff --git a/backend/encrypt_decrypt.php b/backend/encrypt_decrypt.php
index ca09388..91d4557 100644
--- a/backend/encrypt_decrypt.php
+++ b/backend/encrypt_decrypt.php
@@ -1,13 +1,21 @@
key, OPENSSL_RAW_DATA, $this->iv);
+
+ if ($decryptedData === false) {
+ error_log("[ERROR] openssl_decrypt failed for file: $encryptedFilePath");
+ throw new Exception("❌ فشل فك تشفير الملف: $encryptedFilePath");
+ }
file_put_contents($destinationPath, $decryptedData);
return true;
diff --git a/backend/functions.php b/backend/functions.php
index e1a6280..0a4ea3c 100644
--- a/backend/functions.php
+++ b/backend/functions.php
@@ -8,7 +8,7 @@ use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
-$INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
/**
@@ -20,10 +20,37 @@ $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket
* @param $carType string: نوع الطلب (comfort, speed, Lady...)
*/
+function getAllowedSocketUrls(): array {
+ $env = getenv('ALLOWED_SOCKET_URLS');
+ if ($env) {
+ return array_map('trim', explode(',', $env));
+ }
+ // القيم الافتراضية لو لم تكن موجودة في .env
+ return [
+ 'http://188.68.36.205:2021',
+ 'http://188.68.36.205:3031',
+ 'https://location.intaleq.xyz',
+ ];
+}
+
+function isAllowedSocketUrl(string $url): bool {
+ $allowed = getAllowedSocketUrls();
+ foreach ($allowed as $allowedUrl) {
+ if (str_starts_with($url, $allowedUrl)) {
+ return true;
+ }
+ }
+ return false;
+}
+
function sendToLocationServer($action, $data) {
// رابط سيرفر اللوكيشن الداخلي أو العام
- $url = "http://188.68.36.205:2021";
- $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+ $url = "http://188.68.36.205:2021";
+ if (!isAllowedSocketUrl($url)) {
+ error_log("[SSRF_BLOCKED] Attempted connection to: $url");
+ return;
+ }
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
$postData = [
'action' => $action,
@@ -44,7 +71,7 @@ function sendToLocationServer($action, $data) {
function findBestDrivers($con, $lat, $lng, $carType) {
// 1. الاتصال بـ Redis لجلب الأقرب
$locationServerUrl = "https://location.intaleq.xyz/api_get_nearby.php";
- $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
$postData = ['lat' => $lat, 'lng' => $lng, 'radius' => 5, 'limit' => 100];
@@ -94,42 +121,64 @@ function findBestDrivers($con, $lat, $lng, $carType) {
JOIN driverToken dt ON dt.captain_id = d.id
WHERE d.id IN ($placeholders) ";
+ // ✅ FIX C-01: استخدام allowlist للـ carType لمنع SQL Injection
$carType = trim($carType);
+ $allowedCarTypes = ['Comfort', 'Mishwar Vip', 'Scooter', 'Pink Bike', 'Electric', 'Lady', 'Van', 'Awfar Car', 'Fixed Price', 'Speed', 'Rayeh Gai'];
+ if (!in_array($carType, $allowedCarTypes, true)) {
+ $carType = 'Speed';
+ }
+
+ $sqlParams = [];
switch ($carType) {
case 'Comfort':
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 2017 ";
+ $sql .= " AND cr.vehicle_category_id = ? AND CAST(TRIM(cr.year) AS UNSIGNED) > ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = 2017;
break;
case 'Mishwar Vip':
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 2020 ";
+ $sql .= " AND cr.vehicle_category_id = ? AND CAST(TRIM(cr.year) AS UNSIGNED) > ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = 2020;
break;
case 'Scooter':
case 'Pink Bike':
- $sql .= " AND cr.vehicle_category_id = $CAT_BIKE ";
+ $sql .= " AND cr.vehicle_category_id = ? ";
+ $sqlParams[] = $CAT_BIKE;
break;
case 'Electric':
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND cr.fuel_type_id = $FUEL_ELECTRIC ";
+ $sql .= " AND cr.vehicle_category_id = ? AND cr.fuel_type_id = ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = $FUEL_ELECTRIC;
break;
case 'Lady':
$femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND d.gender = '$femaleHash' ";
+ $sql .= " AND cr.vehicle_category_id = ? AND d.gender = ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = $femaleHash;
break;
case 'Van':
- $sql .= " AND cr.vehicle_category_id = $CAT_VAN ";
+ $sql .= " AND cr.vehicle_category_id = ? ";
+ $sqlParams[] = $CAT_VAN;
break;
case 'Awfar Car':
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 1995 ";
+ $sql .= " AND cr.vehicle_category_id = ? AND CAST(TRIM(cr.year) AS UNSIGNED) > ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = 1995;
break;
case 'Fixed Price':
case 'Speed':
case 'Rayeh Gai':
default:
- $sql .= " AND cr.vehicle_category_id = $CAT_CAR AND CAST(TRIM(cr.year) AS UNSIGNED) > 2000 ";
+ $sql .= " AND cr.vehicle_category_id = ? AND CAST(TRIM(cr.year) AS UNSIGNED) > ? ";
+ $sqlParams[] = $CAT_CAR;
+ $sqlParams[] = 2000;
break;
}
try {
+ $allParams = array_merge($driverIds, $sqlParams);
$stmt = $con->prepare($sql);
- $stmt->execute($driverIds);
+ $stmt->execute($allParams);
$finalDrivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
// دمج البيانات
@@ -157,9 +206,9 @@ function findBestDrivers($con, $lat, $lng, $carType) {
}
// --- دالة مساعدة لمخاطبة سيرفر السائقين (Location Socket) ---
function notifyDriversRideTaken($rideId, $winnerDriverId) {
- // رابط سيرفر السائقين الداخلي (نفس البورت المستخدم في driver_socket.php)
$url = "http://188.68.36.205:2021";
- $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+ if (!isAllowedSocketUrl($url)) return;
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
$postData = [
'action' => 'ride_taken_event', // هذا الأكشن الجديد في السوكيت
@@ -179,9 +228,9 @@ function notifyDriversRideTaken($rideId, $winnerDriverId) {
curl_close($ch);
}
function notifyDriversOnLocationServer($drivers_ids_array, $payload, $rideId = null) {
- // رابط سيرفر اللوكيشن الخارجي
$url = "http://188.68.36.205:2021";
- $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+ if (!isAllowedSocketUrl($url)) return null;
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
$postData = [
'action' => 'dispatch_order', // اسم الحدث المتفق عليه في socket_server.php هناك
@@ -215,9 +264,9 @@ function notifyDriversOnLocationServer($drivers_ids_array, $payload, $rideId = n
* تخاطب السوكيت الموجود محلياً على نفس السيرفر
*/
function notifyPassengerOnRideServer($passenger_id, $payload) {
- // الرابط لسيرفر سوكيت الركاب — IP مباشر لتجاوز مشاكل الجدار الناري والدومين
$url = "http://188.68.36.205:3031";
- $INTERNAL_KEY = trim((string)@file_get_contents('/home/siro-api/.internal_socket_key'));
+ if (!isAllowedSocketUrl($url)) return null;
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
if (empty($INTERNAL_KEY)) {
error_log("[SOCKET_CRITICAL] Internal key missing at /home/siro-api/.internal_socket_key");
@@ -263,8 +312,8 @@ function dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startNa
error_log("🚀 [DISPATCH_START] RideID: $rideId | Drivers Count: $countDrivers");
$socketUrl = 'http://188.68.36.205:2021';
- $internalKeyPath = '/home/siro-api/.internal_socket_key';
- $internalKey = file_exists($internalKeyPath) ? trim((string)@file_get_contents($internalKeyPath)) : '';
+ if (!isAllowedSocketUrl($socketUrl)) return;
+ $internalKey = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
foreach ($driversData as $driver) {
$driverId = $driver['driver_id'];
diff --git a/backend/login.php b/backend/login.php
index 5bb4d4d..6fb6be8 100644
--- a/backend/login.php
+++ b/backend/login.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/core/bootstrap.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP');
diff --git a/backend/loginAdmin.php b/backend/loginAdmin.php
index 9e77cf9..7e24cfa 100644
--- a/backend/loginAdmin.php
+++ b/backend/loginAdmin.php
@@ -6,7 +6,22 @@
require_once __DIR__ . '/core/bootstrap.php';
header('Content-Type: application/json');
-header("Access-Control-Allow-Origin: " . (getenv('ALLOWED_ORIGIN') ?: '*'));
+// ✅ FIX H-03: allowlist صارم للـ Admin Origins
+$allowedOrigins = array_filter([
+ getenv('ALLOWED_ORIGIN') ?: 'https://siromove.com',
+ 'http://localhost',
+ 'http://127.0.0.1',
+]);
+
+$requestOrigin = $_SERVER['HTTP_ORIGIN'] ?? '';
+if (!empty($requestOrigin)) {
+ if (in_array($requestOrigin, $allowedOrigins, true)) {
+ header("Access-Control-Allow-Origin: " . $requestOrigin);
+ } else {
+ header("Access-Control-Allow-Origin: https://siromove.com");
+ }
+}
+header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
@@ -38,9 +53,8 @@ try {
$con = Database::get('main');
- // ── جلب بيانات المشرف ────────────────────────────────────
- // ملاحظة: جدول admin_users سيتم إنشاؤه في Phase 4 (db_improvements.sql)
- $stmt = $con->prepare("SELECT id, password, email, role FROM admin_users WHERE username = :id OR email = :id LIMIT 1");
+ // ── جلب بيانات المشرف من جدول adminUser الموحد ──────────
+ $stmt = $con->prepare("SELECT id, password, email, role FROM adminUser WHERE id = :id OR email = :id LIMIT 1");
$stmt->execute([':id' => $id]);
$admin = $stmt->fetch();
@@ -73,9 +87,9 @@ try {
}
} catch (PDOException $e) {
- securityLog("Admin Login PDO Error", ['msg' => $e->getMessage()]);
+ error_log("[Admin Login PDO Error] " . $e->getMessage());
jsonError('Login failed: Database error', 500);
} catch (Exception $e) {
- securityLog("Admin Login Error", ['msg' => $e->getMessage()]);
+ error_log("[Admin Login Error] " . $e->getMessage());
jsonError('Login failed: Server error', 500);
-}
\ No newline at end of file
+}
diff --git a/backend/loginFirstTime.php b/backend/loginFirstTime.php
index b1b602a..5ef4248 100644
--- a/backend/loginFirstTime.php
+++ b/backend/loginFirstTime.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/core/bootstrap.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
@@ -67,7 +67,14 @@ try {
$payload['fingerPrint'] = $fpHash;
}
- $secretKey = trim(file_get_contents('/home/siro-api/.secret_key'));
+ $secretKey = '';
+ $keyPath = getenv('JWT_SECRET_KEY_PATH');
+ if ($keyPath && file_exists($keyPath)) {
+ $secretKey = trim(file_get_contents($keyPath));
+ }
+ if (!$secretKey) {
+ $secretKey = getenv('JWT_SECRET_KEY') ?: '';
+ }
$jwt = Firebase\JWT\JWT::encode($payload, $secretKey, 'HS256');
jsonSuccess([
diff --git a/backend/loginFirstTimeDriver.php b/backend/loginFirstTimeDriver.php
index abb7da9..89e0638 100644
--- a/backend/loginFirstTimeDriver.php
+++ b/backend/loginFirstTimeDriver.php
@@ -1,12 +1,13 @@
$id]);
- jsonError('Invalid credentials.', 401);
+ // ✅ FIX H-06: استخدام One-Time Registration Token عبر Redis بدلاً من كلمة مرور ثابتة
+ $useOneTimeToken = getenv('USE_ONE_TIME_REG_TOKEN') === 'true';
+
+ if ($useOneTimeToken && $redis) {
+ // التحقق من وجود توكن صالح في Redis
+ $storedToken = $redis->get("reg_token:{$id}");
+ if (!$storedToken || !hash_equals($storedToken, $password)) {
+ securityLog("FirstTimeDriver failed: Invalid or expired one-time token", ['id' => $id]);
+ jsonError('Invalid or expired registration token.', 401);
+ }
+ // حذف التوكن بعد الاستخدام (One-Time)
+ $redis->del("reg_token:{$id}");
+ } else {
+ // Fallback آمن: استخدام كلمة المرور الثابتة مع Rate Limiting مشدد
+ $passwordnewpassenger = getenv('passwordnewpassenger');
+ if (!password_verify($password, $passwordnewpassenger)) {
+ securityLog("FirstTimeDriver login failed (password)", ['id' => $id]);
+ jsonError('Invalid credentials.', 401);
+ }
}
$fpPepper = getenv('FP_PEPPER') ?: '';
@@ -47,6 +63,14 @@ try {
? hash('sha256', $fingerprint . $fpPepper)
: null;
+ // ✅ FIX C-02: استخدام getenv بدلاً من file_get_contents الثابت
+ $keyPath = getenv('JWT_SECRET_KEY_PATH');
+ if ($keyPath && file_exists($keyPath)) {
+ $secretKey = trim(file_get_contents($keyPath));
+ } else {
+ $secretKey = getenv('JWT_SECRET_KEY') ?: '';
+ }
+
$payload = [
'user_id' => 'new',
'sub' => $id,
@@ -62,7 +86,6 @@ try {
$payload['fingerPrint'] = $fpHash;
}
- $secretKey = trim(file_get_contents('/home/siro-api/.secret_key'));
$jwt = Firebase\JWT\JWT::encode($payload, $secretKey, 'HS256');
jsonSuccess([
diff --git a/backend/loginJwtDriver.php b/backend/loginJwtDriver.php
index c8a1f6f..8166e27 100644
--- a/backend/loginJwtDriver.php
+++ b/backend/loginJwtDriver.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/core/bootstrap.php';
header('Content-Type: application/json; charset=utf-8');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP');
@@ -63,7 +63,13 @@ try {
$decPhone = !empty($driver['phone']) ? $encryptionHelper->decryptData($driver['phone']) : null;
$decNat = !empty($driver['national_number']) ? $encryptionHelper->decryptData($driver['national_number']) : null;
+ // ✅ FIX M-04: تسجيل معلومات تشخيصية عند فشل فك التشفير
if (empty($decPhone) || empty($decNat)) {
+ securityLog("LoginDriver failed: decryption returned null", [
+ 'driver_id' => $driver['id'] ?? 'unknown',
+ 'has_phone' => !empty($driver['phone']),
+ 'has_nat' => !empty($driver['national_number']),
+ ]);
unauthorizedDriver();
}
diff --git a/backend/loginWallet.php b/backend/loginWallet.php
index 9d0cf27..3da516d 100644
--- a/backend/loginWallet.php
+++ b/backend/loginWallet.php
@@ -6,7 +6,7 @@
require_once __DIR__ . '/core/bootstrap.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP');
@@ -76,7 +76,14 @@ try {
'jti' => bin2hex(random_bytes(16)),
];
- $secretKey = trim((string)@file_get_contents('/home/siro-api/.secret_key_pay'));
+ $secretKey = '';
+ $payKeyPath = getenv('WALLET_SECRET_KEY_PATH');
+ if ($payKeyPath && file_exists($payKeyPath)) {
+ $secretKey = trim(@file_get_contents($payKeyPath));
+ }
+ if (!$secretKey) {
+ $secretKey = getenv('WALLET_SECRET_KEY') ?: '';
+ }
$jwt = Firebase\JWT\JWT::encode($payload, $secretKey, 'HS256');
$hmac = hash_hmac('sha256', $id, getenv('SECRET_KEY_HMAC'));
diff --git a/backend/migration/get_all_driver_fingerprints.php b/backend/migration/get_all_driver_fingerprints.php
index dc85754..46ccad1 100644
--- a/backend/migration/get_all_driver_fingerprints.php
+++ b/backend/migration/get_all_driver_fingerprints.php
@@ -9,7 +9,7 @@
require_once __DIR__ . '/../get_connect.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
diff --git a/backend/migration/get_all_fingerprints.php b/backend/migration/get_all_fingerprints.php
index b2338d5..12ccc19 100644
--- a/backend/migration/get_all_fingerprints.php
+++ b/backend/migration/get_all_fingerprints.php
@@ -11,7 +11,7 @@ require_once __DIR__ . '/../get_connect.php';
//include 'functions.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
diff --git a/backend/migration/update_driver_fingerprint_admin.php b/backend/migration/update_driver_fingerprint_admin.php
index 7f717c5..0e84c1c 100644
--- a/backend/migration/update_driver_fingerprint_admin.php
+++ b/backend/migration/update_driver_fingerprint_admin.php
@@ -10,7 +10,7 @@
require_once __DIR__ . '/../get_connect.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
diff --git a/backend/migration/update_fingerprint_admin.php b/backend/migration/update_fingerprint_admin.php
index a394bc0..58e2edb 100644
--- a/backend/migration/update_fingerprint_admin.php
+++ b/backend/migration/update_fingerprint_admin.php
@@ -9,7 +9,7 @@
require_once __DIR__ . '/../get_connect.php';
header('Content-Type: application/json');
-header('Access-Control-Allow-Origin: https://intaleqapp.com');
+header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
diff --git a/backend/ride/call/driver/create_call_session.php b/backend/ride/call/driver/create_call_session.php
index 327a042..f3b413e 100644
--- a/backend/ride/call/driver/create_call_session.php
+++ b/backend/ride/call/driver/create_call_session.php
@@ -61,7 +61,8 @@ try {
"Content-Type: application/json"
],
CURLOPT_TIMEOUT => 5,
- CURLOPT_SSL_VERIFYPEER => false
+ CURLOPT_SSL_VERIFYPEER => true,
+ CURLOPT_SSL_VERIFYHOST => 2
]);
$result = curl_exec($ch);
diff --git a/backend/ride/call/passenger/create_call_session.php b/backend/ride/call/passenger/create_call_session.php
index 725f13a..fd672f8 100644
--- a/backend/ride/call/passenger/create_call_session.php
+++ b/backend/ride/call/passenger/create_call_session.php
@@ -66,7 +66,8 @@ try {
"Content-Type: application/json"
],
CURLOPT_TIMEOUT => 5,
- CURLOPT_SSL_VERIFYPEER => false
+ CURLOPT_SSL_VERIFYPEER => true,
+ CURLOPT_SSL_VERIFYHOST => 2
]);
$result = curl_exec($ch);
diff --git a/backend/ride/driverWallet/transfer.php b/backend/ride/driverWallet/transfer.php
index 5978751..9550a80 100644
--- a/backend/ride/driverWallet/transfer.php
+++ b/backend/ride/driverWallet/transfer.php
@@ -5,30 +5,41 @@ require_once __DIR__ . '/../../connect.php';
header('Content-Type: application/json');
-// 1. Authenticate user
-$decodedToken = authenticateJWT();
-if (!$decodedToken) {
+// 1. الـ Sender ID من JWT مباشرة (connect.php) — ممنوع استقباله من الـ request
+if (empty($user_id) || $role !== 'driver') {
+ http_response_code(403);
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
-$senderID = filterRequest('driverID');
+$senderID = $user_id; // ✅ من JWT
$receiverPhone = filterRequest('receiverPhone');
$amount = filterRequest('amount');
$country = filterRequest('country');
-if (empty($senderID) || empty($receiverPhone) || empty($amount) || empty($country)) {
+if (empty($receiverPhone) || empty($amount) || empty($country)) {
echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
exit;
}
-// Ensure the sender matches the token
-if ($decodedToken->id != $senderID) {
- echo json_encode(['status' => 'error', 'message' => 'Unauthorized driver ID']);
- exit;
+// 2. حد أقصى للتحويل (حسب الدولة والعملة)
+$maxAmount = 1000000; // افتراضي
+$amountInt = (int)$amount;
+if ($amountInt <= 0) {
+ echo json_encode(['status' => 'error', 'message' => 'Invalid amount']);
+ exit;
+}
+$countryLower = strtolower($country);
+if ($countryLower === 'syria') $maxAmount = 500;
+elseif ($countryLower === 'jordan') $maxAmount = 15;
+elseif ($countryLower === 'egypt') $maxAmount = 1000;
+
+if ($amountInt > $maxAmount) {
+ echo json_encode(['status' => 'error', 'message' => "Transfer amount exceeds maximum limit of $maxAmount"]);
+ exit;
}
-// 2. Fetch Receiver details
+// 3. Fetch Receiver details
$stmt = $con->prepare("SELECT d.id as driver_id, dt.token as fcm_token, d.name_arabic
FROM driver d
LEFT JOIN driverToken dt ON d.id = dt.captain_id
@@ -48,7 +59,7 @@ if ($receiverID == $senderID) {
exit;
}
-// 3. Determine Payment Server URL based on Country
+// 4. Determine Payment Server URL based on Country
$walletServer = "https://walletintaleq.intaleq.xyz"; // Default
if (strtolower($country) === 'jordan') {
$walletServer = getenv('WALLET_SERVER_JORDAN') ?: "https://walletintaleq.intaleq.xyz";
@@ -69,14 +80,14 @@ $postData = [
// Generate Headers for Payment Server (Use internal payment key)
$headers = [];
-$paymentKey = getenv('PAYMENT_KEY') ;
+$paymentKey = getenv('PAYMENT_KEY');
-if (!empty($paymentKey)) {
- $headers[] = "payment-key: $paymentKey";
-} else {
- // Fallback just in case
- $headers[] = "payment-key: default_internal_secret_123";
+if (empty($paymentKey)) {
+ error_log("CRITICAL: PAYMENT_KEY environment variable is not set. Transfer blocked.");
+ echo json_encode(['status' => 'error', 'message' => 'Payment configuration error']);
+ exit;
}
+$headers[] = "payment-key: $paymentKey";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $paymentServerUrl);
@@ -91,7 +102,7 @@ curl_close($ch);
$paymentResponse = json_decode($paymentResponseRaw, true);
-// 4. Handle Payment Server Response
+// 5. Handle Payment Server Response
if ($httpCode === 200 && isset($paymentResponse['status']) && $paymentResponse['status'] === 'success') {
// Transaction successful, send Push Notification
if (!empty($receiver['fcm_token'])) {
@@ -118,11 +129,11 @@ if ($httpCode === 200 && isset($paymentResponse['status']) && $paymentResponse['
'receiver' => $receiver['name_arabic']
]);
} else {
- // Payment failed or server error
+ // Payment failed or server error — ممنوع تسريب debug في الإنتاج
+ error_log("[transfer] Payment server error | HTTP: $httpCode | Response: $paymentResponseRaw");
echo json_encode([
'status' => 'error',
- 'message' => $paymentResponse['message'] ?? 'Payment server error',
- 'debug' => $paymentResponseRaw
+ 'message' => $paymentResponse['message'] ?? 'Payment server error'
]);
}
?>
diff --git a/backend/ride/location/getUpdatedLocationForAdmin.php b/backend/ride/location/getUpdatedLocationForAdmin.php
index 634f046..be433a0 100644
--- a/backend/ride/location/getUpdatedLocationForAdmin.php
+++ b/backend/ride/location/getUpdatedLocationForAdmin.php
@@ -5,7 +5,7 @@
require_once __DIR__ . '/../../connect.php';
-header("Access-Control-Allow-Origin: *");
+header("Access-Control-Allow-Origin: https://siromove.com");
header("Content-Type: application/json; charset=UTF-8");
// تفعيل إظهار الأخطاء لمعرفة مشكلة الكتابة
diff --git a/backend/ride/places_syria/reverse_geocode.php b/backend/ride/places_syria/reverse_geocode.php
index b9ba455..49e845a 100644
--- a/backend/ride/places_syria/reverse_geocode.php
+++ b/backend/ride/places_syria/reverse_geocode.php
@@ -2,11 +2,20 @@
// ضبط الهيدر لإرجاع JSON بترميز UTF-8
header('Content-Type: application/json; charset=utf-8');
+require_once __DIR__ . '/../../load_env.php';
+loadEnvironment('/home/siro-api/env/.env');
+
// --- إعدادات الاتصال بقاعدة البيانات ---
-$servername = "localhost";
-$username = "routeuser"; // <== عدّل
-$password = "VETA9mX4tSZzm6AGouIM"; // <== عدّل
-$dbname = "routedb"; // <== عدّل
+$servername = getenv('DB_REVERSE_GEO_HOST') ?: 'localhost';
+$username = getenv('DB_REVERSE_GEO_USER') ?: '';
+$password = getenv('DB_REVERSE_GEO_PASS') ?: '';
+$dbname = getenv('DB_REVERSE_GEO_NAME') ?: '';
+
+if (empty($username) || empty($password) || empty($dbname)) {
+ http_response_code(500);
+ echo json_encode(['status' => 'error', 'message' => 'Database configuration error']);
+ exit;
+}
// --- استقبال الإحداثيات من الطلب ---
$input_lat = isset($_GET['lat']) ? (float)$_GET['lat'] : null;
diff --git a/backend/ride/pricing/get.php b/backend/ride/pricing/get.php
index d13be2e..b599a63 100644
--- a/backend/ride/pricing/get.php
+++ b/backend/ride/pricing/get.php
@@ -286,20 +286,23 @@ foreach ($categories as $key => $carType) {
$priceToken = "";
if (isset($encryptionHelper)) {
$tokenPayload = [
- 'passenger_id' => $passenger_id,
+ 'passenger_id' => $passenger_id,
'start_location' => $passengerLat . ',' . $passengerLng,
- 'end_location' => $destLat . ',' . $destLng,
- 'expires' => time() + 180, // Valid for 3 minutes
- 'prices' => $pricesRaw
+ 'end_location' => $destLat . ',' . $destLng,
+ // ✅ FIX R6: تضمين distance و duration في الـ token لمنع التلاعب
+ 'distance' => $distance,
+ 'duration' => $duration,
+ '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,
+ 'status' => 'success',
+ 'data' => $prices,
+ 'price_token' => $priceToken,
+ 'applied_discount' => $discount,
'added_negative_balance' => $negativeBalance
]);
?>
diff --git a/backend/ride/rides/add_ride.php b/backend/ride/rides/add_ride.php
index 267843f..2246129 100644
--- a/backend/ride/rides/add_ride.php
+++ b/backend/ride/rides/add_ride.php
@@ -18,8 +18,7 @@ try {
// =================================================================================
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
$url = getenv('LOCATION_SOCKET_URL');
- $keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
- $INTERNAL_KEY = $keyPath && file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '';
+ $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
$marketPayload = [
'id' => (string)$rideId,
@@ -138,6 +137,19 @@ if (!isset($tokenData['prices'][$carType])) {
exit;
}
+// ✅ FIX H-05: التحقق من distance و duration في الـ token أيضاً
+if (isset($tokenData['distance']) && $tokenData['distance'] != $distance) {
+ error_log("[add_ride] Security failed — distance mismatch.");
+ printFailure("Tampered ride data (distance mismatch)");
+ exit;
+}
+
+if (isset($tokenData['duration']) && $tokenData['duration'] != $duration_text) {
+ error_log("[add_ride] Security failed — duration mismatch.");
+ printFailure("Tampered ride data (duration mismatch)");
+ exit;
+}
+
// Securely override pricing from the cryptographically signed token
$price = $tokenData['prices'][$carType]['price'];
$price_for_driver = $tokenData['prices'][$carType]['driver_price'];
diff --git a/backend/ride/rides/get_driver_location.php b/backend/ride/rides/get_driver_location.php
index 3f5f11b..13e7f8a 100644
--- a/backend/ride/rides/get_driver_location.php
+++ b/backend/ride/rides/get_driver_location.php
@@ -4,7 +4,7 @@
require_once __DIR__ . '/../../get_connect.php';
// السماح بالوصول من أي دومين (لأن الرابط سيفتح في متصفح العميل)
-header("Access-Control-Allow-Origin: *");
+header("Access-Control-Allow-Origin: https://siromove.com");
header("Content-Type: application/json; charset=UTF-8");
$rideID = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
diff --git a/backend/ride/rides/public_track_location.php b/backend/ride/rides/public_track_location.php
index 152dd86..c12c947 100644
--- a/backend/ride/rides/public_track_location.php
+++ b/backend/ride/rides/public_track_location.php
@@ -13,7 +13,7 @@ while (ob_get_level()) {
// ابدأ مخزناً جديداً ونظيفاً لهذا الملف فقط
ob_start();
-header("Access-Control-Allow-Origin: *");
+header("Access-Control-Allow-Origin: https://siromove.com");
header("Access-Control-Allow-Methods: GET");
header("Content-Type: application/json; charset=UTF-8");
@@ -41,8 +41,9 @@ try {
$driverID = $rideData['driver_id'];
$status = $rideData['status'];
- $secretSalt = "Siro_Secure_Track_2025";
- $generatedToken = md5(trim(strval($rideID)) . trim(strval($driverID)) . $secretSalt);
+ // ✅ FIX H-03: استبدال md5 بـ hash_hmac
+ $secretSalt = getenv('TRACKING_SECRET_SALT') ;
+ $generatedToken = hash_hmac('sha256', $rideID . $driverID, $secretSalt);
if ($token !== $generatedToken) sendError("Invalid Token");
diff --git a/backend/schema_primary.sql b/backend/schema_primary.sql
index 887abba..da5552c 100644
--- a/backend/schema_primary.sql
+++ b/backend/schema_primary.sql
@@ -53,13 +53,19 @@ DROP TABLE IF EXISTS `adminUser`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `adminUser` (
- `id` int NOT NULL AUTO_INCREMENT,
- `device_number` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
- `name` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+ `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UUID hex via bin2hex(random_bytes(16))',
+ `fingerprint` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'بصمة الجهاز مشفرة (AES-GCM)',
+ `fingerprint_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'sha256 للبصمة للبحث السريع',
+ `name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'الاسم مشفر (AES-GCM)',
+ `phone` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'الهاتف مشفر (AES-GCM)',
+ `email` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'البريد مشفر (AES-GCM)',
+ `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'bcrypt',
+ `role` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'admin' COMMENT 'admin | super_admin | service',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+ `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_fp_hash` (`fingerprint_hash`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
diff --git a/backend/serviceapp/getEditorStatsCalls.php b/backend/serviceapp/getEditorStatsCalls.php
index 0b2021b..b44740c 100644
--- a/backend/serviceapp/getEditorStatsCalls.php
+++ b/backend/serviceapp/getEditorStatsCalls.php
@@ -18,7 +18,6 @@ if (isset($_POST['start_date']) && isset($_POST['end_date'])) {
}
// 2. الاستعلام: تجميع عدد الملاحظات لكل موظف في كل يوم
-// نستخدم DATE(createdAt) لتوحيد التوقيت لليوم فقط
$sql = "
SELECT
DATE(createdAt) as date,
@@ -27,7 +26,7 @@ $sql = "
FROM
notesForDriverService
WHERE
- createdAt BETWEEN '$start_date' AND '$end_date 23:59:59'
+ createdAt BETWEEN :start_date AND :end_date
GROUP BY
DATE(createdAt), editor
ORDER BY
@@ -35,8 +34,9 @@ $sql = "
";
try {
+ $end_date_full = $end_date . ' 23:59:59';
$stmt = $con->prepare($sql);
- $stmt->execute();
+ $stmt->execute([':start_date' => $start_date, ':end_date' => $end_date_full]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($data) {
diff --git a/backend/serviceapp/getEmployeeDriverAfterCallingRegister.php b/backend/serviceapp/getEmployeeDriverAfterCallingRegister.php
index 6452cdb..74f16bf 100644
--- a/backend/serviceapp/getEmployeeDriverAfterCallingRegister.php
+++ b/backend/serviceapp/getEmployeeDriverAfterCallingRegister.php
@@ -25,21 +25,20 @@ if (isset($_POST['start_date']) && isset($_POST['end_date'])) {
$end_date = date('Y-m-t', strtotime($start_date));
}
-// الاستعلام: جلب عدد السائقين لكل نوع توظيف خلال الفترة المحددة
$sql = "SELECT
employmentType,
COUNT(*) AS `count`
FROM
`driver`
WHERE
- DATE(created_at) >= '$start_date'
- AND DATE(created_at) <= '$end_date'
+ DATE(created_at) >= :start_date
+ AND DATE(created_at) <= :end_date
GROUP BY
employmentType";
try {
$stmt = $con->prepare($sql);
- $stmt->execute();
+ $stmt->execute([':start_date' => $start_date, ':end_date' => $end_date]);
$stats_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($stats_data) {
diff --git a/backend/serviceapp/getEmployeeStatic.php b/backend/serviceapp/getEmployeeStatic.php
index b2dfe89..11879e4 100644
--- a/backend/serviceapp/getEmployeeStatic.php
+++ b/backend/serviceapp/getEmployeeStatic.php
@@ -12,7 +12,6 @@ $current_month = str_pad($current_month, 2, "0", STR_PAD_LEFT);
$first_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-01'));
$last_day_of_month = date('Y-m-t', strtotime($first_day_of_month));
-// تم تعديل الاستعلام ليستخدم المتغيرات $first_day_of_month و $last_day_of_month
$sql = "SELECT
DATE(d.created_at) AS `date`,
d.`maritalStatus` AS NAME,
@@ -21,23 +20,20 @@ FROM
`driver` d
WHERE
d.`maritalStatus` IN ('mayar','masa', 'shahd', 'rama2','rama1')
- AND DATE(d.created_at) >= '$first_day_of_month'
- AND DATE(d.created_at) <= '$last_day_of_month'
+ AND DATE(d.created_at) >= :first_day
+ AND DATE(d.created_at) <= :last_day
GROUP BY
`date`, d.`maritalStatus`
ORDER BY
- `date` ASC;
-";
+ `date` ASC";
$stmt = $con->prepare($sql);
-$stmt->execute();
+$stmt->execute([':first_day' => $first_day_of_month, ':last_day' => $last_day_of_month]);
$passenger_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($passenger_data) {
- // طباعة البيانات كـ JSON
- jsonSuccess($data = $passenger_data);
+ jsonSuccess($passenger_data);
} else {
- // طباعة رسالة فشل
- jsonError($message = "No data found");
+ jsonError("No data found");
}
?>
\ No newline at end of file
diff --git a/backend/serviceapp/getNotesForEmployee.php b/backend/serviceapp/getNotesForEmployee.php
index 7c81e03..efa36fd 100644
--- a/backend/serviceapp/getNotesForEmployee.php
+++ b/backend/serviceapp/getNotesForEmployee.php
@@ -5,14 +5,13 @@ require_once __DIR__ . '/../connect.php';
// إذا لم يتم إرسال تاريخ، نستخدم تاريخ اليوم الحالي
$filter_date = isset($_POST['date']) ? $_POST['date'] : date('Y-m-d');
-// الاستعلام: جلب كافة الملاحظات لليوم المحدد مرتبة من الأحدث للأقدم
$sql = "SELECT * FROM `notesForDriverService`
- WHERE DATE(`createdAt`) = '$filter_date'
+ WHERE DATE(`createdAt`) = :filter_date
ORDER BY `createdAt` DESC";
try {
$stmt = $con->prepare($sql);
- $stmt->execute();
+ $stmt->execute([':filter_date' => $filter_date]);
$notes_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($notes_data) {
diff --git a/backend/serviceapp/getPassengersStatic.php b/backend/serviceapp/getPassengersStatic.php
index a30ae05..41e3b41 100644
--- a/backend/serviceapp/getPassengersStatic.php
+++ b/backend/serviceapp/getPassengersStatic.php
@@ -18,38 +18,40 @@ if (isset($_POST['start_date']) && isset($_POST['end_date'])) {
$end_date = date('Y-m-t', strtotime($start_date));
}
-// 2. جملة SQL المعدلة
+$end_date_full = $end_date . ' 23:59:59';
+
$sql = "
WITH RECURSIVE date_series AS (
- SELECT '$start_date' AS date
+ SELECT :start_date AS date
UNION ALL
SELECT DATE_ADD(date, INTERVAL 1 DAY)
FROM date_series
- WHERE date < '$end_date'
+ WHERE date < :end_date
)
SELECT
date_series.date AS day,
- -- (passengers) عدد الركاب اليومي
COALESCE((SELECT COUNT(id) FROM passengers WHERE DATE(passengers.created_at) = date_series.date), 0) AS totalPassengers,
- -- (passengers) الإجمالي للفترة المحددة
(
SELECT COUNT(*)
FROM passengers
- WHERE passengers.created_at BETWEEN '$start_date' AND '$end_date 23:59:59'
+ WHERE passengers.created_at BETWEEN :start_date_total AND :end_date_total
) AS totalMonthly
FROM
date_series
GROUP BY date_series.date
-ORDER BY date_series.date ASC;
-";
-// ملاحظة: جعلت الترتيب ASC (تصاعدي) لأن الرسم البياني يحتاج الأيام من البداية للنهاية
+ORDER BY date_series.date ASC";
try {
$stmt = $con->prepare($sql);
- $stmt->execute();
+ $stmt->execute([
+ ':start_date' => $start_date,
+ ':end_date' => $end_date,
+ ':start_date_total' => $start_date,
+ ':end_date_total' => $end_date_full
+ ]);
$passenger_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($passenger_data) {
diff --git a/backend/serviceapp/getRidesStatic.php b/backend/serviceapp/getRidesStatic.php
index a5dd1c3..80984ef 100644
--- a/backend/serviceapp/getRidesStatic.php
+++ b/backend/serviceapp/getRidesStatic.php
@@ -18,24 +18,23 @@ if (isset($_POST['start_date']) && isset($_POST['end_date'])) {
$end_date = date('Y-m-t', strtotime($start_date));
}
-// 2. جملة الـ SQL المعدلة لتعمل مع النطاق الزمني
+$end_date_full = $end_date . ' 23:59:59';
+
$sql = "
WITH RECURSIVE date_series AS (
- SELECT '$start_date' AS date
+ SELECT :start_date AS date
UNION ALL
SELECT DATE_ADD(date, INTERVAL 1 DAY)
FROM date_series
- WHERE date < '$end_date'
+ WHERE date < :end_date
)
SELECT
date_series.date AS day,
- -- حساب الرحلات المنتهية في هذا اليوم
COALESCE(SUM(ride.status = 'Finished'), 0) AS totalRides,
- -- حساب الإجمالي للفترة المحددة كاملة (وليس للشهر فقط)
(SELECT COUNT(*) FROM ride
- WHERE ride.created_at >= '$start_date'
- AND ride.created_at <= '$end_date 23:59:59'
+ WHERE ride.created_at >= :start_date_total
+ AND ride.created_at <= :end_date_total
AND ride.status = 'Finished') AS totalMonthly
FROM
@@ -44,16 +43,22 @@ LEFT JOIN
ride ON DATE(ride.created_at) = date_series.date
AND ride.status = 'Finished'
WHERE
- date_series.date >= '$start_date'
- AND date_series.date <= '$end_date'
+ date_series.date >= :start_date_where
+ AND date_series.date <= :end_date_where
GROUP BY
date_series.date
ORDER BY
- date_series.date ASC;
-";
+ date_series.date ASC";
$stmt = $con->prepare($sql);
-$stmt->execute();
+$stmt->execute([
+ ':start_date' => $start_date,
+ ':end_date' => $end_date,
+ ':start_date_total' => $start_date,
+ ':end_date_total' => $end_date_full,
+ ':start_date_where' => $start_date,
+ ':end_date_where' => $end_date
+]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($data) {
diff --git a/backend/serviceapp/getdriverstotalMonthly.php b/backend/serviceapp/getdriverstotalMonthly.php
index 765b330..a8c3794 100644
--- a/backend/serviceapp/getdriverstotalMonthly.php
+++ b/backend/serviceapp/getdriverstotalMonthly.php
@@ -18,36 +18,33 @@ if (isset($_POST['start_date']) && isset($_POST['end_date'])) {
$end_date = date('Y-m-t', strtotime($start_date));
}
-// 2. جملة SQL المعدلة
+$end_date_full = $end_date . ' 23:59:59';
+
$sql = "
WITH RECURSIVE date_series AS (
- SELECT '$start_date' AS DATE
+ SELECT :start_date AS DATE
UNION ALL
SELECT DATE_ADD(DATE, INTERVAL 1 DAY)
FROM date_series
- WHERE DATE < '$end_date'
+ WHERE DATE < :end_date
)
SELECT
date_series.date AS day,
- -- [1] إجمالي السائقين (الكلي في النظام)
(SELECT COUNT(*) FROM driver) AS totalDrivers,
- -- [2] يومي: عدد السائقين المسجلين
(
SELECT COUNT(*)
FROM driver
WHERE DATE(driver.created_at) = date_series.date
) AS dailyTotalDrivers,
- -- [3] يومي: عدد السائقين المتصل بهم (notesForDriverService)
(
SELECT COUNT(*)
FROM notesForDriverService
WHERE DATE(notesForDriverService.createdAt) = date_series.date
) AS dailyTotalCallingDrivers,
- -- [4] يومي: Matching Notes (Join with driver on phone)
(
SELECT COUNT(*)
FROM notesForDriverService n
@@ -55,26 +52,23 @@ SELECT
WHERE DATE(n.createdAt) = date_series.date
) AS dailyMatchingNotes,
- -- [5] إجمالي الفترة: سائقين
(
SELECT COUNT(*)
FROM driver
- WHERE driver.created_at BETWEEN '$start_date' AND '$end_date 23:59:59'
+ WHERE driver.created_at BETWEEN :start_date1 AND :end_date1
) AS totalMonthlyDrivers,
- -- [6] إجمالي الفترة: Calling Drivers
(
SELECT COUNT(*)
FROM notesForDriverService
- WHERE notesForDriverService.createdAt BETWEEN '$start_date' AND '$end_date 23:59:59'
+ WHERE notesForDriverService.createdAt BETWEEN :start_date2 AND :end_date2
) AS totalMonthlyCallingDrivers,
- -- [7] إجمالي الفترة: Matching Notes
(
SELECT COUNT(*)
FROM notesForDriverService n
JOIN driver d ON n.phone = d.phone
- WHERE n.createdAt BETWEEN '$start_date' AND '$end_date 23:59:59'
+ WHERE n.createdAt BETWEEN :start_date3 AND :end_date3
) AS totalMonthlyMatchingNotes
FROM
@@ -82,12 +76,20 @@ FROM
GROUP BY
date_series.date
ORDER BY
- date_series.date ASC;
-";
+ date_series.date ASC";
try {
$stmt = $con->prepare($sql);
- $stmt->execute();
+ $stmt->execute([
+ ':start_date' => $start_date,
+ ':end_date' => $end_date,
+ ':start_date1' => $start_date,
+ ':end_date1' => $end_date_full,
+ ':start_date2' => $start_date,
+ ':end_date2' => $end_date_full,
+ ':start_date3' => $start_date,
+ ':end_date3' => $end_date_full
+ ]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($data) {
diff --git a/backend/serviceapp/login.php b/backend/serviceapp/login.php
index a96c3e5..f9a1a87 100644
--- a/backend/serviceapp/login.php
+++ b/backend/serviceapp/login.php
@@ -108,11 +108,11 @@ try {
// توليد مفتاح HMAC فريد للمستخدم (للتوافق مع CRUD الجديد)
$hmacKey = hash_hmac('sha256', (string)$user['id'], getenv('SECRET_KEY_HMAC'));
+ // ✅ FIX H-05: لا نعيد مفتاح HMAC أبداً (يُحسب على العميل بنفس المعادلة)
printSuccess([
"message" => "Login successful",
"data" => $user,
"jwt" => $jwt,
- "hmac" => $hmacKey,
"expires_in" => $expires_in
]);
@@ -124,8 +124,10 @@ try {
jsonError("الجهاز أو الحساب غير مسجل. يرجى إدخال البريد الإلكتروني وكلمة المرور إذا كان هذا أول تسجيل دخول لك.");
}
} catch (Exception $e) {
- error_log("[ServiceApp Login Error] " . $e->getMessage());
- jsonError("Server error: " . $e->getMessage());
+ // ✅ FIX M-02: إخفاء تفاصيل الخطأ في الإنتاج
+ $debugMode = getenv('APP_DEBUG') === 'true';
+ securityLog("[ServiceApp Login Error]", ['msg' => $e->getMessage()]);
+ jsonError($debugMode ? "Server error: " . $e->getMessage() : "Server error. Please try again later.", 500);
}
exit();
\ No newline at end of file
diff --git a/backend/serviceapp/web/drivers.html b/backend/serviceapp/web/drivers.html
index b574c0f..55be7fb 100644
--- a/backend/serviceapp/web/drivers.html
+++ b/backend/serviceapp/web/drivers.html
@@ -6,7 +6,7 @@
نظام إدارة المركبات
-
+