diff --git a/backend/auth/otp/request.php b/backend/auth/otp/request.php index 1b0afc6c..bfb3c096 100644 --- a/backend/auth/otp/request.php +++ b/backend/auth/otp/request.php @@ -120,6 +120,9 @@ switch (strtolower($country)) { // 6. DB Storage on Success if ($sentSuccessfully) { $encryptedPhone = otpPhoneKey($receiver); // مفتاح بحث ثابت مستقل عن نمط التشفير + // نسخة قابلة للاسترجاع: خدمة العملاء تتابع من طلب رمزاً ولم يُكمل تسجيله، + // والمفتاح أعلاه أحادي الاتجاه فلا يُستخرج منه الرقم. + $phoneEncStored = $encryptionHelper->encryptData($receiver); $encryptedOtp = $encryptionHelper->encryptDataGCM($otp); // Random GCM $encryptedEmail = !empty($email) ? $encryptionHelper->encryptData($email) : ''; @@ -135,11 +138,12 @@ if ($sentSuccessfully) { $stmtIns = $con->prepare(" INSERT INTO `phone_verification_service` - (`phone_number`, `token_code`, `expiration_time`, `is_verified`, `created_at`) - VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) + (`phone_number`, `phone_enc`, `token_code`, `expiration_time`, `is_verified`, `created_at`) + VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) "); $stmtIns->execute([ $encryptedPhone, + $phoneEncStored, $encryptedOtp ]); } elseif ($user_type === 'driver') { @@ -166,11 +170,12 @@ if ($sentSuccessfully) { // Insert new attempt $stmtIns = $con->prepare(" INSERT INTO `phone_verification` - (`phone_number`, `driverId`, `email`, `token_code`, `expiration_time`, `is_verified`, `created_at`) - VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) + (`phone_number`, `phone_enc`, `driverId`, `email`, `token_code`, `expiration_time`, `is_verified`, `created_at`) + VALUES (?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) "); $stmtIns->execute([ $encryptedPhone, + $phoneEncStored, $driverId ?: '', $encryptedEmail, $encryptedOtp @@ -185,11 +190,12 @@ if ($sentSuccessfully) { // Insert new attempt $stmtIns = $con->prepare(" INSERT INTO `token_verification` - (`phone_number`, `token`, `expiration_time`, `verified`, `created_at`) - VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) + (`phone_number`, `phone_enc`, `token`, `expiration_time`, `verified`, `created_at`) + VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) "); $stmtIns->execute([ $encryptedPhone, + $phoneEncStored, $encryptedOtp ]); } else { @@ -200,11 +206,12 @@ if ($sentSuccessfully) { // Insert new attempt $stmtIns = $con->prepare(" INSERT INTO `phone_verification_passenger` - (`phone_number`, `token`, `expiration_time`, `verified`, `created_at`) - VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) + (`phone_number`, `phone_enc`, `token`, `expiration_time`, `verified`, `created_at`) + VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW()) "); $stmtIns->execute([ $encryptedPhone, + $phoneEncStored, $encryptedOtp ]); } diff --git a/backend/scripts/migrate.php b/backend/scripts/migrate.php index 6e60cef6..acc9ba01 100644 --- a/backend/scripts/migrate.php +++ b/backend/scripts/migrate.php @@ -74,6 +74,16 @@ $columns = [ ['passengers', 'name_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للاسم بعد التطبيع'"], ['adminUser', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالهاتف'"], ['adminUser', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالبريد'"], + // نسخة مشفّرة قابلة للاسترجاع من رقم الهاتف في جداول التحقق. + // عمود phone_number صار يحمل مفتاح بحث أحادي الاتجاه (HMAC)، فلا يمكن + // استرجاع الرقم منه — وخدمة العملاء تحتاجه لمتابعة من طلب رمزاً ولم + // يُكمل تسجيله. + ['phone_verification', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"], + ['phone_verification_passenger', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"], + ['phone_verification_service', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"], + ['token_verification', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"], + ['token_verification_driver', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"], + // أعمدة موافقات المشرفين المفقودة في هذا النشر ['adminUser', 'status', "VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active | pending | suspended | rejected'"], ['adminUser', 'approved_by', "VARCHAR(32) NULL DEFAULT NULL"], diff --git a/backend/serviceapp/getDriversPhoneNotComplete.php b/backend/serviceapp/getDriversPhoneNotComplete.php index d3a57938..62fee7d4 100644 --- a/backend/serviceapp/getDriversPhoneNotComplete.php +++ b/backend/serviceapp/getDriversPhoneNotComplete.php @@ -1,56 +1,61 @@ = (NOW() - INTERVAL 6 DAY) -- تم الإنشاء خلال آخر 3 أيام -ORDER BY RAND() -LIMIT 1; - -"; +$sql = "SELECT id, phone_number, phone_enc, created_at + FROM phone_verification + WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 5 DAY) + ORDER BY created_at DESC + LIMIT 200"; $stmt = $con->prepare($sql); $stmt->execute(); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -if ($stmt->rowCount() > 0) { +$registered = []; +foreach ($con->query("SELECT phone FROM driver WHERE phone IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN) as $enc) { + $plain = $encryptionHelper->decryptData($enc); + if ($plain) $registered[normalizePhone($plain)] = true; +} - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +$contacted = []; +try { + foreach ($con->query("SELECT phone FROM notesForDriverService")->fetchAll(PDO::FETCH_COLUMN) as $enc) { + $plain = $encryptionHelper->decryptData($enc) ?: $enc; + if ($plain) $contacted[normalizePhone($plain)] = true; + } +} catch (PDOException $e) { + error_log('[getDriversPhoneNotComplete] notes unavailable: ' . $e->getMessage()); +} - // فك تشفير أرقام الهاتف والإيميل - foreach ($rows as &$r) { +$result = []; +foreach ($rows as $row) { + $phone = $encryptionHelper->decryptData($row['phone_enc'] ?? null); + if (!$phone) continue; - if (isset($r['phone_number']) && $r['phone_number'] != null) { - $r['phone_number'] = $encryptionHelper->decryptData($r['phone_number']); - } + $key = normalizePhone($phone); + if (isset($registered[$key])) continue; // أكمل تسجيله + if (isset($contacted[$key])) continue; // تم التواصل معه سابقاً - if (isset($r['email']) && $r['email'] != null) { - $r['email'] = $encryptionHelper->decryptData($r['email']); - } - } + $result[] = [ + 'id' => $row['id'], + 'phone_number' => $phone, + 'created_at' => $row['created_at'], + ]; - jsonSuccess($rows); + if (count($result) >= 25) break; +} +if ($result) { + jsonSuccess($result); } else { - jsonError("No phone numbers found in the last 5 days"); + jsonError("No records found"); } -?> diff --git a/backend/serviceapp/getPassengersNotCompleteRegistration.php b/backend/serviceapp/getPassengersNotCompleteRegistration.php index 50925728..df082537 100644 --- a/backend/serviceapp/getPassengersNotCompleteRegistration.php +++ b/backend/serviceapp/getPassengersNotCompleteRegistration.php @@ -1,44 +1,72 @@ = DATE_SUB(CURDATE(), INTERVAL 4 DAY) -ORDER BY - phone_verification_passenger.created_at DESC -LIMIT 25; -"; +/** + * الركاب الذين طلبوا رمز تحقق ولم يُكملوا التسجيل — لمتابعتهم. + * + * سابقاً كان الاستعلام يقارن phone_number (مشفّراً) بعمود phone في جدول + * الركاب، ويجلب الملاحظات بربط على نفس القيمة. هذا يعمل فقط ما دام التشفير + * حتمياً؛ ومع AES-GCM تختلف القيمتان لنفس الرقم فيُصبح كل رقم "غير مسجَّل" + * وتختفي الملاحظات. + * + * الآن: يُقرأ الرقم من phone_enc (نسخة قابلة للاسترجاع)، ثم تتم المطابقة + * والاستبعاد في PHP على الأرقام الأصلية — صحيح تحت أي نمط تشفير. + */ + +$sql = "SELECT id, phone_number, phone_enc, created_at + FROM phone_verification_passenger + WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 4 DAY) + ORDER BY created_at DESC + LIMIT 200"; $stmt = $con->prepare($sql); $stmt->execute(); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -// فك التشفير إذا كان مطلوباً (مثلاً إذا phone_number مشفّر) -foreach ($rows as &$row) { - if (isset($row['phone_number'])) { - $row['phone_number'] = $encryptionHelper->decryptData($row['phone_number']); - } - if (isset($row['note'])) { - $row['note'] = $encryptionHelper->decryptData($row['note']); // إذا كانت مضافة مشفّرة - } +// أرقام الركاب المسجَّلين فعلاً، بصيغتها الأصلية +$registered = []; +foreach ($con->query("SELECT phone FROM passengers WHERE phone IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN) as $enc) { + $plain = $encryptionHelper->decryptData($enc); + if ($plain) $registered[normalizePhone($plain)] = true; } -if ($rows) { - jsonSuccess($rows); +// الملاحظات المسجَّلة سابقاً عن كل رقم +$notes = []; +try { + $noteRows = $con->query("SELECT phone, note, editor, createdAt FROM notesForPassengerService")->fetchAll(PDO::FETCH_ASSOC); + foreach ($noteRows as $n) { + $plain = $encryptionHelper->decryptData($n['phone']) ?: $n['phone']; + if ($plain) $notes[normalizePhone($plain)] = $n; + } +} catch (PDOException $e) { + error_log('[getPassengersNotCompleteRegistration] notes unavailable: ' . $e->getMessage()); +} + +$result = []; +foreach ($rows as $row) { + $phone = $encryptionHelper->decryptData($row['phone_enc'] ?? null); + + // السجلات التي سبقت إضافة phone_enc لا يمكن استرجاع رقمها من المفتاح + if (!$phone) continue; + + $key = normalizePhone($phone); + if (isset($registered[$key])) continue; // أكمل تسجيله فعلاً + + $note = $notes[$key] ?? null; + $result[] = [ + 'id' => $row['id'], + 'phone_number' => $phone, + 'created_at' => $row['created_at'], + 'note' => $note['note'] ?? null, + 'editor' => $note['editor'] ?? null, + 'note_created_at' => $note['createdAt'] ?? null, + ]; + + if (count($result) >= 25) break; +} + +if ($result) { + jsonSuccess($result); } else { - jsonError("No phone verified passengers found"); + jsonError("No records found"); } -?> \ No newline at end of file