Files
Siro/backend/Admin/auth/login.php
T

155 lines
6.5 KiB
PHP

<?php
/**
* Admin/auth/login.php
* تسجيل دخول المشرفين باستخدام البصمة وكلمة المرور المشفرة
*/
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
require_once __DIR__ . '/../../encrypt_decrypt.php';
global $encryptionHelper;
if (!isset($encryptionHelper) || $encryptionHelper === null) {
if (class_exists('EncryptionHelper')) {
$key = getenv('ENC_KEY') ?: '';
$iv = getenv('initializationVector') ?: '';
$encryptionHelper = new EncryptionHelper($key, $iv);
}
}
$fingerprint = filterRequest('fingerprint');
$password = filterRequest('password');
$phone = filterRequest('phone');
$audience = filterRequest('aud') ?? 'admin';
$isRenewal = filterRequest('is_renewal') === '1';
if (empty($fingerprint) || empty($password)) {
jsonError("Fingerprint and password are required.");
exit;
}
// Rate Limiting محسَّن مع Exponential Backoff
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'login');
// تتبع المحاولات الفاشلة لكل حساب لمنع credential stuffing عبر IPs متعددة
if ($redis && !empty($phone)) {
$accountKey = "login_attempts:account:" . hash('sha256', $phone);
$accountAttempts = (int) $redis->get($accountKey);
if ($accountAttempts >= 5) {
$ttl = $redis->ttl($accountKey);
$waitMinutes = ceil($ttl / 60);
jsonError("تم تعليق تسجيل الدخول لهذا الحساب مؤقتاً. يرجى المحاولة بعد {$waitMinutes} دقيقة.");
exit;
}
}
// البحث عن المشرف باستخدام بصمة الجهاز (Fingerprint Hash)
$fpHash = hash('sha256', $fingerprint);
// تسجيل محاولة تسجيل الدخول للتدقيق
$loginAuditData = [
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'fingerprint_hash' => $fpHash,
'phone_hash' => !empty($phone) ? hash('sha256', $phone) : null,
'timestamp' => date('Y-m-d H:i:s'),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'result' => 'pending'
];
error_log("[LOGIN_AUDIT] " . json_encode($loginAuditData));
try {
$con = Database::get('main');
$stmt = $con->prepare("SELECT * FROM adminUser WHERE fingerprint_hash = :fp LIMIT 1");
$stmt->execute([':fp' => $fpHash]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
// إذا لم يتم العثور بالبصمة، وتم تمرير اسم المستخدم/الإيميل/الهاتف (تسجيل دخول لأول مرة أو جهاز جديد)
if (!$admin && !empty($phone)) {
// 1. تجربة البحث بالهاتف المشفّر
if ($encryptionHelper) {
$encPhoneInput = $encryptionHelper->encryptData($phone);
$stmtPhone = $con->prepare("SELECT * FROM adminUser WHERE phone = :phone LIMIT 1");
$stmtPhone->execute([':phone' => $encPhoneInput]);
$admin = $stmtPhone->fetch(PDO::FETCH_ASSOC);
}
// 2. إذا لم يجد بالهاتف المشفّر، نجرب بالبريد الإلكتروني أو المعرّف (id / email) أو رقم الهاتف غير المشفّر
if (!$admin) {
$stmtAlt = $con->prepare("SELECT * FROM adminUser WHERE email = :input OR id = :input OR phone = :input LIMIT 1");
$stmtAlt->execute([':input' => $phone]);
$admin = $stmtAlt->fetch(PDO::FETCH_ASSOC);
}
// تأكيد كلمة المرور وتحديث بصمة الجهاز إذا تم إيجاد الحساب
if ($admin && password_verify($password, $admin['password'])) {
$encFpRaw = $encryptionHelper ? $encryptionHelper->encryptData($fingerprint) : $fingerprint;
$updateStmt = $con->prepare("UPDATE adminUser SET fingerprint = :fp_raw, fingerprint_hash = :fp WHERE id = :id");
$updateStmt->execute([
':fp_raw' => $encFpRaw,
':fp' => $fpHash,
':id' => $admin['id']
]);
$admin['fingerprint_hash'] = $fpHash; // Update locally
} else if ($admin) {
// Password incorrect, fail later.
}
}
if ($admin) {
// 1. التحقق من حالة الحساب
if (isset($admin['status'])) {
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'])) {
// إصدار التوكن النهائي للمشرف
$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);
// فك تشفير البيانات للعرض
if ($encryptionHelper && !empty($admin['name'])) {
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
}
unset($admin['password']);
printSuccess([
"message" => "Login successful",
"admin" => $admin,
"jwt" => $jwt,
"expires_in" => 3600
]);
exit;
} else {
jsonError("كلمة المرور غير صحيحة.");
}
} else {
jsonError("الحساب غير موجود. يرجى التأكد من البريد الإلكتروني أو رقم الهاتف وكلمة المرور.");
}
} catch (Throwable $e) {
error_log("[Admin Login Throwable Error] " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
jsonError("حدث خطأ في السيرفر: " . $e->getMessage(), 500);
}