diff --git a/CHANGELOG.md b/CHANGELOG.md index f14b9a29..5ada6fd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.9.24] - 2025-09-24 + +### Fixed +- 🔧 **OAuth Token Redirect**: Исправлена передача JWT токена - теперь токен передается через URL параметры (`access_token`) вместо cookie для корректной обработки фронтендом +- 🔒 **OAuth State Security**: Добавлена обязательная передача `state` параметра в редиректе для CSRF защиты +- 🔗 **OAuth URL Parameters**: Реализована поддержка передачи токена через URL query parameters согласно OAuth 2.0 спецификации + +### Changed +- 🍪 **OAuth Cookie Compatibility**: Cookie с сессией оставлена для обратной совместимости, но основной способ передачи токена - URL параметры + ## [0.9.23] - 2025-09-24 ### Fixed diff --git a/auth/oauth.py b/auth/oauth.py index 7597ebba..2c204df1 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -486,10 +486,34 @@ async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse: if not isinstance(redirect_uri, str) or not redirect_uri: redirect_uri = FRONTEND_URL - # Создаем ответ с редиректом - response = RedirectResponse(url=str(redirect_uri)) + # 🔧 Передаем JWT токен через URL параметры вместо cookie + from urllib.parse import parse_qs, urlencode, urlparse, urlunparse - # Устанавливаем cookie с сессией + parsed_url = urlparse(redirect_uri) + query_params = parse_qs(parsed_url.query) + + # Добавляем access_token и state в URL параметры + query_params['access_token'] = [session_token] + if state: + query_params['state'] = [state] + + # Собираем новый URL с параметрами + new_query = urlencode(query_params, doseq=True) + final_redirect_url = urlunparse(( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + parsed_url.params, + new_query, + parsed_url.fragment + )) + + logger.info(f"🔗 OAuth redirect URL: {final_redirect_url}") + + # Создаем ответ с редиректом + response = RedirectResponse(url=final_redirect_url) + + # 🍪 Оставляем cookie для обратной совместимости (опционально) response.set_cookie( SESSION_COOKIE_NAME, session_token, @@ -679,8 +703,33 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon if not isinstance(redirect_uri, str) or not redirect_uri: redirect_uri = FRONTEND_URL - # Возвращаем redirect с cookie - response = RedirectResponse(url=str(redirect_uri), status_code=307) + # 🔧 Передаем JWT токен через URL параметры вместо cookie + from urllib.parse import parse_qs, urlencode, urlparse, urlunparse + + parsed_url = urlparse(redirect_uri) + query_params = parse_qs(parsed_url.query) + + # Добавляем access_token и state в URL параметры + query_params['access_token'] = [session_token] + query_params['state'] = [state] + + # Собираем новый URL с параметрами + new_query = urlencode(query_params, doseq=True) + final_redirect_url = urlunparse(( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + parsed_url.params, + new_query, + parsed_url.fragment + )) + + logger.info(f"🔗 OAuth redirect URL: {final_redirect_url}") + + # Возвращаем redirect с токеном в URL + response = RedirectResponse(url=final_redirect_url, status_code=307) + + # 🍪 Оставляем cookie для обратной совместимости (опционально) response.set_cookie( SESSION_COOKIE_NAME, session_token,