🔧 Add detailed OAuth callback logging for debugging auth_failed errors
All checks were successful
Deploy on push / deploy (push) Successful in 54m37s
All checks were successful
Deploy on push / deploy (push) Successful in 54m37s
This commit is contained in:
136
auth/oauth.py
136
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user