diff --git a/auth/oauth.py b/auth/oauth.py index e8788048..15b892f2 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -467,7 +467,7 @@ async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse: else None, device_info={ "user_agent": request.headers.get("user-agent"), - "ip": request.client.host if hasattr(request, "client") else None, + "ip": request.client.host if hasattr(request, "client") and request.client else None, }, ) @@ -536,12 +536,7 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: code_challenge = create_s256_code_challenge(code_verifier) state = token_urlsafe(32) - # Сохраняем состояние в сессии - request.session["code_verifier"] = code_verifier - request.session["provider"] = provider - request.session["state"] = state - - # Сохраняем состояние OAuth в Redis + # 🔍 Сохраняем состояние OAuth только в Redis (убираем зависимость от request.session) oauth_data = { "code_verifier": code_verifier, "provider": provider, @@ -569,24 +564,27 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: async def oauth_callback_http(request: Request) -> JSONResponse | RedirectResponse: """HTTP handler для OAuth callback""" try: - # Используем GraphQL resolver логику - provider = request.session.get("provider") - if not provider: - return JSONResponse({"error": "No OAuth session found"}, status_code=400) - + # 🔍 Получаем состояние OAuth только из Redis (убираем зависимость от request.session) state = request.query_params.get("state") - session_state = request.session.get("state") - - if not state or state != session_state: - return JSONResponse({"error": "Invalid or expired OAuth state"}, status_code=400) + if not state: + 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) + provider = oauth_data.get("provider") + if not provider: + return JSONResponse({"error": "No provider in OAuth state"}, status_code=400) + # Используем существующую логику client = oauth.create_client(provider) + if not client: + return JSONResponse({"error": "Provider not configured"}, status_code=400) + token = await client.authorize_access_token(request) + if not token: + return JSONResponse({"error": "Failed to get access token"}, status_code=400) profile = await get_user_profile(provider, client, token) if not profile: @@ -594,17 +592,34 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon # Создаем или обновляем пользователя используя helper функцию author = await _create_or_update_user(provider, profile) + if not author: + return JSONResponse({"error": "Failed to create/update user"}, status_code=500) - # Создаем токен сессии - session_token = await TokenStorage.create_session(str(author.id)) + # Создаем токен сессии с полными данными + 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, + }, + ) - # Очищаем OAuth сессию - request.session.pop("code_verifier", None) - request.session.pop("provider", None) - request.session.pop("state", None) + # Получаем redirect_uri из OAuth данных + redirect_uri = oauth_data.get("redirect_uri", FRONTEND_URL) + if not isinstance(redirect_uri, str) or not redirect_uri: + redirect_uri = FRONTEND_URL # Возвращаем redirect с cookie - response = RedirectResponse(url="/auth/success", status_code=307) + response = RedirectResponse(url=str(redirect_uri), status_code=307) response.set_cookie( SESSION_COOKIE_NAME, session_token, @@ -612,12 +627,17 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon secure=SESSION_COOKIE_SECURE, samesite=SESSION_COOKIE_SAMESITE, max_age=SESSION_COOKIE_MAX_AGE, + path="/", # Важно: устанавливаем path="/" для доступности cookie во всех путях ) + + logger.info(f"OAuth успешно завершен для {provider}, user_id={author.id}") return response except Exception as e: - logger.error(f"OAuth callback error: {e}") - return JSONResponse({"error": "OAuth callback failed"}, status_code=500) + logger.error(f"OAuth callback error: {e!s}") + # В случае ошибки редиректим на фронтенд с ошибкой + fallback_redirect = request.query_params.get("redirect_uri", FRONTEND_URL) + return RedirectResponse(url=f"{fallback_redirect}?error=auth_failed") async def _create_or_update_user(provider: str, profile: dict) -> Author: