🔧 Add detailed OAuth callback logging for debugging auth_failed errors
All checks were successful
Deploy on push / deploy (push) Successful in 54m37s

This commit is contained in:
2025-09-25 07:54:00 +03:00
parent 5d0ad2a2e3
commit 2ce8a5b957

View File

@@ -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)