enforce(RateLimiter::identifier(), 'otp'); 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("An internal error occurred. Please try again later."); }