first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?php
/**
* Admin/auth/approve_admin.php
* الموافقة على أو رفض طلبات انضمام المشرفين
* مسموح فقط للسوبر أدمن
*/
require_once __DIR__ . '/../../connect.php';
if ($role !== 'super_admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden. Super Admin access required.']);
exit;
}
$targetId = filterRequest('admin_id');
$action = filterRequest('action'); // approved, rejected, suspended
if (empty($targetId) || empty($action)) {
jsonError("Admin ID and action are required.");
exit;
}
if (!in_array($action, ['approved', 'rejected', 'suspended'])) {
jsonError("Invalid action.");
exit;
}
try {
$con = Database::get('main');
$sql = "UPDATE adminUser SET status = :status, approved_by = :by, approved_at = NOW() WHERE id = :id";
$stmt = $con->prepare($sql);
$stmt->execute([
':status' => $action,
':by' => $user_id, // السوبر أدمن الحالي
':id' => $targetId
]);
if ($stmt->rowCount() > 0) {
printSuccess(null, "Admin status updated to $action.");
} else {
jsonError("Admin not found or status already updated.");
}
} catch (Exception $e) {
error_log("[Approve Admin Error] " . $e->getMessage());
jsonError("Server Error: " . $e->getMessage());
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Admin/auth/debug_login.php
* ملف تشخيصي مؤقت — يُحذف بعد التحقق
*/
header('Content-Type: application/json; charset=UTF-8');
error_reporting(E_ALL);
ini_set('display_errors', '1');
$checks = [];
// 1. التحقق من ملف bootstrap
$bootstrapPath = __DIR__ . '/../../core/bootstrap.php';
$checks['bootstrap_exists'] = file_exists($bootstrapPath);
// 2. محاولة تحميل bootstrap مع التقاط الأخطاء
try {
ob_start();
require_once $bootstrapPath;
$bootstrapOutput = ob_get_clean();
$checks['bootstrap_loaded'] = true;
$checks['bootstrap_output'] = $bootstrapOutput ?: '(clean)';
} catch (Throwable $e) {
ob_end_clean();
$checks['bootstrap_loaded'] = false;
$checks['bootstrap_error'] = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
}
// 3. التحقق من الدوال المطلوبة
$checks['filterRequest_exists'] = function_exists('filterRequest');
$checks['jsonError_exists'] = function_exists('jsonError');
$checks['sendWhatsAppFromServer_exists'] = function_exists('sendWhatsAppFromServer');
// 4. التحقق من قاعدة البيانات
try {
$con = Database::get('main');
$checks['db_connected'] = true;
// التحقق من بنية الجدول
$stmt = $con->query("DESCRIBE adminUser");
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
$checks['adminUser_columns'] = $columns;
// هل يوجد جدول token_verification_admin؟
$stmt2 = $con->query("SHOW TABLES LIKE 'token_verification_admin'");
$checks['token_verification_admin_exists'] = $stmt2->rowCount() > 0;
if (!$checks['token_verification_admin_exists']) {
$checks['CRITICAL'] = 'Table token_verification_admin does NOT exist! This is why login fails.';
}
} catch (Throwable $e) {
$checks['db_connected'] = false;
$checks['db_error'] = $e->getMessage();
}
// 5. التحقق من PHP error log الأخير
$logPath = '/home/intaleq-api/logs/php_errors.log';
if (file_exists($logPath)) {
$lines = file($logPath);
$checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
} else {
$logPath2 = __DIR__ . '/../../logs/php_errors.log';
if (file_exists($logPath2)) {
$lines = file($logPath2);
$checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
} else {
$checks['error_log'] = 'Log file not found';
}
}
// 6. نسخة PHP
$checks['php_version'] = PHP_VERSION;
echo json_encode($checks, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

View File

@@ -0,0 +1,33 @@
<?php
/**
* Admin/auth/list_pending.php
* عرض قائمة المشرفين الذين ينتظرون الموافقة
* مسموح فقط للسوبر أدمن
*/
require_once __DIR__ . '/../../connect.php';
// التحقق من الصلاحيات
if ($role !== 'super_admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden. Super Admin access required.']);
exit;
}
try {
$con = Database::get('main');
$stmt = $con->prepare("SELECT id, name, phone, created_at FROM adminUser WHERE status = 'pending' ORDER BY created_at DESC");
$stmt->execute();
$pending = $stmt->fetchAll(PDO::FETCH_ASSOC);
// فك تشفير الأسماء
foreach ($pending as &$admin) {
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
}
printSuccess($pending);
} catch (Exception $e) {
error_log("[List Pending Admins Error] " . $e->getMessage());
jsonError("Server Error: " . $e->getMessage());
}

120
backend/Admin/auth/login.php Executable file
View File

@@ -0,0 +1,120 @@
<?php
/**
* Admin/auth/login.php
* تسجيل دخول المشرفين باستخدام البصمة وكلمة المرور المشفرة
*/
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
$fingerprint = filterRequest('fingerprint');
$password = filterRequest('password');
$audience = filterRequest('aud') ?? 'admin';
$isRenewal = filterRequest('is_renewal') === '1';
if (empty($fingerprint) || empty($password)) {
jsonError("Fingerprint and password are required.");
exit;
}
try {
$con = Database::get('main');
// البحث عن المشرف باستخدام بصمة الجهاز (Fingerprint Hash)
$fpHash = hash('sha256', $fingerprint);
$stmt = $con->prepare("SELECT * FROM adminUser WHERE fingerprint_hash = :fp LIMIT 1");
$stmt->execute([':fp' => $fpHash]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
if ($admin) {
// 1. التحقق من حالة الحساب
if ($admin['status'] === 'pending') {
jsonError("حسابك قيد المراجعة حالياً. يرجى الانتظار للموافقة.");
exit;
} elseif ($admin['status'] === 'suspended') {
jsonError("هذا الحساب معلق. يرجى التواصل مع المدير.");
exit;
} elseif ($admin['status'] === 'rejected') {
jsonError("تم رفض طلب الانضمام لهذا الحساب.");
exit;
}
// 2. التحقق من كلمة المرور
if (password_verify($password, $admin['password'])) {
// إذا كان هذا مجرد تجديد للتوكن (إعادة الدخول التلقائي من التطبيق)، فلا داعي لإرسال OTP
if ($isRenewal) {
$jwtService = new JwtService($redis);
$role = $admin['role'] ?? 'admin';
// إلغاء التوكن القديم إذا وجد في Redis
if ($redis) {
$oldJti = $redis->get("active_jti:" . $admin['id']);
if ($oldJti) {
$jwtService->revokeToken($oldJti, 3600);
}
}
$jwt = $jwtService->generateAccessToken($admin['id'], $role, $audience, $fingerprint);
// فك تشفير البيانات للعرض
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
unset($admin['password']);
printSuccess([
"message" => "Login successful",
"admin" => $admin,
"jwt" => $jwt,
"expires_in" => 3600
]);
exit;
}
// 3. توليد رمز تحقق OTP وإرساله عبر WhatsApp
$otp = rand(10000, 99999);
$encryptedPhone = $admin['phone'] ?? '';
if (empty($encryptedPhone)) {
jsonError("رقم الهاتف غير مسجل لهذا الحساب. يرجى مراجعة الإدارة.");
exit;
}
// فك تشفير رقم الهاتف (مخزن مشفراً في قاعدة البيانات)
$phone = $encryptionHelper->decryptData($encryptedPhone);
if (!$phone || empty($phone)) {
// إذا فشل فك التشفير، قد يكون الرقم مخزناً بدون تشفير
$phone = $encryptedPhone;
}
$messageBody = "رمز التحقق الخاص بك للدخول إلى لوحة الإدارة هو: $otp";
$success = sendWhatsAppFromServer($phone, $messageBody);
if ($success) {
// حفظ الرمز مشفراً في قاعدة البيانات (وحفظ رقم الهاتف مشفراً أيضاً)
$encryptedOtp = $encryptionHelper->encryptData((string)$otp);
$stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 10 MINUTE))
ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
$stmt->execute([$encryptedPhone, $encryptedOtp]);
// إخفاء جزء من الرقم في الاستجابة للأمان
$maskedPhone = substr($phone, 0, 4) . '****' . substr($phone, -3);
printSuccess([
"status" => "otp_required",
"message" => "تم إرسال رمز التحقق إلى WhatsApp الخاص بك.",
"phone" => $maskedPhone
]);
} else {
jsonError("فشل في إرسال رمز التحقق عبر WhatsApp.");
}
} else {
jsonError("كلمة المرور غير صحيحة.");
}
} else {
jsonError("الجهاز غير مسجل كمشرف.");
}
} catch (Exception $e) {
error_log("[Admin Login Error] " . $e->getMessage());
jsonError("خطأ في السيرفر: " . $e->getMessage());
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Admin/auth/loginWallet.php
* توليد توكن خاص بسيرفر المحفظة (Wallet SSO)
* يتم توقيعه بالمفتاح المشترك (SECRET_KEY_PAY)
*/
declare(strict_types=1);
require_once __DIR__ . '/../../core/bootstrap.php';
use Firebase\JWT\JWT;
// التحقق من الجلسة الحالية للأدمن
$jwtService = new JwtService($redis ?? null);
$admin = $jwtService->authenticate();
error_log("[Wallet_SSO] Authenticated Admin ID: " . ($admin->user_id ?? 'N/A') . " | Role: " . ($admin->role ?? 'N/A'));
if ($admin->role !== 'admin' && $admin->role !== 'super_admin') {
jsonError("Unauthorized. Admin access required.");
exit;
}
try {
// جلب المفتاح المشترك لسيرفر المحفظة
$payKeyPath = '/home/intaleq-api/.secret_key_pay';
$payKey = file_exists($payKeyPath) ? trim(file_get_contents($payKeyPath)) : getenv('SECRET_KEY_PAY');
if (empty($payKey)) {
$payKey = trim(@file_get_contents('/home/intaleq-api/.secret_key'));
}
if (empty($payKey)) {
jsonError("Internal configuration error: Shared secret key missing.");
exit;
}
$issuer = 'Tripz-Wallet';
$audience = 'Tripz-Wallet';
$hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
$ttl = 600; // 10 دقائق
$iat = time();
$exp = $iat + $ttl;
$jti = bin2hex(random_bytes(16));
// محتوى التوكن (Payload)
$payload = [
'iss' => $issuer,
'aud' => $audience,
'user_id' => $admin->user_id,
'role' => 'admin', // نرسل 'admin' للمحفظة لضمان التوافق مع برمجياتها القديمة
'iat' => $iat,
'exp' => $exp,
'jti' => $jti
];
// إلغاء التوكن القديم إذا وجد في Redis
if ($redis) {
$oldJtiKey = "wallet_jti:" . $admin->user_id;
$oldJti = $redis->get($oldJtiKey);
if ($oldJti) {
// إضافة التوكن القديم للقائمة السوداء
$redis->setex("jwt:blacklist:$oldJti", $ttl + 60, '1');
}
// تخزين الـ JTI الجديد
$redis->setex($oldJtiKey, $ttl, $jti);
}
// إضافة بصمة الجهاز للتوكن لزيادة الأمان
$fpHeader = $_SERVER['HTTP_X_DEVICE_FP'] ?? null;
$fpPepper = getenv('FP_PEPPER');
if ($fpHeader && $fpPepper) {
$payload['fingerPrint'] = hash('sha256', $fpHeader . $fpPepper);
}
// توليد التوكن
$jwt = JWT::encode($payload, $payKey, 'HS256');
// حساب الـ HMAC Hash المطلوب لسيرفر المحفظة
$hmacHash = hash_hmac('sha256', (string)$admin->user_id, $hmacSecret);
printSuccess([
"status" => "success",
"jwt" => $jwt,
"hmac" => $hmacHash,
"expires_in" => $ttl
]);
} catch (Exception $e) {
error_log("[Admin Wallet SSO Error] " . $e->getMessage());
jsonError("Server Error: " . $e->getMessage());
}

View File

@@ -0,0 +1,28 @@
<?php
require_once __DIR__ . '/../../core/bootstrap.php';
try {
$con = Database::get('main');
// Check if columns already exist to avoid errors
$check = $con->query("SHOW COLUMNS FROM adminUser LIKE 'status'");
if ($check->rowCount() == 0) {
$sql = "ALTER TABLE adminUser
ADD COLUMN status ENUM('pending', 'approved', 'suspended', 'rejected') NOT NULL DEFAULT 'pending' AFTER role,
ADD COLUMN phone VARCHAR(50) DEFAULT NULL AFTER name,
ADD COLUMN email VARCHAR(255) DEFAULT NULL AFTER phone,
ADD COLUMN approved_by VARCHAR(64) DEFAULT NULL AFTER status,
ADD COLUMN approved_at DATETIME DEFAULT NULL AFTER approved_by";
$con->exec($sql);
// Update existing admins to approved and super_admin
$con->exec("UPDATE adminUser SET status = 'approved', role = 'super_admin' WHERE id IS NOT NULL");
echo json_encode(["status" => "success", "message" => "Migration completed successfully."]);
} else {
echo json_encode(["status" => "success", "message" => "Columns already exist."]);
}
} catch (Exception $e) {
echo json_encode(["status" => "error", "message" => $e->getMessage()]);
}

View File

@@ -0,0 +1,128 @@
<?php
// ============================================================
// Admin/auth/migration_cryptography.php
// سكريبت لترحيل التشفير القديم (CBC) إلى التشفير الجديد (AES-256-GCM)
// يمكن تشغيله عبر الـ CLI أو المتصفح (بصلاحيات مسؤول).
// ============================================================
require_once __DIR__ . '/../../connect.php';
echo "Starting Cryptography Migration to AES-256-GCM...\n";
ob_flush(); flush();
$tables = [
'driver' => [
'phone', 'email', 'gender', 'birthdate', 'site',
'first_name', 'last_name', 'accountBank', 'education',
'employmentType', 'maritalStatus', 'national_number',
'name_arabic', 'address'
],
'passengers' => [
'phone', 'email', 'gender', 'birthdate',
'first_name', 'last_name', 'token'
],
'CarRegistration' => [
'vin', 'car_plate', 'owner', 'address'
],
'carPlateEdit' => [
'carPlate', 'owner'
],
'phone_verification' => [
'phone_number'
],
'phone_verification_passenger' => [
'phone_number'
],
'driverToken' => [
'token'
],
'passengerToken' => [
'token'
],
'mishwari' => [
'phone', 'gender', 'name', 'name_english', 'car_plate', 'token', 'education', 'national_number', 'age'
],
'rate_app' => [
'email', 'phone'
],
'admins' => [
'name', 'phone', 'email', 'fp'
],
'driver_assurance' => [
'assured', 'health_insurance_provider'
],
'blacklist_drivers' => [
'phone'
],
'blacklist_passengers' => [
'phone'
],
'feedBack' => [
'feedBack'
]
];
$totalUpdated = 0;
foreach ($tables as $table => $columns) {
echo "Processing table: $table ...\n";
ob_flush(); flush();
try {
$sql = "SELECT `id`, `" . implode("`, `", $columns) . "` FROM `$table`";
$stmt = $con->query($sql);
if (!$stmt) {
echo "Skipped $table (Not found or missing columns).\n";
continue;
}
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
echo "Skipped $table due to error: " . $e->getMessage() . "\n";
continue;
}
$tableUpdatedCount = 0;
foreach ($rows as $row) {
$id = $row['id'];
$needsUpdate = false;
$updateValues = [];
$params = [':id' => $id];
foreach ($columns as $col) {
$value = $row[$col];
// تحقق إذا كان الحقل يحتوي على قيمة وإذا لم يكن مشفر بالنظام الجديد
if (!empty($value) && strpos($value, 'GCM:') !== 0) {
// محاولة فك التشفير القديم (CBC)
try {
$decrypted = $encryptionHelper->decryptData($value);
if ($decrypted !== false && $decrypted !== '') {
// إعادة التشفير (سيستخدم GCM الآن)
$newEncrypted = $encryptionHelper->encryptData($decrypted);
$updateValues[] = "`$col` = :$col";
$params[":$col"] = $newEncrypted;
$needsUpdate = true;
}
} catch (Exception $e) {
error_log("Failed to migrate $col for ID $id in $table: " . $e->getMessage());
}
}
}
if ($needsUpdate) {
$setClause = implode(", ", $updateValues);
$updateSql = "UPDATE `$table` SET $setClause WHERE `id` = :id";
$updateStmt = $con->prepare($updateSql);
$updateStmt->execute($params);
$tableUpdatedCount++;
}
}
echo "Finished $table. Updated rows: $tableUpdatedCount\n";
$totalUpdated += $tableUpdatedCount;
ob_flush(); flush();
}
echo "Migration completed! Total rows updated: $totalUpdated\n";
?>

View File

@@ -0,0 +1,59 @@
<?php
/**
* Admin/auth/register.php
* التسجيل الذاتي للمشرفين - الحساب يكون بحالة pending بانتظار موافقة السوبر أدمن
*/
require_once __DIR__ . '/../../core/bootstrap.php';
$name = filterRequest('name');
$phone = filterRequest('phone');
$password = filterRequest('password');
$fingerprint = filterRequest('fingerprint');
if (empty($name) || empty($phone) || empty($password) || empty($fingerprint)) {
jsonError("All fields are required.");
exit;
}
try {
$con = Database::get('main');
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
$fpHash = hash('sha256', $fingerprint);
$check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
$check->execute([$phone, $fpHash]);
if ($check->rowCount() > 0) {
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
exit;
}
// 2. تجهيز البيانات
$id = bin2hex(random_bytes(16));
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$encName = $encryptionHelper->encryptData($name);
$encFp = $encryptionHelper->encryptData($fingerprint);
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي pending)
$sql = "INSERT INTO adminUser (id, name, phone, password, fingerprint, fingerprint_hash, role, status, created_at)
VALUES (:id, :name, :phone, :pass, :fp, :fp_hash, 'admin', 'pending', NOW())";
$stmt = $con->prepare($sql);
$stmt->execute([
':id' => $id,
':name' => $encName,
':phone' => $phone,
':pass' => $hashedPassword,
':fp' => $encFp,
':fp_hash' => $fpHash
]);
printSuccess([
"status" => "pending",
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
]);
} catch (Exception $e) {
error_log("[Admin Register Error] " . $e->getMessage());
jsonError("خطأ في السيرفر: " . $e->getMessage());
}

View File

@@ -0,0 +1,56 @@
<?php
// send_otp_admin.php — إرسال رمز التحقق لمسؤول عبر WhatsApp
require_once __DIR__ . '/../../connect.php';
error_log("--- [send_otp_admin] Script started ---");
// جلب الرقم من الطلب
$receiver = filterRequest("receiver");
//error_log("[send_otp_admin] Received phone number: " . var_export($receiver, true));
if (!$receiver) {
// error_log("[send_otp_admin] Missing phone number");
jsonError("رقم الهاتف مفقود.");
exit;
}
// قراءة الأرقام المصرح بها من ENV
$allowedPhones = explode(',', getenv('ADMIN_PHONE_NUMBERS'));
//error_log("[send_otp_admin] Allowed phones: " . implode(', ', $allowedPhones));
if (!in_array($receiver, $allowedPhones)) {
error_log("[send_otp_admin] Unauthorized phone number attempted: $receiver");
jsonError("رقم الهاتف غير مصرح له.");
exit;
}
// توليد رمز تحقق عشوائي
$otp = rand(10000, 99999);
$messageBody = "رمز التحقق الخاص بك للدخول إلى لوحة الإدارة هو: $otp";
//error_log("[send_otp_admin] Generated OTP: $otp for $receiver");
// إرسال الرسالة عبر WhatsApp
$success = sendWhatsAppFromServer($receiver, $messageBody);
error_log("[send_otp_admin] WhatsApp sending result: " . ($success ? "success" : "failure"));
if ($success) {
try {
$stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE))
ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
$stmt->execute([$receiver, $otp]);
// error_log("[send_otp_admin] OTP saved to database successfully for $receiver");
jsonSuccess(null, "OTP sent successfully.");
} catch (PDOException $e) {
// error_log("[send_otp_admin] Database error: " . $e->getMessage());
jsonError("حدث خطأ في حفظ الرمز.");
}
} else {
// error_log("[send_otp_admin] Failed to send WhatsApp message to $receiver");
jsonError("فشل في إرسال الرمز عبر WhatsApp.");
}
//error_log("--- [send_otp_admin] Script ended ---");
?>

View File

@@ -0,0 +1,83 @@
<?php
/**
* Admin/auth/verify_login.php
* الخطوة الثانية من تسجيل الدخول: التحقق من الـ OTP وإصدار التوكن النهائي
*/
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
$otp = filterRequest('otp');
$fingerprint = filterRequest('fingerprint'); // مطلوب لربط التوكن بالجهاز
$audience = filterRequest('aud') ?? 'admin';
if (empty($otp) || empty($fingerprint)) {
jsonError("OTP and fingerprint are required.");
exit;
}
try {
$con = Database::get('main');
// 1. جلب بيانات المسؤول عبر البصمة (مصدر موثوق وغير مشفر)
$fpHash = hash('sha256', $fingerprint);
$stmt = $con->prepare("SELECT * FROM adminUser WHERE fingerprint_hash = :fp LIMIT 1");
$stmt->execute([':fp' => $fpHash]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$admin) {
jsonError("المسؤول غير موجود أو البصمة غير مطابقة.");
exit;
}
// 2. رقم الهاتف المشفر (للاستخدام في جدول OTP)
$encryptedPhone = $admin['phone'] ?? '';
// فك تشفيره لو احتجنا إرساله أو عرضه، لكن هنا نحن نحتاج المشفر للبحث
// $phone = $encryptionHelper->decryptData($encryptedPhone);
// تشفير الرمز (OTP) القادم من التطبيق للمقارنة
$encryptedOtp = $encryptionHelper->encryptData((string)$otp);
// 3. التحقق من الـ OTP (باستخدام القيم المشفرة)
$stmt = $con->prepare("SELECT * FROM token_verification_admin
WHERE phone_number = ? AND token = ?
AND expiration_time >= NOW()");
$stmt->execute([$encryptedPhone, $encryptedOtp]);
if ($stmt->rowCount() === 0) {
jsonError("رمز التحقق غير صالح أو منتهي الصلاحية.");
exit;
}
// حذف الرمز بعد استخدامه لمرة واحدة (باستخدام الرقم المشفر)
$con->prepare("DELETE FROM token_verification_admin WHERE phone_number = ?")->execute([$encryptedPhone]);
// 4. إصدار التوكن النهائي
$jwtService = new JwtService($redis);
$role = $admin['role'] ?? 'admin';
// إلغاء التوكن القديم إذا وجد في Redis (Token Revocation)
if ($redis) {
$oldJti = $redis->get("active_jti:" . $admin['id']);
if ($oldJti) {
$jwtService->revokeToken($oldJti, 3600);
}
}
$jwt = $jwtService->generateAccessToken($admin['id'], $role, $audience, $fingerprint);
// فك تشفير البيانات للعرض
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
unset($admin['password']);
printSuccess([
"message" => "Login successful",
"admin" => $admin,
"jwt" => $jwt,
"expires_in" => 3600
]);
} catch (Exception $e) {
error_log("[Admin Verify OTP Error] " . $e->getMessage());
jsonError("خطأ في السيرفر: " . $e->getMessage());
}

View File

@@ -0,0 +1,46 @@
<?php
require_once __DIR__ . '/../../connect.php';
$phone = filterRequest("phone_number");
$otp = filterRequest("otp");
$deviceNumber = filterRequest("device_number");
if (empty($phone) || empty($otp)) {
jsonError("رقم الهاتف أو رمز التحقق مفقود.");
exit;
}
// التحقق من رمز التحقق (OTP)
$stmt = $con->prepare("SELECT * FROM token_verification_admin
WHERE phone_number = ? AND token = ?
AND expiration_time >= NOW()");
$stmt->execute([$phone, $otp]);
if ($stmt->rowCount() > 0) {
// ✅ تحقق ناجح - ننتقل إلى إدخال أو تحديث سجل adminUser
// تحقق إن كان المستخدم موجود مسبقًا
$checkAdmin = $con->prepare("SELECT * FROM adminUser WHERE name = ?");
$checkAdmin->execute([$phone]);
$now = date("Y-m-d H:i:s");
if ($checkAdmin->rowCount() > 0) {
// المستخدم موجود ✅ تحديث device_number و updated_at
$update = $con->prepare("UPDATE adminUser
SET device_number = ?, updated_at = ?
WHERE name = ?");
$update->execute([$deviceNumber, $now, $phone]);
jsonSuccess(["message" => "verified and updated existing admin"]);
} else {
// المستخدم غير موجود ✅ إدخال جديد
$insert = $con->prepare("INSERT INTO adminUser (device_number, name, created_at, updated_at)
VALUES (?, ?, ?, ?)");
$insert->execute([$deviceNumber, $phone, $now, $now]);
jsonSuccess(["message" => "verified and new admin created"]);
}
} else {
// ❌ رمز التحقق غير صالح
jsonError("رمز التحقق غير صالح أو منتهي.");
}