Update codebase

This commit is contained in:
Hamza-Ayed
2026-08-09 16:56:13 +03:00
parent 95e2e4f35d
commit b64debaa88
1058 changed files with 164327 additions and 113928 deletions
+10 -2
View File
@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../../../connect.php';
require_once __DIR__ . '/../../connect.php';
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط — أي مستخدم
// مسجّل دخول (راكب/سائق آخر) كان يقدر يجلب بيانات أي سائق مفكوكة التشفير
@@ -10,7 +10,15 @@ if ($role !== 'admin' && $role !== 'super_admin') {
exit;
}
$driverId = filterRequest("id");
// filterRequest() تقرأ من POST أو JSON body فقط ولا تلمس $_GET
// (core/helpers.php). شاشة مراجعة الوثائق تنادي هذه النقطة بـ
// ?id=... في الـ query string، فكانت تُرجع "driver_id is required"
// دائماً رغم إرسال المعرّف. نقبل الشكلين، ونقبل driver_id أيضاً
// لأن رسالة الخطأ نفسها تسمّيه هكذا.
$driverId = filterRequest("id")
?: filterRequest("driver_id")
?: (isset($_GET['id']) ? trim((string) $_GET['id']) : null)
?: (isset($_GET['driver_id']) ? trim((string) $_GET['driver_id']) : null);
if (empty($driverId)) {
jsonError("driver_id is required.");
+4 -1
View File
@@ -1,5 +1,8 @@
<?php
require_once __DIR__ . '/../../../connect.php';
// المسار كان '/../../../connect.php' فيُحلّ من backend/auth/driver إلى
// /var/www/connect.php — مستوى واحد فوق جذر الباك إند — فتسقط الشاشة بخطأ
// قاتل رغم أن الاستجابة تُرجع 200.
require_once __DIR__ . '/../../connect.php';
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط بدون التحقق
// من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين
+1 -1
View File
@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../../../connect.php';
require_once __DIR__ . '/../../connect.php';
$phoneNumber = filterRequest("phone_number");
@@ -11,18 +11,16 @@ $password = filterRequest('password');
$audience = filterRequest('aud') ?? 'siro-driver-android'; // الافتراضي
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP
// 1. حد معدل الطلبات مطبّق على الجميع (الحد مرفوع إلى 30/دقيقة في RateLimiter)
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'tester_login');
if (!$email || !$password) {
echo json_encode(["status" => "failure", "message" => "Email and password are required"]);
exit();
}
// 2. التحقق من أن الحساب مخصص للفحص فقط (isTest check)
$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: '';
$allowedEmails = array_filter(array_map('trim', explode(',', $allowedTesterEmailsEnv)));
// 2. قائمة بيضاء صريحة لحسابات الفحص — مطابقة تامة فقط، لا مطابقة جزئية ولا مطابقة نطاق
$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ($_ENV['ALLOWED_TESTER_EMAILS'] ?? '');
$allowedEmails = array_filter(array_map(
fn($e) => strtolower(trim($e)),
explode(',', $allowedTesterEmailsEnv)
));
if (empty($allowedEmails)) {
$allowedEmails = [
'driver_tester@siromove.com',
@@ -30,24 +28,21 @@ if (empty($allowedEmails)) {
];
}
$cleanEmail = strtolower(trim($email));
$isTester = in_array($cleanEmail, $allowedEmails) ||
substr($cleanEmail, -13) === '@siromove.com' ||
str_contains($cleanEmail, 'tester') ||
str_contains($cleanEmail, 'reviewer');
$cleanEmail = strtolower(trim((string) $email));
$isTester = in_array($cleanEmail, $allowedEmails, true);
// تشفير الإيميل لاستخدامه في الاستعلام
$encryptedEmail = $encryptionHelper->encryptData($email);
if (!$email || !$password) {
echo json_encode(["status" => "failure", "message" => "Email and password are required"]);
exit();
}
try {
$con = Database::get('main');
// Auto-seed/create tester driver logic removed for security
$encryptedEmail = $encryptionHelper->encryptData($email);
global $blindIndex;
$emailBidx = $blindIndex ? $blindIndex->index('driver.email', $email) : null;
// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر أو الفهرس الأعمى
$sql = "SELECT
driver.*,
phone_verification.is_verified,
@@ -57,46 +52,47 @@ try {
FROM driver
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone_key
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
WHERE
driver.email = :email OR (:email_bidx IS NOT NULL AND driver.email_bidx = :email_bidx)
LIMIT 1";
WHERE driver.email = :email";
$params = [':email' => $encryptedEmail];
if ($emailBidx !== null) {
$sql .= " OR driver.email_bidx = :email_bidx";
$params[':email_bidx'] = $emailBidx;
}
$sql .= " LIMIT 1";
$stmt = $con->prepare($sql);
$stmt->execute([':email' => $encryptedEmail, ':email_bidx' => $emailBidx]);
$stmt->execute($params);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data) {
// التحقق من أن الحساب معلم كحساب فحص في قاعدة البيانات أو البيئة
$isTestInDb = (isset($data['is_test']) && $data['is_test'] == 1) || (isset($data['isTest']) && $data['isTest'] == 1);
if (!$isTestInDb && !$isTester) {
jsonError("Access denied. Not a tester account.");
exit();
}
// فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين)
// لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt
if (password_verify($password, $data['password'])) {
if (password_verify($password, $data['password'] ?? '')) {
unset($data['password']);
// فك تشفير الحقول الحساسة
$data['phone'] = $encryptionHelper->decryptData($data['phone']);
$data['email'] = $encryptionHelper->decryptData($data['email']);
$data['gender'] = $encryptionHelper->decryptData($data['gender']);
$data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
$data['site'] = $encryptionHelper->decryptData($data['site']);
$data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
$data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
if(isset($data['phone'])) $data['phone'] = $encryptionHelper->decryptData($data['phone']);
if(isset($data['email'])) $data['email'] = $encryptionHelper->decryptData($data['email']);
if(isset($data['gender'])) $data['gender'] = $encryptionHelper->decryptData($data['gender']);
if(isset($data['birthdate'])) $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
if(isset($data['site'])) $data['site'] = $encryptionHelper->decryptData($data['site']);
if(isset($data['first_name'])) $data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
if(isset($data['last_name'])) $data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
// توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين
$jwtService = new JwtService($redis);
$jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint);
echo json_encode([
"status" => "success",
"jwt" => $jwt,
"data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة
"data" => [$data]
], JSON_UNESCAPED_UNICODE);
} else {
jsonError("Incorrect password.");
@@ -104,9 +100,9 @@ try {
} else {
jsonError("User does not exist.");
}
} catch (Exception $e) {
error_log("[Tester Login Error] " . $e->getMessage());
jsonError("Server error occurred.");
} catch (Throwable $e) {
error_log("[Tester Login Error] " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
jsonError("Server error occurred: " . $e->getMessage() . " in " . basename($e->getFile()) . ":" . $e->getLine());
} finally {
$stmt = null;
$con = null;
+2 -2
View File
@@ -7,7 +7,7 @@
*/
//register_driver_and_car.php
$allowRegistration = true;
require_once __DIR__ . '/../../../connect.php';
require_once __DIR__ . '/../../connect.php';
header('Content-Type: application/json; charset=utf-8');
// Rate Limiting: الحماية من التسجيل العشوائي وهجمات الـ Bots
@@ -128,7 +128,7 @@ try {
// ======================================================
// Step 1.5: التحقق الفعلي من ملكية رقم الهاتف قبل إكمال المعالجة (سد الثغرة)
// ======================================================
require_once __DIR__ . '/../../../core/Auth/EncryptionHelper.php';
require_once __DIR__ . '/../../core/Security/EncryptionHelper.php';
$tempEncryptionHelper = new EncryptionHelper($redis);
$phoneNumber_encrypted_check = $tempEncryptionHelper->encryptData($data['phone']);
+72 -5
View File
@@ -147,7 +147,70 @@ try {
$stmtUpd = $con->prepare($sqlUpdate);
$stmtUpd->bindParam(':id', $matchedRowId, PDO::PARAM_INT);
$stmtUpd->execute();
jsonSuccess(null, "Your phone number has been verified.");
// ═══════════════════════════════════════════════════════
// تغيير الجهاز: التحقق وحده لا يكفي. لا بد من ربط بصمة
// الجهاز الجديد ثم إصدار توكن driver — وإلا بقي التطبيق
// على توكن التسجيل ورجع كل endpoint محمي 403، وهو ما كان
// يجعل شاشة OTP "تنجح" ثم لا يعمل شيء بعدها.
// ═══════════════════════════════════════════════════════
$chk = $con->prepare("SELECT id FROM driver WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)");
global $blindIndex;
$phoneBidx = $blindIndex ? $blindIndex->index('driver.phone', $phone_number) : null;
$chk->execute([$encryptionHelper->encryptData($phone_number), $phoneBidx, $phoneBidx]);
$driverRow = $chk->fetch(PDO::FETCH_ASSOC);
if (!$driverRow) {
jsonSuccess(null, "Your phone number has been verified.");
exit;
}
$driverID = (string)$driverRow['id'];
$fpRaw = filterRequest("fingerprint") ?? filterRequest("fingerPrint") ?? ($_SERVER['HTTP_X_DEVICE_FP'] ?? null);
if ($fpRaw === null && function_exists('getallheaders')) {
$hdrs = array_change_key_case(getallheaders(), CASE_LOWER);
$fpRaw = $hdrs['x-device-fp'] ?? null;
}
if (empty($fpRaw)) {
jsonError("Device fingerprint is required to complete device change.");
exit;
}
$fpPepper = getenv('FP_PEPPER') ?: '';
$fpHashed = !empty($fpPepper) ? hash('sha256', $fpRaw . $fpPepper) : $fpRaw;
$fcmToken = filterRequest("token");
// ربط الجهاز الجديد — نفس الصيغة التي يتوقعها loginJwtDriver.
$hasRow = $con->prepare("SELECT id FROM driverToken WHERE captain_id = ?");
$hasRow->execute([$driverID]);
if ($hasRow->fetch(PDO::FETCH_ASSOC)) {
if (!empty($fcmToken)) {
$con->prepare("UPDATE driverToken SET fingerPrint = ?, token = ? WHERE captain_id = ?")
->execute([$fpHashed, $encryptionHelper->encryptData($fcmToken), $driverID]);
} else {
$con->prepare("UPDATE driverToken SET fingerPrint = ? WHERE captain_id = ?")
->execute([$fpHashed, $driverID]);
}
} else {
$con->prepare("INSERT INTO driverToken (token, fingerPrint, captain_id, created_at) VALUES (?, ?, ?, NOW())")
->execute([
!empty($fcmToken) ? $encryptionHelper->encryptData($fcmToken) : '',
$fpHashed,
$driverID,
]);
}
$audDriver = filterRequest("aud") ?: filterRequest("audience") ?: (getenv('allowedDriver2') ?: 'driver-app:ios');
$jwtSvc = new JwtService($redis);
$accessToken = $jwtSvc->generateAccessToken($driverID, 'driver', $audDriver, $fpRaw);
jsonSuccess([
"driverID" => $driverID,
"jwt" => $accessToken,
"access_token" => $accessToken,
], "Your phone number has been verified.");
} else {
jsonError("Your phone number could not be verified or the code is expired. Please try again.");
}
@@ -177,8 +240,10 @@ try {
$isRegistered = false;
$driverData = null;
$chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM driver WHERE phone = ?");
$chkStmt->execute([$encryptionHelper->encryptData($phone_number)]);
$chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM driver WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)");
global $blindIndex;
$phoneBidx = $blindIndex ? $blindIndex->index('driver.phone', $phone_number) : null;
$chkStmt->execute([$encryptionHelper->encryptData($phone_number), $phoneBidx, $phoneBidx]);
$driver = $chkStmt->fetch(PDO::FETCH_ASSOC);
// Generate driverID for unregistered users (hash of phone)
@@ -273,8 +338,10 @@ try {
$passengerData = null;
$passengerID = '';
$chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM passengers WHERE phone = ?");
$chkStmt->execute([$encryptionHelper->encryptData($phone_number)]);
$chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM passengers WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)");
global $blindIndex;
$phoneBidx = $blindIndex ? $blindIndex->index('passengers.phone', $phone_number) : null;
$chkStmt->execute([$encryptionHelper->encryptData($phone_number), $phoneBidx, $phoneBidx]);
$passenger = $chkStmt->fetch(PDO::FETCH_ASSOC);
if ($passenger) {
+6 -2
View File
@@ -4,6 +4,10 @@ require_once __DIR__ . '/../connect.php';
$platform = filterRequest("platform");
$appName = filterRequest("appName");
// ‏كان platform وappName يُحقنان مباشرة في نص الاستعلام ثم يُمرَّر إلى prepare
// ‏بلا معاملات مربوطة — حقن SQL مكتمل الأركان. وfilterRequest لا تنظّف النص
// ‏عمداً (انظر core/helpers.php: "بدون htmlspecialchars — نتركه لـ PDO")،
// ‏فالحماية الوحيدة المفترضة هنا هي الربط، ولم تكن موجودة.
$sql = "SELECT
`id`,
`platform`,
@@ -13,10 +17,10 @@ $sql = "SELECT
FROM
`packageInfo`
WHERE
platform='$platform' and appName='$appName';";
`platform` = ? AND `appName` = ?";
$stmt = $con->prepare($sql);
$stmt->execute();
$stmt->execute([$platform, $appName]);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($stmt->rowCount() > 0) {
@@ -9,18 +9,16 @@ $password = filterRequest("password");
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
$audience = filterRequest('aud') ?: 'siro_passenger';
// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP
// 1. حد معدل الطلبات مطبّق على الجميع (الحد مرفوع إلى 30/دقيقة في RateLimiter)
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'tester_login');
if (!$email || !$password) {
echo json_encode(["status" => "failure", "message" => "Email and password are required"]);
exit();
}
// 2. التحقق من أن الحساب مخصص للفحص فقط (isTest check)
$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: '';
$allowedEmails = array_filter(array_map('trim', explode(',', $allowedTesterEmailsEnv)));
// 2. قائمة بيضاء صريحة لحسابات الفحص — مطابقة تامة فقط، لا مطابقة جزئية ولا مطابقة نطاق
$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ($_ENV['ALLOWED_TESTER_EMAILS'] ?? '');
$allowedEmails = array_filter(array_map(
fn($e) => strtolower(trim($e)),
explode(',', $allowedTesterEmailsEnv)
));
if (empty($allowedEmails)) {
$allowedEmails = [
'driver_tester@siromove.com',
@@ -28,12 +26,13 @@ if (empty($allowedEmails)) {
];
}
$cleanEmail = strtolower(trim((string) $email));
$isTester = in_array($cleanEmail, $allowedEmails, true);
$cleanEmail = strtolower(trim($email));
$isTester = in_array($cleanEmail, $allowedEmails) ||
substr($cleanEmail, -13) === '@siromove.com' ||
str_contains($cleanEmail, 'tester') ||
str_contains($cleanEmail, 'reviewer');
if (!$email || !$password) {
echo json_encode(["status" => "failure", "message" => "Email and password are required"]);
exit();
}
try {
$con = Database::get('main');
@@ -121,7 +120,7 @@ try {
http_response_code(500);
echo json_encode([
"status" => "failure",
"message" => "Server error: " . $e->getMessage() . " in " . basename($e->getFile()) . " on line " . $e->getLine()
"message" => "Server error. Please try again."
]);
}
exit();
+3 -2
View File
@@ -63,7 +63,7 @@ try {
$firstName_encrypted = $encryptionHelper->encryptData($firstName);
$lastName_encrypted = $encryptionHelper->encryptData($lastName);
$email_encrypted = $encryptionHelper->encryptData($email);
$uniqueId = substr(md5($phoneNumber), 0, 20);
$uniqueId = substr(md5($phoneNumber), 0, 16);
$password_hashed = password_hash($email . $uniqueId, PASSWORD_DEFAULT);
$unknown_encrypted = $encryptionHelper->encryptData("unknown yet");
@@ -77,12 +77,13 @@ try {
// (مهلة أوسع من صلاحية الرمز نفسه [5 دقائق] لإعطاء وقت كافٍ لإكمال
// نموذج التسجيل بعد التحقق مباشرة).
$step = 4.5;
$otpSearchKey = otpPhoneKey($phoneNumber);
$verifyCheckStmt = $con->prepare(
"SELECT id FROM phone_verification_passenger
WHERE phone_number = ? AND verified = 1 AND created_at > DATE_SUB(NOW(), INTERVAL 30 MINUTE)
LIMIT 1"
);
$verifyCheckStmt->execute([$phoneNumber_encrypted]);
$verifyCheckStmt->execute([$otpSearchKey]);
if ($verifyCheckStmt->rowCount() === 0) {
error_log("$logTag Step 4.5 Error: Phone number not verified via OTP.");
jsonError("Phone number must be verified before registration.");
+1 -1
View File
@@ -1,6 +1,6 @@
<?php
// File: send_otp.php (بديل عن النسخة المعتمدة على RaseelPlus)
require_once __DIR__ . '/../../connect.php';
require_once __DIR__ . '/../../../connect.php';
/* 1) توليد رمز التحقق (3 خانات) */
$otp = (string)random_int(100, 999);
+1 -1
View File
@@ -1,7 +1,7 @@
<?php
// File: verify_otp.php (with enhanced logging)
// siro_v1/auth/token_passenger
require_once __DIR__ . '/../../connect.php';
require_once __DIR__ . '/../../../connect.php';
// --- Start of Script Execution ---
error_log("--- [verify_otp.php] Script execution started. ---");