Refactor(Auth): Complete auth folder restructuring, security patches, and Flutter endpoint updates

This commit is contained in:
Hamza-Ayed
2026-07-09 05:22:05 +03:00
parent 45859883a5
commit 87dc925ea7
37 changed files with 40 additions and 31 deletions
+108
View File
@@ -0,0 +1,108 @@
<?php
require_once __DIR__ . '/../connect.php';
// لا نستقبل id أو email من التطبيق بل نأخذهم من التوكن (JWT) لزيادة الأمان
$platform = filterRequest("platform") ?: 'unknown';
$appName = filterRequest("appName") ?: 'unknown';
// الاعتماد كلياً على الـ ID المستخرج من JWT داخل connect.php
$id = $user_id;
if ($id === 'new') {
if (isset($decoded->sub) && $decoded->sub !== 'new') {
$id = $decoded->sub;
} else {
$id = filterRequest("passengerID") ?: filterRequest("passengerId");
}
}
// تجهيز الاستعلام
$sql = "SELECT
p.`id`,
p.`phone`,
p.`email`,
p.`gender`,
p.`status`,
p.`birthdate`,
p.`site`,
p.`first_name`,
p.`last_name`,
p.`sosPhone`,
p.`education`,
p.`employmentType`,
p.`maritalStatus`,
p.`created_at`,
p.`updated_at`,
phone_verification_passenger.verified,
invitesToPassengers.isInstall,
invitesToPassengers.inviteCode,
invitesToPassengers.isGiftToken,
(SELECT `version` FROM `packageInfo` WHERE platform = :platform AND appName = :appName) AS package,
promos.promo_code AS promo,
promos.amount AS discount,
promos.validity_end_date AS validity,
t.token AS fcm_token,
t.fingerPrint AS fcm_fingerprint
FROM passengers p
LEFT JOIN phone_verification_passenger
ON phone_verification_passenger.phone_number = p.phone
LEFT JOIN invitesToPassengers
ON invitesToPassengers.inviterPassengerPhone = p.phone
LEFT JOIN promos
ON promos.passengerID = p.id
LEFT JOIN tokens t
ON t.passengerID = p.id
WHERE p.id = :id
LIMIT 1";
// تنفيذ الاستعلام
$stmt = $con->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->bindParam(':appName', $appName);
$stmt->bindParam(':platform', $platform);
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$count = $stmt->rowCount();
// تجهيز الرد
header('Content-Type: application/json');
if ($count > 0) {
foreach ($data as &$row) {
// فك تشفير الحقول الحساسة
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
$row['email'] = $encryptionHelper->decryptData($row['email']);
$row['gender'] = $encryptionHelper->decryptData($row['gender']);
$row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
$row['site'] = $encryptionHelper->decryptData($row['site']);
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
$row['sosPhone'] = $encryptionHelper->decryptData($row['sosPhone']);
$row['education'] = $encryptionHelper->decryptData($row['education']);
$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
$row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']);
// فك تشفير توكن FCM إذا وجد
if (!empty($row['fcm_token'])) {
$row['fcm_token'] = $encryptionHelper->decryptData($row['fcm_token']);
}
}
echo json_encode([
"status" => "success",
"count" => $count,
"data" => $data
]);
} else {
error_log("User does not exist: " . $email);
echo json_encode([
"status" => "Failure",
"data" => "User does not exist."
]);
}
// تنظيف الموارد
$stmt = null;
$con = null;
exit();
@@ -0,0 +1,169 @@
<?php
// loginUsingCredentialsWithoutGooglePassenger.php
// مسار مخصص لفاحصي التطبيق (الركاب) يعمل بدون JWT Interceptors
require_once __DIR__ . '/../core/bootstrap.php';
$email = filterRequest("email");
$password = filterRequest("password");
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
$audience = filterRequest('aud') ?: 'siro_passenger';
// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP
$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)));
if (empty($allowedEmails)) {
$allowedEmails = [
'driver_tester@siromove.com',
'passenger_tester@siromove.com',
];
}
$cleanEmail = strtolower(trim($email));
$isTester = in_array($cleanEmail, $allowedEmails) ||
substr($cleanEmail, -13) === '@siromove.com' ||
str_contains($cleanEmail, 'tester') ||
str_contains($cleanEmail, 'reviewer');
try {
$con = Database::get('main');
// تشفير الإيميل للبحث في قاعدة البيانات
$encryptedEmail = $encryptionHelper->encryptData($email);
// Auto-seed/create tester passenger if it doesn't exist
if ($cleanEmail === 'passenger_tester@siromove.com') {
$stmtCheck = $con->prepare("SELECT id FROM passengers WHERE email = :email LIMIT 1");
$stmtCheck->bindParam(':email', $encryptedEmail);
$stmtCheck->execute();
if (!$stmtCheck->fetch()) {
$passengerId = 'tester_passenger_id_2026';
$phone = '+962790000003';
$hashedPassword = password_hash('SiroPassenger2026!', PASSWORD_DEFAULT);
$encryptedPhone = $encryptionHelper->encryptData($phone);
$encryptedFirstName = $encryptionHelper->encryptData('Passenger');
$encryptedLastName = $encryptionHelper->encryptData('Tester');
$encryptedGender = $encryptionHelper->encryptData('Male');
$encryptedBirthdate = $encryptionHelper->encryptData('1990-01-01');
$encryptedSite = $encryptionHelper->encryptData('Jordan');
// Insert passenger with verified = 1 so app doesn't reject
$insert = $con->prepare("INSERT INTO passengers (id, phone, email, password, gender, birthdate, site, first_name, last_name, is_test, verified)
VALUES (:id, :phone, :email, :password, :gender, :birthdate, :site, :first_name, :last_name, 1, 1)");
$insert->execute([
':id' => $passengerId,
':phone' => $encryptedPhone,
':email' => $encryptedEmail,
':password' => $hashedPassword,
':gender' => $encryptedGender,
':birthdate' => $encryptedBirthdate,
':site' => $encryptedSite,
':first_name' => $encryptedFirstName,
':last_name' => $encryptedLastName
]);
// Ensure phone_verification_passenger row exists
$stmtPhone = $con->prepare("SELECT * FROM phone_verification_passenger WHERE phone_number = :phone LIMIT 1");
$stmtPhone->bindParam(':phone', $encryptedPhone);
$stmtPhone->execute();
if (!$stmtPhone->fetch()) {
$insertPhone = $con->prepare("INSERT INTO phone_verification_passenger (phone_number, verified) VALUES (:phone, 1)");
$insertPhone->bindParam(':phone', $encryptedPhone);
$insertPhone->execute();
} else {
$updatePhone = $con->prepare("UPDATE phone_verification_passenger SET verified = 1 WHERE phone_number = :phone");
$updatePhone->bindParam(':phone', $encryptedPhone);
$updatePhone->execute();
}
}
}
$sql = "SELECT
p.*,
phone_verification_passenger.verified,
invitesToPassengers.isInstall,
invitesToPassengers.inviteCode,
invitesToPassengers.isGiftToken
FROM passengers p
LEFT JOIN phone_verification_passenger
ON phone_verification_passenger.phone_number = p.phone
LEFT JOIN invitesToPassengers
ON invitesToPassengers.inviterPassengerPhone = p.phone
WHERE p.email = :email
LIMIT 1";
$stmt = $con->prepare($sql);
$stmt->bindParam(':email', $encryptedEmail);
$stmt->execute();
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data) {
// فحص الباسورد
if (password_verify($password, $data['password']) || $password === $data['password']) {
// التحقق من أن الحساب معلم كحساب فحص في قاعدة البيانات أو البيئة
$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();
}
// فك تشفير البيانات للرد
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']);
if(isset($data['sosPhone'])) $data['sosPhone'] = $encryptionHelper->decryptData($data['sosPhone']);
if(isset($data['education'])) $data['education'] = $encryptionHelper->decryptData($data['education']);
if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
// Force verified = 1 for the test user so the Rider app doesn't reject the login
if (isset($data['is_test']) && $data['is_test'] == 1) {
$data['verified'] = 1;
}
// توليد الـ JWT بصلاحية (tester) لتميزهم عن المستخدمين الفعليين
$jwtService = new JwtService($redis);
$jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint);
echo json_encode([
"status" => "success",
"jwt" => $jwt,
"data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة
], JSON_UNESCAPED_UNICODE);
} else {
echo json_encode([
"status" => "failure",
"message" => "Invalid credentials"
]);
}
} else {
echo json_encode([
"status" => "failure",
"message" => "Invalid credentials"
]);
}
} catch (Exception $e) {
error_log("Error in loginUsingCredentialsWithoutGooglePassenger: " . $e->getMessage());
echo json_encode([
"status" => "failure",
"message" => "Server error"
]);
}
exit();
+182
View File
@@ -0,0 +1,182 @@
<?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);
$password_hashed = password_hash($email, 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 = substr(md5($phoneNumber_encrypted), 0, 20);
error_log("$logTag Step 5: Generated Unique ID: $uniqueId");
// ======================================================
// Step 6: التحقق من وجود المستخدم (Database Check)
// ======================================================
$step = 6;
$checkStmt = $con->prepare("SELECT id FROM passengers WHERE phone = ?");
$checkStmt->execute([$phoneNumber_encrypted]);
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)
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
]);
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.");
}
?>
+30
View File
@@ -0,0 +1,30 @@
<?php
require_once __DIR__ . '/../connect.php';
$email = filterRequest("email");
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
$headers .= "From: SEFER Team" . "\r\n";
// Create the email subject and body
$subject = 'Your SEFER account has been deleted';
$body = '
Dear passenger,
We are sorry to see you go, but we respect your decision to delete your SEFER account.
We would like to thank you for using our platform and for being a part of the SEFER community. We hope that you had a positive experience and that we were able to make your travels easier and more enjoyable.
If you have any questions or concerns, please do not hesitate to contact us.
Sincerely,
The SEFER Team
';
// Send the email
mail($email, $subject, $body);
?>
+38
View File
@@ -0,0 +1,38 @@
<?php
require_once __DIR__ . '/../connect.php';
// Ensure the caller has a valid user_id
if (empty($user_id)) {
jsonError("Unauthorized", 401);
exit;
}
$latitude = filterRequest("latitude");
$longitude = filterRequest("longitude");
// Validate inputs
if ($latitude === '' || $longitude === '') {
jsonError("Latitude and longitude are required", 400);
exit;
}
try {
// Insert location log
$sql = "INSERT INTO `passenger_opening_locations` (`passenger_id`, `latitude`, `longitude`)
VALUES (:passenger_id, :latitude, :longitude)";
$stmt = $con->prepare($sql);
$stmt->bindParam(':passenger_id', $user_id);
$stmt->bindParam(':latitude', $latitude);
$stmt->bindParam(':longitude', $longitude);
if ($stmt->execute()) {
jsonSuccess(null, "Location logged successfully");
} else {
jsonError("Failed to log location", 500);
}
} catch (PDOException $e) {
error_log("Database Error in save_passenger_location.php: " . $e->getMessage());
jsonError("An error occurred while logging location", 500);
}
?>
+88
View File
@@ -0,0 +1,88 @@
<?php
// File: send_otp.php (بديل عن النسخة المعتمدة على RaseelPlus)
require_once __DIR__ . '/../../connect.php';
/* 1) توليد رمز التحقق (3 خانات) */
$otp = (string)random_int(100, 999);
$receiver = filterRequest("receiver");
if (empty($receiver)) {
jsonError('Phone number is required.');
exit();
}
/* 2) إرسال عبر بوابة الفلاش كول / واتساب */
$nabehUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
$appKey = getenv('NABEH_OTP_APP_KEY');
$payload = [
'phone' => $receiver,
'device_type' => 'android',
'method' => 'whatsapp',
'code' => $otp
];
$ch = curl_init($nabehUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-App-Key: $appKey"
],
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5
]);
$res = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("⚠️ [Flash Call OTP Token Passenger] Curl Error: $error");
jsonError('Failed to connect to OTP service');
exit;
}
$decoded = json_decode((string)$res, true);
$sentOK = ($httpCode === 200 && ($decoded['success'] ?? false));
if ($sentOK) {
/* 3) حفظ الرمز في Redis + قاعدة البيانات */
$receiver_enc = $encryptionHelper->encryptData($receiver);
$otp_enc = $encryptionHelper->encryptData($otp);
$exp = date('Y-m-d H:i:s', strtotime('+5 minutes'));
$now = date('Y-m-d H:i:s');
try {
// Save to MySQL
$con->prepare("DELETE FROM token_verification WHERE phone_number = ?")
->execute([$receiver_enc]);
$stmt = $con->prepare("
INSERT INTO token_verification
(phone_number, token, expiration_time, verified, created_at)
VALUES (?, ?, ?, 0, ?)
");
$stmt->execute([$receiver_enc, $otp_enc, $exp, $now]);
// Also save to Redis for verify_otp.php compatibility
if ($redis) {
$redis->setex("otp:passenger:$receiver", 300, $otp);
}
jsonSuccess(null, 'OTP sent and saved successfully');
} catch (PDOException $e) {
error_log("[send_otp.php] " . $e->getMessage());
jsonError('OTP sent but failed to save to database');
}
} else {
$errMsg = $decoded['message'] ?? 'Unknown error';
jsonError('Failed to send OTP');
}
?>
@@ -0,0 +1,80 @@
<?php
// File: verify_otp.php (with enhanced logging)
// siro_v1/auth/token_passenger
require_once __DIR__ . '/../../connect.php';
// --- Start of Script Execution ---
error_log("--- [verify_otp.php] Script execution started. ---");
$phoneNumber = filterRequest("phone_number");
$otp = filterRequest("otp");
// Log received data for debugging. Be mindful of logging sensitive data in production.
error_log("[verify_otp.php] Received phone_number: $phoneNumber | Received otp: $otp");
if (empty($phoneNumber) || empty($otp)) {
error_log("[verify_otp.php] Error: Phone number or OTP is empty.");
jsonError("Phone number and OTP are required.");
exit();
}
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
$otp_encrypted = $encryptionHelper->encryptData($otp);
try {
// 1. التحقق من Redis بدلاً من MySQL
if (!$redis) {
jsonError("Security service unavailable");
exit;
}
$cachedOtp = $redis->get("otp:passenger:$phoneNumber");
if ($cachedOtp && $cachedOtp === $otp) {
// ننجح في التحقق ونحذف المفتاح من Redis لمنع استخدامه مرة أخرى (One-time use)
$redis->del("otp:passenger:$phoneNumber");
error_log("[verify_otp.php] OTP verified via Redis for phone: $phoneNumber");
// 2. التحقق من وجود الراكب في قاعدة البيانات
$passengerStmt = $con->prepare("SELECT id FROM passengers WHERE phone = ?");
$passengerStmt->execute([$phoneNumber_encrypted]);
$passenger = $passengerStmt->fetch(PDO::FETCH_ASSOC);
if ($passenger) {
$passengerID = $passenger['id'];
// تحديث التوكن والبصمة إن وجدا
$newToken = filterRequest("token");
$fingerPrint = filterRequest("fingerPrint");
if ($newToken && $fingerPrint) {
$tokenEncrypted = $encryptionHelper->encryptData($newToken);
$updateTokenStmt = $con->prepare("UPDATE tokens SET token = ?, fingerPrint = ? WHERE passengerID = ?");
$updateTokenStmt->execute([$tokenEncrypted, $fingerPrint, $passengerID]);
}
printSuccess([
"message" => "Token verified and updated.",
"isRegistered" => true,
"passengerID" => $passengerID
]);
} else {
printSuccess([
"message" => "Phone verified, passenger not found.",
"isRegistered" => false
]);
}
} else {
error_log("[verify_otp.php] Invalid or expired OTP for phone: $phoneNumber");
jsonError("Invalid or expired OTP.");
}
} catch (Exception $e) {
// Log the detailed database error message for debugging.
error_log("[verify_otp.php] FATAL DATABASE ERROR: " . $e->getMessage());
jsonError("Database error");
}
?>