From 40d37cd0d9198ce2ef3abcebda152a1fddcf887f Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 2 May 2026 14:50:16 +0300 Subject: [PATCH] service 4 --- core/Auth/JwtService.php | 46 ++++++++++++++++++++++++++++++++++++++-- serviceapp/login.php | 35 +++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/core/Auth/JwtService.php b/core/Auth/JwtService.php index 40d29fd..54fac05 100644 --- a/core/Auth/JwtService.php +++ b/core/Auth/JwtService.php @@ -41,8 +41,16 @@ class JwtService $this->fpPepper = getenv('FP_PEPPER') ?: ''; $this->issuer = (string)(getenv('APP_ISSUER') ?: ''); $this->redis = $redis; + + // Debugging fpPepper + if (empty($this->fpPepper)) { + error_log("[JWT_DEBUG] fpPepper is EMPTY in constructor"); + } else { + error_log("[JWT_DEBUG] fpPepper is SET (length: " . strlen($this->fpPepper) . ")"); + } } + // ── توليد Access Token ────────────────────────────────── public function generateAccessToken( int|string $userId, @@ -76,9 +84,28 @@ class JwtService $payload['fingerPrint'] = hash('sha256', $fingerprint . $this->fpPepper); } - return JWT::encode($payload, $this->secretKey, self::ALGO); + $token = JWT::encode($payload, $this->secretKey, self::ALGO); + + // تخزين في Redis لضمان عدم التكرار وإمكانية الإلغاء + if ($this->redis) { + $this->redis->setex("active_jti:{$userId}", $ttl, $jti); + $this->redis->setex("active_token:{$userId}:{$audience}", $ttl, $token); + } + + return $token; } + // ── فك تشفير التوكن للتحقق الداخلي ──────────────────────── + public function decodeToken(string $token): ?object + { + try { + return JWT::decode($token, new Key($this->secretKey, self::ALGO)); + } catch (Exception $e) { + return null; + } + } + + // ── توليد Refresh Token ───────────────────────────────── public function generateRefreshToken(int|string $userId): array { @@ -176,12 +203,21 @@ class JwtService if ($this->fpPepper && $tokenType === 'access') { $fpInToken = $decoded->fingerPrint ?? null; $fpHeader = $_SERVER['HTTP_X_DEVICE_FP'] ?? null; + + // محاولة جلب الهيدر بطرق بديلة إذا لم يوجد في $_SERVER + if ($fpHeader === null && function_exists('getallheaders')) { + $headers = array_change_key_case(getallheaders(), CASE_LOWER); + $fpHeader = $headers['x-device-fp'] ?? null; + } if ($fpInToken === null || $fpHeader === null) { - error_log("[SECURITY] Fingerprint missing | user: $userId"); + + $allHeaders = json_encode(getallheaders()); + error_log("[SECURITY] Fingerprint missing | user: $userId | fpInToken: " . ($fpInToken ?? 'NULL') . " | fpHeader: " . ($fpHeader ?? 'NULL') . " | Headers: $allHeaders"); self::abort(403, 'Device verification required'); } + $expected = hash('sha256', $fpHeader . $this->fpPepper); if (!hash_equals($expected, $fpInToken)) { error_log("[SECURITY] Device mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?')); @@ -232,7 +268,13 @@ class JwtService } } + public function getFpPepper(): string + { + return $this->fpPepper; + } + private static function abort(int $code, string $message) + { error_log("[JWT_AUTH_FAILED] Code: $code | Message: $message | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?') . " | URI: " . ($_SERVER['REQUEST_URI'] ?? '?')); http_response_code($code); diff --git a/serviceapp/login.php b/serviceapp/login.php index 88fb6ce..b45798b 100755 --- a/serviceapp/login.php +++ b/serviceapp/login.php @@ -45,17 +45,46 @@ try { unset($user['password']); - // توليد التوكن + // توليد التوكن أو استرجاع التوكن الحالي إذا كان صالحاً $jwtService = new JwtService($redis); $role = 'service'; - $jwt = $jwtService->generateAccessToken($user['id'], $role, $audience); + $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; + } printSuccess([ "message" => "Login successful", "data" => $user, "jwt" => $jwt, - "expires_in" => 3600 + "expires_in" => $expires_in ]); + } else { jsonError("Incorrect password"); }