Fix Redis session mismatch in AuthMiddleware and ensure instant Onboarding UI transition
This commit is contained in:
@@ -26,7 +26,12 @@ class TeacherController
|
|||||||
|
|
||||||
$profile = Database::selectOne("SELECT * FROM teacher_profiles WHERE user_id = ? LIMIT 1", [$userId]);
|
$profile = Database::selectOne("SELECT * FROM teacher_profiles WHERE user_id = ? LIMIT 1", [$userId]);
|
||||||
|
|
||||||
$decryptedName = Security::decrypt($user['full_name']);
|
$rawName = (string)($user['full_name'] ?? '');
|
||||||
|
$decryptedName = Security::decrypt($rawName) ?: $rawName;
|
||||||
|
if (empty($decryptedName)) {
|
||||||
|
$decryptedName = 'معلم جديد';
|
||||||
|
}
|
||||||
|
|
||||||
$isComplete = !empty($profile) && !empty($profile['specialization']) && $decryptedName !== 'معلم جديد';
|
$isComplete = !empty($profile) && !empty($profile['specialization']) && $decryptedName !== 'معلم جديد';
|
||||||
|
|
||||||
$response->json([
|
$response->json([
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Middlewares;
|
|||||||
use App\Core\Request;
|
use App\Core\Request;
|
||||||
use App\Core\Response;
|
use App\Core\Response;
|
||||||
use App\Core\Security;
|
use App\Core\Security;
|
||||||
|
use App\Core\RedisClient;
|
||||||
|
|
||||||
class AuthMiddleware
|
class AuthMiddleware
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,7 @@ class AuthMiddleware
|
|||||||
$authHeader = $request->getHeader('authorization', '');
|
$authHeader = $request->getHeader('authorization', '');
|
||||||
|
|
||||||
if (!$authHeader || !preg_match('/Bearer\s(\S+)/i', $authHeader, $matches)) {
|
if (!$authHeader || !preg_match('/Bearer\s(\S+)/i', $authHeader, $matches)) {
|
||||||
$response->json(['error' => 'Unauthorized', 'message' => 'Token not provided or invalid format'], 401);
|
$response->status(401)->json(['error' => 'Unauthorized', 'message' => 'Token not provided or invalid format']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,33 +25,42 @@ class AuthMiddleware
|
|||||||
$payload = Security::verifyJWT($token);
|
$payload = Security::verifyJWT($token);
|
||||||
|
|
||||||
if (!$payload) {
|
if (!$payload) {
|
||||||
$response->json(['error' => 'Unauthorized', 'message' => 'Invalid or expired token'], 401);
|
$response->status(401)->json(['error' => 'Unauthorized', 'message' => 'Invalid or expired token']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if session is active in Redis
|
|
||||||
try {
|
|
||||||
$redis = \App\Core\RedisClient::getInstance();
|
|
||||||
$userId = $payload['user_id'];
|
|
||||||
$sessionState = $redis->get("session:{$userId}:{$token}");
|
|
||||||
|
|
||||||
if (!$sessionState || $sessionState !== 'active') {
|
|
||||||
$response->json(['error' => 'Unauthorized', 'message' => 'Session has been revoked or expired'], 401);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// If Redis is down, we fallback to just JWT verification
|
|
||||||
error_log("AuthMiddleware Redis Error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate required custom payload elements
|
// Validate required custom payload elements
|
||||||
if (!isset($payload['user_id']) || !isset($payload['role'])) {
|
if (!isset($payload['user_id']) || !isset($payload['role'])) {
|
||||||
$response->json(['error' => 'Unauthorized', 'message' => 'Malformed token payload structure'], 401);
|
$response->status(401)->json(['error' => 'Unauthorized', 'message' => 'Malformed token payload structure']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userId = (int)$payload['user_id'];
|
||||||
|
|
||||||
|
// Check if session is active in Redis (Single Active Device / Session Tracking)
|
||||||
|
try {
|
||||||
|
$redis = RedisClient::getInstance();
|
||||||
|
$sessionData = $redis->get("active_session:{$userId}");
|
||||||
|
|
||||||
|
if ($sessionData) {
|
||||||
|
$session = json_decode($sessionData, true);
|
||||||
|
$expectedSig = substr($token, -32);
|
||||||
|
if (is_array($session) && isset($session['token_signature']) && $session['token_signature'] !== $expectedSig) {
|
||||||
|
$response->status(401)->json([
|
||||||
|
'error' => 'Unauthorized',
|
||||||
|
'message' => 'تم تسجيل الدخول من جهاز أو متصفح آخر. يرجى إعادة تسجيل الدخول.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If Redis is temporarily unreachable, fallback gracefully to JWT verification
|
||||||
|
error_log("AuthMiddleware Redis Warning: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Attach user info to the Request instance dynamically so controllers can use it
|
// Attach user info to the Request instance dynamically so controllers can use it
|
||||||
$request->user_id = $payload['user_id'];
|
$request->user_id = $userId;
|
||||||
$request->role = $payload['role'];
|
$request->role = $payload['role'];
|
||||||
|
$request->uuid = $payload['uuid'] ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -955,7 +955,17 @@ class TeacherPortal
|
|||||||
if (res.ok && data.status === 'success' && data.data?.token) {
|
if (res.ok && data.status === 'success' && data.data?.token) {
|
||||||
const token = data.data.token;
|
const token = data.data.token;
|
||||||
localStorage.setItem('saqel_teacher_jwt', token);
|
localStorage.setItem('saqel_teacher_jwt', token);
|
||||||
await checkTeacherSession(token);
|
|
||||||
|
const user = data.data.user;
|
||||||
|
// Immediate UI Transition
|
||||||
|
if (user && user.name && user.name !== 'معلم جديد' && user.name !== 'مستخدم صَقِل') {
|
||||||
|
renderDashboard(user);
|
||||||
|
} else {
|
||||||
|
switchToOnboardingStep(user?.name || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync profile status in background
|
||||||
|
checkTeacherSession(token);
|
||||||
} else {
|
} else {
|
||||||
showError(data.message || 'رمز التحقق غير صحيح');
|
showError(data.message || 'رمز التحقق غير صحيح');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user