Files
Siro/backend/auth/passenger/register.php
T
Hamza-AyedandClaude Opus 5 35a66935aa Repair verification joins broken by the OTP key change; extend backfill
Storing the verification phone as a keyed HMAC fixed OTP lookups but broke
every query that joined those tables back to the account, because
phone_verification*.phone_number no longer holds the same value as
driver.phone / passengers.phone. Six joins were affected, and four of them
feed the `verified` flag that the rider and driver apps check at sign-in — so
this was already failing under the current CBC mode, not only after a switch
to GCM.

Accounts now carry phone_key, computed exactly as otpPhoneKey() does, and the
joins match on it. It is written at registration for both apps and populated
for existing rows by the backfill.

The backfill also covers the columns added for the remaining lookups:
users.email_bidx/phone_bidx and driver.national_bidx, which were migrated but
never populated, and honours a per-field prefix so phone_key reproduces
otpPhoneKey's exact output.

Insert column/value counts verified with a paren-aware parser after editing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:40:53 +03:00

199 lines
8.6 KiB
PHP

<?php
// File: register_passenger.php
// إعدادات إظهار الأخطاء
ini_set('display_errors', 0);
error_reporting(E_ALL);
$allowRegistration = true;
require_once __DIR__ . '/../../connect.php';
// Rate Limiting: الحماية من البرمجيات الخبيثة والتسجيل العشوائي
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'register_passenger');
// تعريف بادئة للوج (Tag) لسهولة البحث عنها في ملف الأخطاء
$logTag = "[Register_Debug_passenger]";
$step = 0;
try {
// ======================================================
// Step 1: استقبال البيانات
// ======================================================
$step = 1;
$phoneNumber = filterRequest("phone_number");
$firstName = filterRequest("first_name");
$lastName = filterRequest("last_name");
$email = filterRequest("email");
// طباعة وصول البيانات (مع إخفاء جزء من الرقم)
error_log("$logTag Step 1: Received request. Phone: " . substr($phoneNumber, 0, 7) . "*****");
// ======================================================
// Step 2: التحقق من المدخلات
// ======================================================
$step = 2;
if (empty($phoneNumber) || empty($firstName) || empty($lastName)) {
error_log("$logTag Step 2 Error: Missing required fields.");
jsonError("Required fields are missing.");
exit();
}
// ======================================================
// Step 3: معالجة الإيميل
// ======================================================
$step = 3;
if (empty($email)) {
$email = $phoneNumber . '@intaleqapp.com';
error_log("$logTag Step 3: Email was empty, generated default: " . substr($email, 0, 5) . "***");
}
// ======================================================
// Step 4: تشفير البيانات
// ======================================================
$step = 4;
error_log("$logTag Step 4: Encrypting data...");
if (!isset($encryptionHelper)) {
throw new Exception("Encryption Helper class is missing.");
}
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
$firstName_encrypted = $encryptionHelper->encryptData($firstName);
$lastName_encrypted = $encryptionHelper->encryptData($lastName);
$email_encrypted = $encryptionHelper->encryptData($email);
$uniqueId = substr(md5($phoneNumber), 0, 20);
$password_hashed = password_hash($email . $uniqueId, PASSWORD_DEFAULT);
$unknown_encrypted = $encryptionHelper->encryptData("unknown yet");
// ======================================================
// Step 4.5: التحقق الفعلي من ملكية رقم الهاتف (🔥 Fix)
// ======================================================
// كانت هذه النقطة تسمح بإنشاء حساب راكب بأي رقم هاتف بدون إثبات
// ملكيته فعلياً — auth/otp/verify.php يُعلّم الصف verified=1 لكن
// register_passenger.php لم يكن يتحقق من ذلك إطلاقاً. الآن نشترط
// وجود صف تحقق ناجح (verified=1) لنفس رقم الهاتف خلال آخر 30 دقيقة
// (مهلة أوسع من صلاحية الرمز نفسه [5 دقائق] لإعطاء وقت كافٍ لإكمال
// نموذج التسجيل بعد التحقق مباشرة).
$step = 4.5;
$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]);
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.");
exit();
}
// ======================================================
// Step 5: إنشاء ID فريد
// ======================================================
$step = 5;
// $uniqueId = substr(md5(uniqid(mt_rand(), true)), 0, 20);
// $uniqueId is now generated earlier
error_log("$logTag Step 5: Generated Unique ID: $uniqueId");
// ======================================================
// Step 6: التحقق من وجود المستخدم (Database Check)
// ======================================================
$step = 6;
// كشف التكرار عبر الفهرس الأعمى + المقارنة القديمة: بدون الفهرس يفشل
// الكشف بعد الانتقال إلى GCM فيُسمح بتسجيل نفس الرقم مرتين.
global $blindIndex;
$phoneBidx = $blindIndex ? $blindIndex->index('passengers.phone', $phoneNumber) : null;
$emailBidx = $blindIndex ? $blindIndex->index('passengers.email', $email) : null;
$nameBidx = $blindIndex ? $blindIndex->index('passengers.name', trim("$firstName $lastName")) : null;
// مفتاح ربط جداول التحقق — يجب أن يطابق otpPhoneKey() حرفياً
$phoneKey = otpPhoneKey($phoneNumber);
$checkStmt = $con->prepare(
"SELECT id FROM passengers WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)"
);
$checkStmt->execute([$phoneNumber_encrypted, $phoneBidx, $phoneBidx]);
if ($checkStmt->rowCount() > 0) {
error_log("$logTag Step 6 Error: User already exists.");
jsonError("User with this phone number or email already exists.");
exit();
}
// ======================================================
// Step 7: الإضافة (Insert User)
// ======================================================
$step = 7;
error_log("$logTag Step 7: Inserting into passengers table...");
$insertStmt = $con->prepare("
INSERT INTO passengers (id, first_name, last_name, email, phone, password, gender, birthdate, site, sosPhone, education, employmentType, maritalStatus, status, created_at, updated_at, phone_bidx, email_bidx, name_bidx, phone_key)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NOW(), NOW(), ?, ?, ?, ?)
");
$success = $insertStmt->execute([
$uniqueId,
$firstName_encrypted,
$lastName_encrypted,
$email_encrypted,
$phoneNumber_encrypted,
$password_hashed,
$unknown_encrypted,
$unknown_encrypted,
$unknown_encrypted,
$unknown_encrypted,
$unknown_encrypted,
$unknown_encrypted,
$unknown_encrypted,
// فهارس البحث: تُكتب مع السجل حتى يكون قابلاً للبحث فوراً
$phoneBidx,
$emailBidx,
$nameBidx,
$phoneKey
]);
if (!$success) {
$errorInfo = $insertStmt->errorInfo();
// طباعة تفاصيل خطأ الـ SQL في اللوج
error_log("$logTag Step 7 Error: SQL Insert Failed. Details: " . json_encode($errorInfo));
jsonError("Failed to create user account.");
exit();
}
// ======================================================
// Step 9: جلب البيانات لإعادتها
// ======================================================
$step = 9;
$userStmt = $con->prepare("SELECT * FROM passengers WHERE id = ?");
$userStmt->execute([$uniqueId]);
$newUser = $userStmt->fetch(PDO::FETCH_ASSOC);
// ======================================================
// Step 10: فك التشفير وإرسال الرد
// ======================================================
$step = 10;
if ($newUser) {
unset($newUser['password']);
foreach ($newUser as $key => &$value) {
if ($key !== 'id' && $key !== 'status' && $key !== 'created_at' && $key !== 'updated_at' && !is_null($value)) {
$value = $encryptionHelper->decryptData($value);
}
}
}
error_log("$logTag Success: User registered successfully.");
jsonSuccess(["status" => "registration_success", "data" => $newUser]);
} catch (PDOException $e) {
// طباعة خطأ قاعدة البيانات في اللوج
error_log("$logTag PDO Exception at Step $step: " . $e->getMessage());
jsonError("Database Error.");
} catch (Exception $e) {
// طباعة الأخطاء العامة في اللوج
error_log("$logTag General Exception at Step $step: " . $e->getMessage());
jsonError("General Error.");
}
?>