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>
This commit is contained in:
Hamza-Ayed
2026-07-25 16:40:53 +03:00
co-authored by Claude Opus 5
parent 8d7e3118b5
commit 35a66935aa
9 changed files with 35 additions and 13 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ try {
CarRegistration.make, CarRegistration.model, CarRegistration.year,
df.is_claimed, inv.isInstall, inv.isGiftToken
FROM driver
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone_key
LEFT JOIN driver_gifts df ON df.driver_id = driver.id
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
LEFT JOIN invites inv ON inv.driverId = driver.id
@@ -52,7 +52,7 @@ try {
CarRegistration.model,
CarRegistration.year
FROM driver
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone_key
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
WHERE
driver.email = :email
+5 -2
View File
@@ -392,6 +392,8 @@ Therefore, do NOT assume a specific field is on the front or the back of a card.
'driver.name',
trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? ''))
) : null;
// مفتاح ربط جداول التحقق — يجب أن يطابق otpPhoneKey() حرفياً
$phoneKey = otpPhoneKey($data['phone'] ?? null);
$toEncryptDriver = [
"phone","email","first_name","last_name","name_arabic","gender",
@@ -439,7 +441,7 @@ Therefore, do NOT assume a specific field is on the front or the back of a card.
first_name, last_name, accountBank, bankCode,
employmentType, ai_data, user_input, maritalStatus,
fullNameMaritial, expirationDate, created_at, updated_at,
phone_bidx, email_bidx, name_bidx
phone_bidx, email_bidx, name_bidx, phone_key
) VALUES (
:id, :phone, :email, :pwd, :gender, :license_type, :national_number,
:name_arabic, :issue_date, :expiry_date, :license_categories,
@@ -447,7 +449,7 @@ Therefore, do NOT assume a specific field is on the front or the back of a card.
:first_name, :last_name, :accountBank, :bankCode,
:employmentType, :ai_data, :user_input, :maritalStatus,
:fullNameMaritial, :expirationDate, NOW(), NOW(),
:phone_bidx, :email_bidx, :name_bidx
:phone_bidx, :email_bidx, :name_bidx, :phone_key
)
";
$insD = $con->prepare($sqlDriver);
@@ -481,6 +483,7 @@ Therefore, do NOT assume a specific field is on the front or the back of a card.
':phone_bidx' => $phoneBidx,
':email_bidx' => $emailBidx,
':name_bidx' => $nameBidx,
':phone_key' => $phoneKey,
]);
if (!$okD) {
$con->rollBack();
+1 -1
View File
@@ -45,7 +45,7 @@ $sql = "SELECT
t.fingerPrint AS fcm_fingerprint
FROM passengers p
LEFT JOIN phone_verification_passenger
ON phone_verification_passenger.phone_number = p.phone
ON phone_verification_passenger.phone_number = p.phone_key
LEFT JOIN invitesToPassengers
ON invitesToPassengers.inviterPassengerPhone = p.phone
LEFT JOIN promos
@@ -53,7 +53,7 @@ try {
invitesToPassengers.isGiftToken
FROM passengers p
LEFT JOIN phone_verification_passenger
ON phone_verification_passenger.phone_number = p.phone
ON phone_verification_passenger.phone_number = p.phone_key
LEFT JOIN invitesToPassengers
ON invitesToPassengers.inviterPassengerPhone = p.phone
WHERE p.email = :email OR (:email_bidx IS NOT NULL AND p.email_bidx = :email_bidx)
+6 -3
View File
@@ -109,6 +109,8 @@ try {
$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 = ?)"
@@ -128,8 +130,8 @@ try {
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)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NOW(), NOW(), ?, ?, ?)
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,
@@ -148,7 +150,8 @@ try {
// فهارس البحث: تُكتب مع السجل حتى يكون قابلاً للبحث فوراً
$phoneBidx,
$emailBidx,
$nameBidx
$nameBidx,
$phoneKey
]);
if (!$success) {
+12 -3
View File
@@ -131,19 +131,27 @@ if (isset($options['audit'])) {
$targets = [
'driver' => [
'phone_bidx' => ['scope' => 'driver.phone', 'columns' => ['phone']],
'email_bidx' => ['scope' => 'driver.email', 'columns' => ['email']],
'name_bidx' => ['scope' => 'driver.name', 'columns' => ['first_name', 'last_name']],
'phone_bidx' => ['scope' => 'driver.phone', 'columns' => ['phone']],
'email_bidx' => ['scope' => 'driver.email', 'columns' => ['email']],
'name_bidx' => ['scope' => 'driver.name', 'columns' => ['first_name', 'last_name']],
'national_bidx' => ['scope' => 'driver.national', 'columns' => ['national_number']],
// نفس نطاق otpPhoneKey: يسمح بربط جداول التحقق بصاحب الحساب
'phone_key' => ['scope' => 'otp.phone', 'columns' => ['phone'], 'prefix' => 'K:'],
],
'passengers' => [
'phone_bidx' => ['scope' => 'passengers.phone', 'columns' => ['phone']],
'email_bidx' => ['scope' => 'passengers.email', 'columns' => ['email']],
'name_bidx' => ['scope' => 'passengers.name', 'columns' => ['first_name', 'last_name']],
'phone_key' => ['scope' => 'otp.phone', 'columns' => ['phone'], 'prefix' => 'K:'],
],
'adminUser' => [
'phone_bidx' => ['scope' => 'adminUser.phone', 'columns' => ['phone']],
'email_bidx' => ['scope' => 'adminUser.email', 'columns' => ['email']],
],
'users' => [
'phone_bidx' => ['scope' => 'users.phone', 'columns' => ['phone']],
'email_bidx' => ['scope' => 'users.email', 'columns' => ['email']],
],
];
echo $dryRun ? "── DRY RUN — nothing will be written ──\n" : "── Backfilling blind indexes ──\n";
@@ -202,6 +210,7 @@ foreach ($targets as $table => $fields) {
$value = implode(' ', $parts);
$index = $blind->index($spec['scope'], $value);
if ($index === null) continue;
if (!empty($spec['prefix'])) $index = $spec['prefix'] . $index;
$set[] = "`$column` = :$column";
$params[":$column"] = $index;
+7
View File
@@ -84,6 +84,11 @@ $columns = [
['token_verification', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
['token_verification_driver', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
// مفتاح ربط جداول التحقق: نفس صيغة otpPhoneKey، حتى يمكن ربط
// phone_verification*.phone_number بصاحب الحساب دون مقارنة نصوص مشفّرة.
['driver', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق'"],
['passengers', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق'"],
// بقية الجداول التي يُبحث فيها بحقل مشفّر
['users', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC لبريد موظف الخدمة'"],
['users', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC لهاتف موظف الخدمة'"],
@@ -107,6 +112,8 @@ $indexes = [
['users', 'idx_users_email_bidx', 'email_bidx'],
['users', 'idx_users_phone_bidx', 'phone_bidx'],
['driver', 'idx_driver_national_bidx', 'national_bidx'],
['driver', 'idx_driver_phone_key', 'phone_key'],
['passengers', 'idx_passengers_phone_key', 'phone_key'],
];
$applied = 0;
+1 -1
View File
@@ -22,7 +22,7 @@ $sql = "
FROM
phone_verification pv
LEFT JOIN
driver d ON pv.phone_number = d.phone
driver d ON pv.phone_number = d.phone_key
LEFT JOIN
notesForDriverService n ON pv.phone_number = n.phone
WHERE