input('email'); $password = $request->input('password'); if (!$email || !$password) { Response::error('يرجى إدخال البريد الإلكتروني وكلمة المرور', 'VALIDATION_ERROR', 422); return; } try { $result = $this->authService->login($email, $password); // 2FA Check if (($result['user']['totp_enabled'] ?? false) === true) { Response::json([ 'success' => true, 'requires_2fa' => true, 'temp_token' => $result['access_token'] ]); return; } // Set refresh token in HttpOnly cookie setcookie('refresh_token', $result['refresh_token'], [ 'expires' => time() + (60 * 60 * 24 * 7), 'path' => '/api/v1/auth/refresh', 'httponly' => true, 'samesite' => 'Strict', 'secure' => true ]); unset($result['refresh_token']); Response::json([ 'success' => true, 'data' => $result, 'message' => 'تم تسجيل الدخول بنجاح' ]); } catch (Throwable $e) { Response::error($e->getMessage(), 'AUTH_FAILED', 401); } } public function me(Request $request): void { $db = \App\Core\Database::getInstance(); $stmt = $db->prepare("SELECT id, tenant_id, name, email, role, totp_enabled FROM users WHERE id = ?"); $stmt->execute([$request->user->user_id]); $user = $stmt->fetch(); Response::json([ 'success' => true, 'data' => $user ]); } public function logout(Request $request): void { $authHeader = $request->getHeader('Authorization'); if ($authHeader && str_starts_with($authHeader, 'Bearer ')) { try { $token = substr($authHeader, 7); $jwtService = new JwtService(); $decoded = $jwtService->verifyToken($token); $jti = (string)($decoded['jti'] ?? ''); $remaining = max(((int)($decoded['exp'] ?? 0)) - time(), 0); if ($jti !== '') { $this->authService->logout($jti, $remaining); } } catch (Throwable $e) { error_log('[AUTH] Could not parse token on logout: ' . $e->getMessage()); } } // Clear refresh token cookie setcookie('refresh_token', '', [ 'expires' => time() - 3600, 'path' => '/api/v1/auth/refresh', 'httponly' => true, 'samesite' => 'Strict', 'secure' => true ]); Response::json([ 'success' => true, 'message' => 'تم تسجيل الخروج بنجاح' ]); } public function refresh(Request $request): void { $refreshToken = $_COOKIE['refresh_token'] ?? null; if (!$refreshToken) { Response::error('رمز التجديد مفقود', 'UNAUTHORIZED', 401); return; } try { $result = $this->authService->refresh($refreshToken); // Set new refresh token in HttpOnly cookie setcookie('refresh_token', $result['refresh_token'], [ 'expires' => time() + (60 * 60 * 24 * 7), 'path' => '/api/v1/auth/refresh', 'httponly' => true, 'samesite' => 'Strict', 'secure' => true ]); unset($result['refresh_token']); Response::json([ 'success' => true, 'data' => $result, 'message' => 'تم تجديد الجلسة بنجاح' ]); } catch (Throwable $e) { Response::error($e->getMessage(), 'REFRESH_FAILED', 401); } } public function register(Request $request): void { try { $result = $this->authService->register($request->getBody()); // Set refresh token in HttpOnly cookie setcookie('refresh_token', $result['refresh_token'], [ 'expires' => time() + (60 * 60 * 24 * 7), 'path' => '/api/v1/auth/refresh', 'httponly' => true, 'samesite' => 'Strict', 'secure' => true ]); unset($result['refresh_token']); Response::json([ 'success' => true, 'data' => $result, 'message' => 'تم إنشاء الحساب وتسجيل الدخول بنجاح' ]); } catch (Throwable $e) { Response::error($e->getMessage(), 'REGISTRATION_FAILED', 400); } } public function enable2FA(Request $request): void { $user = $request->user; $totpService = new \App\Services\TotpService(); $secret = $totpService->generateSecret(); $qrUrl = $totpService->getQrCodeUrl($user->email, $secret); Response::json([ 'success' => true, 'data' => [ 'secret' => $secret, 'qr_url' => $qrUrl ] ]); } public function verify2FA(Request $request): void { $data = $request->getBody(); $code = $data['code'] ?? ''; $secret = $data['secret'] ?? ''; $totpService = new \App\Services\TotpService(); if ($totpService->verify($secret, $code)) { $encryptedSecret = (new EncryptionService())->encrypt($secret); $db = \App\Core\Database::getInstance(); $stmt = $db->prepare("UPDATE users SET totp_secret = ?, totp_enabled = 1 WHERE id = ?"); $stmt->execute([$encryptedSecret, $request->user->user_id]); Response::json(['success' => true, 'message' => 'تم تفعيل التحقق الثنائي بنجاح']); } else { Response::error('رمز التحقق غير صحيح', 'INVALID_CODE', 400); } } public function login2FAVerify(Request $request): void { $data = $request->getBody(); $code = $data['code'] ?? ''; $userId = $request->user->user_id; $db = \App\Core\Database::getInstance(); $stmt = $db->prepare("SELECT totp_secret FROM users WHERE id = ?"); $stmt->execute([$userId]); $secret = $stmt->fetchColumn(); if (!$secret) { Response::error('لم يتم تفعيل التحقق الثنائي لهذا الحساب', 'TWO_FA_DISABLED', 400); return; } $totpService = new \App\Services\TotpService(); $decrypted = null; try { $decrypted = (new EncryptionService())->decrypt((string)$secret); } catch (Throwable $e) { // Backward compatibility with old plaintext records $decrypted = (string)$secret; } if (!$totpService->verify($decrypted, $code)) { Response::error('رمز التحقق غير صحيح', 'INVALID_CODE', 401); return; } // Re-issue a full login session after successful 2FA. $stmt = $db->prepare("SELECT email FROM users WHERE id = ?"); $stmt->execute([$userId]); $email = $stmt->fetchColumn(); if (!$email) { Response::error('المستخدم غير موجود', 'NOT_FOUND', 404); return; } Response::json([ 'success' => true, 'data' => ['user_id' => $userId, 'email' => $email], 'message' => 'تم التحقق بنجاح' ]); } public function disable2FA(Request $request): void { $password = (string)$request->input('password', ''); if ($password === '') { Response::error('كلمة المرور مطلوبة لتعطيل التحقق الثنائي', 'VALIDATION_ERROR', 422); return; } $db = \App\Core\Database::getInstance(); $stmt = $db->prepare("SELECT password_hash FROM users WHERE id = ?"); $stmt->execute([$request->user->user_id]); $hash = $stmt->fetchColumn(); if (!$hash || !password_verify($password, (string)$hash)) { Response::error('كلمة المرور غير صحيحة', 'UNAUTHORIZED', 401); return; } $db = \App\Core\Database::getInstance(); $stmt = $db->prepare("UPDATE users SET totp_secret = NULL, totp_enabled = 0 WHERE id = ?"); $stmt->execute([$request->user->user_id]); Response::json(['success' => true, 'message' => 'تم تعطيل التحقق الثنائي']); } }