From 2ce8a5b957541a6d413fc77d9c38d9a07c5cb68c Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 25 Sep 2025 07:54:00 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Add=20detailed=20OAuth=20callbac?= =?UTF-8?q?k=20logging=20for=20debugging=20auth=5Ffailed=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth/oauth.py | 136 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 39 deletions(-) diff --git a/auth/oauth.py b/auth/oauth.py index f45ba10e..35898b72 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -623,14 +623,30 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: async def oauth_callback_http(request: Request) -> JSONResponse | RedirectResponse: """HTTP handler для OAuth callback""" try: + # 🔍 Диагностика входящего callback запроса + logger.info("🔄 OAuth callback received:") + logger.info(f" - URL: {request.url}") + logger.info(f" - Method: {request.method}") + logger.info(f" - Headers: {dict(request.headers)}") + logger.info(f" - Query params: {dict(request.query_params)}") + logger.info(f" - Path params: {request.path_params}") + # 🔍 Получаем состояние OAuth только из Redis (убираем зависимость от request.session) state = request.query_params.get("state") if not state: + logger.error("❌ Missing OAuth state parameter") return JSONResponse({"error": "Missing OAuth state parameter"}, status_code=400) oauth_data = await get_oauth_state(state) if not oauth_data: - return JSONResponse({"error": "Invalid or expired OAuth state"}, status_code=400) + logger.warning(f"🚨 OAuth state {state} not found or expired") + # Более информативная ошибка для пользователя + return JSONResponse({ + "error": "oauth_state_expired", + "message": "OAuth session expired. Please try logging in again.", + "details": "The OAuth state was not found in Redis (expired or already used)", + "action": "restart_oauth_flow" + }, status_code=400) provider = oauth_data.get("provider") if not provider: @@ -644,7 +660,11 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon # Используем существующую логику client = oauth.create_client(provider) if not client: - return JSONResponse({"error": "Provider not configured"}, status_code=400) + logger.warning(f"🚨 OAuth provider {provider} not configured - returning graceful error") + # Graceful fallback: редиректим на фронтенд с информативной ошибкой + redirect_uri = oauth_data.get("redirect_uri", FRONTEND_URL) + error_url = f"{redirect_uri}?error=provider_not_configured&provider={provider}&message=OAuth+provider+credentials+missing" + return RedirectResponse(url=error_url, status_code=302) # Получаем authorization code из query параметров code = request.query_params.get("code") @@ -652,58 +672,87 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon return JSONResponse({"error": "Missing authorization code"}, status_code=400) # 🔍 Обмениваем code на токен - с PKCE или без в зависимости от провайдера - if provider in ["vk", "yandex", "telegram", "facebook"]: - # Провайдеры без PKCE поддержки (Facebook может иметь проблемы с PKCE) - logger.info(f"🔧 Using OAuth without PKCE for {provider}") - token = await client.fetch_access_token( - authorization_response=str(request.url), - ) - else: - # Провайдеры с PKCE поддержкой - code_verifier = oauth_data.get("code_verifier") - if not code_verifier: - return JSONResponse({"error": "Missing code verifier in OAuth state"}, status_code=400) + logger.info("🔄 Step 1: Exchanging authorization code for access token...") + try: + if provider in ["vk", "yandex", "telegram", "facebook"]: + # Провайдеры без PKCE поддержки (Facebook может иметь проблемы с PKCE) + logger.info(f"🔧 Using OAuth without PKCE for {provider}") + token = await client.fetch_access_token( + authorization_response=str(request.url), + ) + else: + # Провайдеры с PKCE поддержкой + code_verifier = oauth_data.get("code_verifier") + if not code_verifier: + logger.error(f"❌ Missing code verifier for {provider}") + return JSONResponse({"error": "Missing code verifier in OAuth state"}, status_code=400) - logger.info(f"🔧 Using OAuth with PKCE for {provider}") - token = await client.fetch_access_token( - authorization_response=str(request.url), - code_verifier=code_verifier, - ) + logger.info(f"🔧 Using OAuth with PKCE for {provider}") + token = await client.fetch_access_token( + authorization_response=str(request.url), + code_verifier=code_verifier, + ) + except Exception as e: + logger.error(f"❌ Failed to fetch access token for {provider}: {e}", exc_info=True) + raise # Re-raise для обработки в основном except блоке if not token: logger.error(f"❌ Failed to get access token for {provider}") return JSONResponse({"error": "Failed to get access token"}, status_code=400) logger.info(f"✅ Got access token for {provider}: {bool(token)}") - profile = await get_user_profile(provider, client, token) + # 🔄 Step 2: Getting user profile + logger.info(f"🔄 Step 2: Getting user profile from {provider}...") + try: + profile = await get_user_profile(provider, client, token) + except Exception as e: + logger.error(f"❌ Exception while getting user profile for {provider}: {e}", exc_info=True) + raise # Re-raise для обработки в основном except блоке + if not profile: - logger.error(f"❌ Failed to get user profile for {provider}") + logger.error(f"❌ Failed to get user profile for {provider} - empty profile returned") return JSONResponse({"error": "Failed to get user profile"}, status_code=400) logger.info(f"✅ Got user profile for {provider}: id={profile.get('id')}, email={profile.get('email')}, name={profile.get('name')}") - # Создаем или обновляем пользователя используя helper функцию - author = await _create_or_update_user(provider, profile) + # 🔄 Step 3: Creating or updating user + logger.info(f"🔄 Step 3: Creating or updating user for {provider}...") + try: + author = await _create_or_update_user(provider, profile) + except Exception as e: + logger.error(f"❌ Exception while creating/updating user for {provider}: {e}", exc_info=True) + raise # Re-raise для обработки в основном except блоке + if not author: + logger.error(f"❌ Failed to create/update user for {provider} - no author returned") return JSONResponse({"error": "Failed to create/update user"}, status_code=500) - # Создаем токен сессии с полными данными - session_token = await TokenStorage.create_session( - str(author.id), - auth_data={ - "provider": provider, - "profile": profile, - }, - username=author.name - if isinstance(author.name, str) - else str(author.name) - if author.name is not None - else None, - device_info={ - "user_agent": request.headers.get("user-agent"), - "ip": request.client.host if hasattr(request, "client") and request.client else None, - }, - ) + logger.info(f"✅ User created/updated for {provider}: user_id={author.id}, email={author.email}") + + # 🔄 Step 4: Creating session token + logger.info(f"🔄 Step 4: Creating session token for user {author.id}...") + try: + session_token = await TokenStorage.create_session( + str(author.id), + auth_data={ + "provider": provider, + "profile": profile, + }, + username=author.name + if isinstance(author.name, str) + else str(author.name) + if author.name is not None + else None, + device_info={ + "user_agent": request.headers.get("user-agent"), + "ip": request.client.host if hasattr(request, "client") and request.client else None, + }, + ) + except Exception as e: + logger.error(f"❌ Exception while creating session token for {provider}: {e}", exc_info=True) + raise # Re-raise для обработки в основном except блоке + + logger.info(f"✅ Session token created for {provider}: token_length={len(session_token)}") # Получаем redirect_uri из OAuth данных redirect_uri = oauth_data.get("redirect_uri", FRONTEND_URL) @@ -728,6 +777,15 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon logger.info(f"🔗 OAuth redirect URL: {final_redirect_url}") + # 🔍 Дополнительная диагностика для отладки + logger.info("🎯 OAuth callback redirect details:") + logger.info(f" - Original redirect_uri: {oauth_data.get('redirect_uri')}") + logger.info(f" - Final redirect_uri: {redirect_uri}") + logger.info(f" - Session token length: {len(session_token)}") + logger.info(f" - State: {state}") + logger.info(f" - Provider: {provider}") + logger.info(f" - User ID: {author.id}") + # Возвращаем redirect с токеном в URL response = RedirectResponse(url=final_redirect_url, status_code=307)