From fc58529b09ef1d636808fc7be494dcdfc4d35653 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 16 Jun 2026 01:17:29 +0300 Subject: [PATCH] Update: 2026-06-16 01:17:28 --- backend/Admin/Staff/add.php | 26 +- backend/Admin/Staff/setup.php | 43 +- backend/Admin/auth/debug_login.php | 74 -- backend/Admin/auth/login.php | 43 +- backend/Admin/debug/.htaccess | 10 + backend/Admin/jwtService.php | 2 +- backend/Admin/rides/get_driver_live_pos.php | 2 +- backend/Admin/rides/get_rides_by_status.php | 2 +- .../syria/driver/register_driver_and_car.php | 30 +- backend/auth/syria/uploadSyrianDocs.php | 9 +- backend/composer.json | 5 +- backend/connect.php | 19 +- backend/core/Auth/JwtService.php | 46 +- backend/core/bootstrap.php | 8 +- backend/core/helpers.php | 30 +- backend/encrypt_decrypt.php | 17 +- backend/functions.php | 91 +- backend/login.php | 2 +- backend/loginAdmin.php | 28 +- backend/loginFirstTime.php | 11 +- backend/loginFirstTimeDriver.php | 35 +- backend/loginJwtDriver.php | 8 +- backend/loginWallet.php | 11 +- .../migration/get_all_driver_fingerprints.php | 2 +- backend/migration/get_all_fingerprints.php | 2 +- .../update_driver_fingerprint_admin.php | 2 +- .../migration/update_fingerprint_admin.php | 2 +- .../ride/call/driver/create_call_session.php | 3 +- .../call/passenger/create_call_session.php | 3 +- backend/ride/driverWallet/transfer.php | 53 +- .../location/getUpdatedLocationForAdmin.php | 2 +- backend/ride/places_syria/reverse_geocode.php | 17 +- backend/ride/pricing/get.php | 19 +- backend/ride/rides/add_ride.php | 16 +- backend/ride/rides/get_driver_location.php | 2 +- backend/ride/rides/public_track_location.php | 7 +- backend/schema_primary.sql | 18 +- backend/serviceapp/getEditorStatsCalls.php | 6 +- .../getEmployeeDriverAfterCallingRegister.php | 7 +- backend/serviceapp/getEmployeeStatic.php | 16 +- backend/serviceapp/getNotesForEmployee.php | 5 +- backend/serviceapp/getPassengersStatic.php | 22 +- backend/serviceapp/getRidesStatic.php | 29 +- backend/serviceapp/getdriverstotalMonthly.php | 34 +- backend/serviceapp/login.php | 8 +- backend/serviceapp/web/drivers.html | 2 +- backend/upload_audio.php | 111 +- security_audit_comprehensive_report.md | 154 +++ security_audit_final_report.md | 1052 +++++------------ .../lib/controller/functions/crud.dart | 20 +- .../controller/functions/encrypt_decrypt.dart | 80 +- .../controller/functions/encrypt_decrypt.dart | 74 +- .../v2/main/jwtconnect.php | 14 +- .../v2/main/loginJwtWalletDriver.php | 117 -- .../v2/main/loginWalletAdmin.php | 2 +- .../v2/main/ride/driverWallet/transfer.php | 10 +- 56 files changed, 1149 insertions(+), 1314 deletions(-) delete mode 100644 backend/Admin/auth/debug_login.php create mode 100644 backend/Admin/debug/.htaccess create mode 100644 security_audit_comprehensive_report.md delete mode 100755 walletintaleq.intaleq.xyz/v2/main/loginJwtWalletDriver.php 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 @@ نظام إدارة المركبات - +