enforce(RateLimiter::identifier(), 'login'); try { $con = Database::get('main'); // البحث بالبصمة للموظف $fpHash = hash('sha256', $fingerprint); $sql = "SELECT * FROM `users` WHERE `fingerprint_hash` = :fp AND `user_type` = 'service' LIMIT 1"; $stmt = $con->prepare($sql); $stmt->execute([':fp' => $fpHash]); $user = $stmt->fetch(PDO::FETCH_ASSOC); // إذا لم يتم العثور بالبصمة، وتم تمرير الإيميل (تسجيل دخول لأول مرة أو من جهاز جديد) if (!$user && !empty($email)) { $encEmailInput = $encryptionHelper->encryptData($email); $stmtEmail = $con->prepare("SELECT * FROM `users` WHERE `email` = :email AND `user_type` = 'service' LIMIT 1"); $stmtEmail->execute([':email' => $encEmailInput]); $user = $stmtEmail->fetch(PDO::FETCH_ASSOC); // تأكيد كلمة المرور وتحديث بصمة الجهاز إذا تم إيجاد الحساب if ($user && password_verify($password, $user['password'])) { $encFpRaw = $encryptionHelper->encryptData($fingerprint); $updateStmt = $con->prepare("UPDATE `users` SET fingerprint = :fp_raw, fingerprint_hash = :fp WHERE id = :id"); $updateStmt->execute([ ':fp_raw' => $encFpRaw, ':fp' => $fpHash, ':id' => $user['id'] ]); $user['fingerprint_hash'] = $fpHash; // Update locally } else if ($user) { // Password incorrect, fail later. } } if ($user) { // التحقق من حالة الحساب if ($user['status'] === 'pending') { jsonError("حسابك قيد المراجعة حالياً. يرجى الانتظار للموافقة."); exit(); } elseif ($user['status'] === 'suspended') { jsonError("هذا الحساب معلق. يرجى التواصل مع الإدارة."); exit(); } elseif ($user['status'] !== 'approved') { jsonError("لم يتم تفعيل حسابك بعد."); exit(); } // التحقق من كلمة المرور if (password_verify($password, $user['password'])) { // فك تشفير البيانات للعرض في التطبيق $user['first_name'] = $encryptionHelper->decryptData($user['first_name']) ?: $user['first_name']; $user['last_name'] = $encryptionHelper->decryptData($user['last_name']) ?: $user['last_name']; $user['email'] = $encryptionHelper->decryptData($user['email']) ?: $user['email']; $user['phone'] = $encryptionHelper->decryptData($user['phone']) ?: $user['phone']; unset($user['password']); // توليد التوكن أو استرجاع التوكن الحالي إذا كان صالحاً $jwtService = new JwtService($redis); $role = 'service'; $ttl = 14400; // 4 hours $jwt = null; $expires_in = $ttl; // محاولة استعادة التوكن الحالي من Redis لتجنب التكرار if ($redis) { $existingToken = $redis->get("active_token:{$user['id']}:{$audience}"); if ($existingToken) { $decoded = $jwtService->decodeToken($existingToken); // يجب أن يكون التوكن صالحاً ويحتوي على بصمة الجهاز (إذا كان التشفير مفعلاً) if ($decoded && $decoded->exp > time() && (isset($decoded->fingerPrint) || empty($jwtService->getFpPepper()))) { $jwt = $existingToken; $expires_in = $decoded->exp - time(); } } } // إذا لم يوجد توكن صالح، نولد واحداً جديداً ونلغي القديم if (!$jwt) { if ($redis) { $oldJti = $redis->get("active_jti:{$user['id']}"); if ($oldJti) { $jwtService->revokeToken($oldJti, $ttl); } } $jwt = $jwtService->generateAccessToken($user['id'], $role, $audience, $fingerprint); $expires_in = $ttl; } // توليد مفتاح HMAC فريد للمستخدم (للتوافق مع CRUD الجديد) $hmacKey = hash_hmac('sha256', (string)$user['id'], getenv('SECRET_KEY_HMAC')); // ✅ FIX H-05: لا نعيد مفتاح HMAC أبداً (يُحسب على العميل بنفس المعادلة) printSuccess([ "message" => "Login successful", "data" => $user, "jwt" => $jwt, "expires_in" => $expires_in ]); } else { jsonError("Incorrect password"); } } else { jsonError("الجهاز أو الحساب غير مسجل. يرجى إدخال البريد الإلكتروني وكلمة المرور إذا كان هذا أول تسجيل دخول لك."); } } catch (Exception $e) { // ✅ FIX M-02: إخفاء تفاصيل الخطأ في الإنتاج $debugMode = getenv('APP_DEBUG') === 'true'; securityLog("[ServiceApp Login Error]", ['msg' => $e->getMessage()]); jsonError($debugMode ? "Server error: " . $e->getMessage() : "Server error. Please try again later.", 500); } exit();