diff --git a/CHANGELOG.md b/CHANGELOG.md index db1a47ab..f14b9a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixed - 🔧 **OAuth Callback URL**: Исправлено формирование callback URL - добавлен отсутствующий слеш между доменом и путем - 🔒 **OAuth HTTPS**: Принудительное использование HTTPS для callback URL в продакшне (исправляет ошибку "redirect_uri is not associated") +- 🔧 **OAuth URL Parsing**: Исправлено извлечение базового URL - теперь используется только схема и хост без пути +- 🔄 **OAuth Path Support**: Добавлена поддержка redirect_uri в path параметрах для совместимости с фронтендом ### Changed - 🔄 **OAuth Routes**: Возвращены к стандартному формату `/oauth/{provider}` - провайдеры не передают параметр provider в callback diff --git a/auth/oauth.py b/auth/oauth.py index e0014312..7597ebba 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -408,10 +408,15 @@ async def oauth_login(_: None, _info: GraphQLResolveInfo, provider: str, callbac await store_oauth_state(state, oauth_data) # Callback должен идти на backend с принудительным HTTPS для продакшна - base_url = callback_data["base_url"].rstrip("/") - # Принудительно HTTPS - base_url = base_url.replace("http://", "https://") - oauth_callback_uri = f"{base_url}/oauth/{provider}/callback" + # Извлекаем только схему и хост из base_url (убираем путь!) + from urllib.parse import urlparse + + parsed_url = urlparse(callback_data["base_url"]) + scheme = "https" if parsed_url.netloc != "localhost:8000" else parsed_url.scheme + backend_base_url = f"{scheme}://{parsed_url.netloc}" + oauth_callback_uri = f"{backend_base_url}/oauth/{provider}/callback" + + logger.info(f"🔗 GraphQL callback URI: '{oauth_callback_uri}'") try: return await client.authorize_redirect( @@ -526,7 +531,12 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: """HTTP handler для OAuth login""" try: provider = request.path_params.get("provider") + logger.info( + f"🔍 OAuth login request: provider='{provider}', url='{request.url}', path_params={request.path_params}, query_params={dict(request.query_params)}" + ) + if not provider or provider not in PROVIDER_CONFIGS: + logger.error(f"❌ Invalid provider: '{provider}', available: {list(PROVIDER_CONFIGS.keys())}") return JSONResponse({"error": "Invalid provider"}, status_code=400) client = oauth.create_client(provider) @@ -540,8 +550,12 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: state = token_urlsafe(32) # 🔍 Сохраняем состояние OAuth только в Redis (убираем зависимость от request.session) - # Получаем redirect_uri из query параметров или используем FRONTEND_URL по умолчанию - final_redirect_uri = request.query_params.get("redirect_uri", FRONTEND_URL) + # Получаем redirect_uri из query параметров, path параметров или используем FRONTEND_URL по умолчанию + final_redirect_uri = ( + request.query_params.get("redirect_uri") or request.path_params.get("redirect_uri") or FRONTEND_URL + ) + logger.info(f"🎯 Final redirect URI: '{final_redirect_uri}'") + oauth_data = { "code_verifier": code_verifier, "provider": provider, @@ -550,10 +564,13 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: } await store_oauth_state(state, oauth_data) - # Получаем backend URL из request (принудительно HTTPS для продакшна) + # Получаем БАЗОВЫЙ backend URL (только схема + хост, без пути!) scheme = "https" if request.url.netloc != "localhost:8000" else request.url.scheme - backend_url = f"{scheme}://{request.url.netloc}" - callback_uri = f"{backend_url}/oauth/{provider}/callback" + backend_base_url = f"{scheme}://{request.url.netloc}" + callback_uri = f"{backend_base_url}/oauth/{provider}/callback" + + logger.info(f"🔗 Backend base URL: '{backend_base_url}'") + logger.info(f"🔗 Callback URI for GitHub: '{callback_uri}'") # 🔍 Создаем redirect URL вручную (обходим использование request.session в authlib) # VK не поддерживает PKCE, используем code_challenge только для поддерживающих провайдеров @@ -572,6 +589,7 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: state=state, ) + logger.info(f"🚀 GitHub authorization URL: '{authorization_url['url']}'") return RedirectResponse(url=authorization_url["url"], status_code=302) except Exception as e: diff --git a/main.py b/main.py index 04a44142..de099c0c 100644 --- a/main.py +++ b/main.py @@ -302,9 +302,12 @@ async def lifespan(app: Starlette): app = Starlette( routes=[ Route("/graphql", graphql_handler, methods=["GET", "POST", "OPTIONS"]), - # OAuth маршруты - Route("/oauth/{provider}", oauth_login_http, methods=["GET"]), + # OAuth маршруты - порядок важен! Более специфичные маршруты должны быть первыми Route("/oauth/{provider}/callback", oauth_callback_http, methods=["GET"]), + Route( + "/oauth/{provider}/{redirect_uri:path}", oauth_login_http, methods=["GET"] + ), # Поддержка старого формата фронтенда + Route("/oauth/{provider}", oauth_login_http, methods=["GET"]), # Health check endpoint Route("/health", health_handler, methods=["GET"]), # Статические файлы (CSS, JS, изображения)