- Customer-service notes joined to the account by comparing encrypted phone columns. Both notes tables now carry phone_key, written when a note is saved, and the three joins match on it. - The email_verifications join was comparing a plaintext column against an encrypted one, so it never matched and `verified` was always NULL in both passenger and driver sign-in. It is now resolved in PHP against the decrypted address, which fixes a pre-existing bug rather than only preparing for GCM. - auth/sendVerifyEmail.php built all three of its statements by interpolating the request values into SQL. Any caller could inject through the email or token field. Now parameterised. - serviceapp/register.php duplicate detection consults the users indexes and writes them with the row. Sweep confirms no join or lookup compares two encrypted columns any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
4.5 KiB
PHP
105 lines
4.5 KiB
PHP
<?php
|
|
/**
|
|
* serviceapp/register.php
|
|
* التسجيل الذاتي لموظفي خدمة العملاء - الحساب يكون بحالة pending بانتظار موافقة الإدارة
|
|
*/
|
|
require_once __DIR__ . '/../core/bootstrap.php';
|
|
|
|
$firstName = filterRequest('first_name');
|
|
$lastName = filterRequest('last_name');
|
|
$email = filterRequest('email');
|
|
$phone = filterRequest('phone');
|
|
$password = filterRequest('password');
|
|
$fingerprint = filterRequest('fingerprint');
|
|
|
|
if (empty($firstName) || empty($lastName) || empty($email) || empty($phone) || empty($password) || empty($fingerprint)) {
|
|
jsonError("All fields are required.");
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
// 1. التحقق من البيئة (Environment Whitelist)
|
|
$allowedPhonesStr = getenv('AUTHORIZED_SERVICE_PHONES');
|
|
if (!$allowedPhonesStr) {
|
|
jsonError("غير مصرح لك بالتسجيل كموظف خدمة (القائمة البيضاء غير معدة).");
|
|
exit;
|
|
}
|
|
|
|
$allowedPhones = array_map('trim', explode(',', $allowedPhonesStr));
|
|
if (!in_array($phone, $allowedPhones)) {
|
|
jsonError("أنت غير مصرح لك بالتسجيل كموظف خدمة. يرجى مراجعة الإدارة.");
|
|
exit;
|
|
}
|
|
|
|
$con = Database::get('main');
|
|
|
|
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق البريد الإلكتروني، الهاتف أو البصمة)
|
|
$fpHash = hash('sha256', $fingerprint);
|
|
global $blindIndex;
|
|
$emailBidx = $blindIndex ? $blindIndex->index('users.email', $email) : null;
|
|
$phoneBidx = $blindIndex ? $blindIndex->index('users.phone', $phone) : null;
|
|
|
|
// كشف التكرار عبر الفهرس أيضاً: بدونه يُقبل نفس البريد مرتين تحت التشفير
|
|
// العشوائي لأن النصين المشفّرين لن يتطابقا.
|
|
$check = $con->prepare(
|
|
"SELECT id FROM users
|
|
WHERE email = ? OR phone = ? OR fingerprint_hash = ?
|
|
OR (? IS NOT NULL AND email_bidx = ?)
|
|
OR (? IS NOT NULL AND phone_bidx = ?)
|
|
LIMIT 1"
|
|
);
|
|
|
|
// تشفير الحقول للبحث عنها إذا كانت مشفرة في قاعدة البيانات (حسب تصميم النظام)
|
|
$encEmail = $encryptionHelper->encryptData($email);
|
|
// ملاحظة: البحث بالهاتف والبريد المشفر يتطلب مطابقة دقيقة أو البحث بالـ Hash إذا كان متوفراً
|
|
// هنا سنفترض البحث بالبيانات الممرة مباشرة أو المشفرة حسب ما تقتضيه سياسة connect.php
|
|
|
|
$check->execute([$email, $phone, $fpHash, $emailBidx, $emailBidx, $phoneBidx, $phoneBidx]);
|
|
|
|
if ($check->rowCount() > 0) {
|
|
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
|
|
exit;
|
|
}
|
|
|
|
// 2. تجهيز البيانات
|
|
$id = bin2hex(random_bytes(16));
|
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
|
|
|
// تشفير البيانات الحساسة قبل التخزين
|
|
$encFirstName = $encryptionHelper->encryptData($firstName);
|
|
$encLastName = $encryptionHelper->encryptData($lastName);
|
|
$encEmail = $encryptionHelper->encryptData($email);
|
|
$encPhone = $encryptionHelper->encryptData($phone);
|
|
$encFp = $encryptionHelper->encryptData($fingerprint);
|
|
|
|
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي 0 أو pending)
|
|
$sql = "INSERT INTO users (id, first_name, last_name, email, phone, password, fingerprint, fingerprint_hash, user_type, created_at, email_bidx, phone_bidx)
|
|
VALUES (:id, :fname, :lname, :email, :phone, :pass, :fp, :fp_hash, 'service', NOW(), :email_bidx, :phone_bidx)";
|
|
|
|
|
|
$stmt = $con->prepare($sql);
|
|
$stmt->execute([
|
|
':id' => $id,
|
|
':fname' => $encFirstName,
|
|
':lname' => $encLastName,
|
|
':email' => $encEmail,
|
|
':phone' => $encPhone,
|
|
':pass' => $hashedPassword,
|
|
':fp' => $encFp,
|
|
':fp_hash' => $fpHash,
|
|
':email_bidx' => $emailBidx,
|
|
':phone_bidx' => $phoneBidx
|
|
]);
|
|
|
|
printSuccess([
|
|
"status" => "pending",
|
|
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log("[Service Register Error] " . $e->getMessage());
|
|
jsonError("An internal error occurred. Please try again later.");
|
|
}
|
|
|
|
exit();
|