This commit is contained in:
@@ -6,9 +6,12 @@
|
|||||||
- 🔧 **OAuth Token Redirect**: Исправлена передача JWT токена - теперь токен передается через URL параметры (`access_token`) вместо cookie для корректной обработки фронтендом
|
- 🔧 **OAuth Token Redirect**: Исправлена передача JWT токена - теперь токен передается через URL параметры (`access_token`) вместо cookie для корректной обработки фронтендом
|
||||||
- 🔒 **OAuth State Security**: Добавлена обязательная передача `state` параметра в редиректе для CSRF защиты
|
- 🔒 **OAuth State Security**: Добавлена обязательная передача `state` параметра в редиректе для CSRF защиты
|
||||||
- 🔗 **OAuth URL Parameters**: Реализована поддержка передачи токена через URL query parameters согласно OAuth 2.0 спецификации
|
- 🔗 **OAuth URL Parameters**: Реализована поддержка передачи токена через URL query parameters согласно OAuth 2.0 спецификации
|
||||||
|
- 🔧 **Facebook OAuth PKCE**: Отключена поддержка PKCE для Facebook - провайдер не поддерживает Code Challenge
|
||||||
|
- 🔍 **OAuth Error Logging**: Добавлено детальное логирование ошибок OAuth для диагностики проблем с провайдерами
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- 🍪 **OAuth Cookie Compatibility**: Cookie с сессией оставлена для обратной совместимости, но основной способ передачи токена - URL параметры
|
- 🍪 **OAuth Cookie Compatibility**: Cookie с сессией оставлена для обратной совместимости, но основной способ передачи токена - URL параметры
|
||||||
|
- 🔧 **OAuth PKCE Support**: Facebook добавлен в список провайдеров без PKCE поддержки
|
||||||
|
|
||||||
## [0.9.23] - 2025-09-24
|
## [0.9.23] - 2025-09-24
|
||||||
|
|
||||||
|
|||||||
@@ -523,10 +523,12 @@ async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"OAuth callback error: {e!s}")
|
logger.error(f"OAuth callback error for {provider}: {e!s}", exc_info=True)
|
||||||
|
logger.error(f"OAuth callback request URL: {request.url}")
|
||||||
|
logger.error(f"OAuth callback query params: {dict(request.query_params)}")
|
||||||
# В случае ошибки редиректим на фронтенд с ошибкой
|
# В случае ошибки редиректим на фронтенд с ошибкой
|
||||||
fallback_redirect = request.query_params.get("redirect_uri", FRONTEND_URL)
|
fallback_redirect = request.query_params.get("redirect_uri", FRONTEND_URL)
|
||||||
return RedirectResponse(url=f"{fallback_redirect}?error=auth_failed")
|
return RedirectResponse(url=f"{fallback_redirect}?error=auth_failed&provider={provider}")
|
||||||
|
|
||||||
|
|
||||||
async def store_oauth_state(state: str, data: dict) -> None:
|
async def store_oauth_state(state: str, data: dict) -> None:
|
||||||
@@ -589,18 +591,20 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse:
|
|||||||
callback_uri = f"{backend_base_url}/oauth/{provider}/callback"
|
callback_uri = f"{backend_base_url}/oauth/{provider}/callback"
|
||||||
|
|
||||||
logger.info(f"🔗 Backend base URL: '{backend_base_url}'")
|
logger.info(f"🔗 Backend base URL: '{backend_base_url}'")
|
||||||
logger.info(f"🔗 Callback URI for GitHub: '{callback_uri}'")
|
logger.info(f"🔗 Callback URI for {provider}: '{callback_uri}'")
|
||||||
|
|
||||||
# 🔍 Создаем redirect URL вручную (обходим использование request.session в authlib)
|
# 🔍 Создаем redirect URL вручную (обходим использование request.session в authlib)
|
||||||
# VK не поддерживает PKCE, используем code_challenge только для поддерживающих провайдеров
|
# VK, Facebook не поддерживают PKCE, используем code_challenge только для поддерживающих провайдеров
|
||||||
if provider in ["vk", "yandex", "telegram"]:
|
if provider in ["vk", "yandex", "telegram", "facebook"]:
|
||||||
# Провайдеры без PKCE поддержки
|
# Провайдеры без PKCE поддержки
|
||||||
|
logger.info(f"🔧 Creating authorization URL without PKCE for {provider}")
|
||||||
authorization_url = await client.create_authorization_url(
|
authorization_url = await client.create_authorization_url(
|
||||||
callback_uri,
|
callback_uri,
|
||||||
state=state,
|
state=state,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Провайдеры с PKCE поддержкой (Google, GitHub, Facebook, X)
|
# Провайдеры с PKCE поддержкой (Google, GitHub, X)
|
||||||
|
logger.info(f"🔧 Creating authorization URL with PKCE for {provider}")
|
||||||
authorization_url = await client.create_authorization_url(
|
authorization_url = await client.create_authorization_url(
|
||||||
callback_uri,
|
callback_uri,
|
||||||
code_challenge=code_challenge,
|
code_challenge=code_challenge,
|
||||||
@@ -608,7 +612,7 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse:
|
|||||||
state=state,
|
state=state,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"🚀 GitHub authorization URL: '{authorization_url['url']}'")
|
logger.info(f"🚀 {provider.title()} authorization URL: '{authorization_url['url']}'")
|
||||||
return RedirectResponse(url=authorization_url["url"], status_code=302)
|
return RedirectResponse(url=authorization_url["url"], status_code=302)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -648,8 +652,9 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
|||||||
return JSONResponse({"error": "Missing authorization code"}, status_code=400)
|
return JSONResponse({"error": "Missing authorization code"}, status_code=400)
|
||||||
|
|
||||||
# 🔍 Обмениваем code на токен - с PKCE или без в зависимости от провайдера
|
# 🔍 Обмениваем code на токен - с PKCE или без в зависимости от провайдера
|
||||||
if provider in ["vk", "yandex", "telegram"]:
|
if provider in ["vk", "yandex", "telegram", "facebook"]:
|
||||||
# Провайдеры без PKCE поддержки
|
# Провайдеры без PKCE поддержки (Facebook может иметь проблемы с PKCE)
|
||||||
|
logger.info(f"🔧 Using OAuth without PKCE for {provider}")
|
||||||
token = await client.fetch_access_token(
|
token = await client.fetch_access_token(
|
||||||
authorization_response=str(request.url),
|
authorization_response=str(request.url),
|
||||||
)
|
)
|
||||||
@@ -659,17 +664,24 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
|||||||
if not code_verifier:
|
if not code_verifier:
|
||||||
return JSONResponse({"error": "Missing code verifier in OAuth state"}, status_code=400)
|
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(
|
token = await client.fetch_access_token(
|
||||||
authorization_response=str(request.url),
|
authorization_response=str(request.url),
|
||||||
code_verifier=code_verifier,
|
code_verifier=code_verifier,
|
||||||
)
|
)
|
||||||
if not token:
|
if not token:
|
||||||
|
logger.error(f"❌ Failed to get access token for {provider}")
|
||||||
return JSONResponse({"error": "Failed to get access token"}, status_code=400)
|
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)
|
profile = await get_user_profile(provider, client, token)
|
||||||
if not profile:
|
if not profile:
|
||||||
|
logger.error(f"❌ Failed to get user profile for {provider}")
|
||||||
return JSONResponse({"error": "Failed to get user profile"}, status_code=400)
|
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 функцию
|
# Создаем или обновляем пользователя используя helper функцию
|
||||||
author = await _create_or_update_user(provider, profile)
|
author = await _create_or_update_user(provider, profile)
|
||||||
if not author:
|
if not author:
|
||||||
@@ -734,10 +746,12 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"OAuth callback error: {e!s}")
|
logger.error(f"OAuth callback error for {provider}: {e!s}", exc_info=True)
|
||||||
|
logger.error(f"OAuth callback request URL: {request.url}")
|
||||||
|
logger.error(f"OAuth callback query params: {dict(request.query_params)}")
|
||||||
# В случае ошибки редиректим на фронтенд с ошибкой
|
# В случае ошибки редиректим на фронтенд с ошибкой
|
||||||
fallback_redirect = request.query_params.get("redirect_uri", FRONTEND_URL)
|
fallback_redirect = request.query_params.get("redirect_uri", FRONTEND_URL)
|
||||||
return RedirectResponse(url=f"{fallback_redirect}?error=auth_failed")
|
return RedirectResponse(url=f"{fallback_redirect}?error=auth_failed&provider={provider}")
|
||||||
|
|
||||||
|
|
||||||
async def _create_or_update_user(provider: str, profile: dict) -> Author:
|
async def _create_or_update_user(provider: str, profile: dict) -> Author:
|
||||||
|
|||||||
Reference in New Issue
Block a user