From 9b284852e9f8778fb9ecb894329ac68a9ed30e6f Mon Sep 17 00:00:00 2001 From: Untone Date: Mon, 29 Sep 2025 16:33:49 +0300 Subject: [PATCH] oath2.0 --- auth/oauth.py | 66 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/auth/oauth.py b/auth/oauth.py index 3cf2f763..fdd65491 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -624,20 +624,54 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse: code_challenge = create_s256_code_challenge(code_verifier) state = token_urlsafe(32) - # 🔍 Сохраняем redirect_uri из Referer header для testing.discours.io - # Приоритет: query параметр → path параметр → Referer header → FRONTEND_URL - final_redirect_uri = ( - request.query_params.get("redirect_uri") - or request.path_params.get("redirect_uri") - or request.headers.get("referer") - or FRONTEND_URL - ) - logger.info(f"🎯 Final redirect URI: '{final_redirect_uri}' (from referer: {request.headers.get('referer')})") + # 🎯 Получаем redirect_uri из query параметра (фронтенд должен передавать явно) + explicit_redirect_uri = request.query_params.get("redirect_uri") + + if explicit_redirect_uri: + # Декодируем если URL-encoded + from urllib.parse import unquote + + if "%3A" in explicit_redirect_uri or "%2F" in explicit_redirect_uri: + explicit_redirect_uri = unquote(explicit_redirect_uri) + + # Если это /oauth, меняем на /settings + if "/oauth" in explicit_redirect_uri: + from urllib.parse import urlparse, urlunparse + + parsed = urlparse(explicit_redirect_uri) + explicit_redirect_uri = urlunparse( + (parsed.scheme, parsed.netloc, "/settings", parsed.params, "", parsed.fragment) + ) + logger.info(f"🔧 Changed /oauth redirect to /settings: {explicit_redirect_uri}") + + final_redirect_uri = explicit_redirect_uri + else: + # Fallback на настройки профиля + final_redirect_uri = FRONTEND_URL.rstrip("/") + "/settings" + + logger.info(f"🎯 Final redirect URI: '{final_redirect_uri}'") + + # 🔑 Создаем state с redirect URL и случайным значением для безопасности + import base64 + import json + + state_data = { + "redirect_uri": final_redirect_uri, + "random": token_urlsafe(16), # Для CSRF protection + "timestamp": int(time.time()), + } + + # Кодируем state в base64 для передачи в URL + state_json = json.dumps(state_data) + state = base64.urlsafe_b64encode(state_json.encode()).decode().rstrip("=") + + logger.info(f"🔑 Created state with redirect_uri: {final_redirect_uri}") oauth_data = { "code_verifier": code_verifier, "provider": provider, "redirect_uri": final_redirect_uri, + "state_data": state_data, # Сохраняем для callback "created_at": int(time.time()), } await store_oauth_state(state, oauth_data) @@ -918,10 +952,14 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon else f"🔧 Session token: {session_token}" ) - # Получаем redirect_uri из OAuth данных - redirect_uri = oauth_data.get("redirect_uri", FRONTEND_URL) + # 🔑 Получаем redirect_uri из state данных (новый подход) + state_data = oauth_data.get("state_data", {}) + redirect_uri = state_data.get("redirect_uri") or oauth_data.get("redirect_uri", FRONTEND_URL) + if not isinstance(redirect_uri, str) or not redirect_uri: - redirect_uri = FRONTEND_URL + redirect_uri = FRONTEND_URL.rstrip("/") + "/settings" + + logger.info(f"🔑 Using redirect_uri from state: {redirect_uri}") # 🎯 Стандартный OAuth flow: токен в URL для фронтенда from urllib.parse import parse_qs, unquote, urlencode, urlparse, urlunparse @@ -999,11 +1037,11 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon if not isinstance(fallback_redirect, str): fallback_redirect = FRONTEND_URL - # Для testing.discours.io используем главную страницу (так как /oauth редиректит на /) + # Для testing.discours.io используем страницу профиля для ошибок if "testing.discours.io" in fallback_redirect: from urllib.parse import quote - error_url = f"https://testing.discours.io/?error=auth_failed&provider={provider}&redirect_url={quote(fallback_redirect)}" + error_url = f"https://testing.discours.io/settings?error=auth_failed&provider={provider}&redirect_url={quote(fallback_redirect)}" else: error_url = f"{fallback_redirect}?error=auth_failed&provider={provider}"