From c76f60df690ee866c0cd4fc8bd37649506cd0cbe Mon Sep 17 00:00:00 2001 From: jze9 Date: Mon, 24 Aug 2026 18:21:23 +0500 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82=D0=B5=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=B0=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5=20OAuth-=D0=BE=D0=B1=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Живая проверка Google-входа упала с "401 Unauthorized" на /token, но лог показывал только код статуса — тело ответа (там у Google/Яндекс error/ error_description с точной причиной: invalid_client и т.п.) терялось. _raise_for_status_verbose() оборачивает raise_for_status(), добавляя resp.text в сообщение исключения — на все 4 вызова (token+userinfo × google+yandex). Чисто диагностическое изменение, поведение не меняет. Тесты/линт/mypy — ок. Co-Authored-By: Claude Opus 4.8 --- services/api/app/core/oauth.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/services/api/app/core/oauth.py b/services/api/app/core/oauth.py index bfd5c92..4ff8237 100644 --- a/services/api/app/core/oauth.py +++ b/services/api/app/core/oauth.py @@ -20,6 +20,18 @@ class OAuthNotConfigured(Exception): """Провайдер не настроен (пустой client_id/secret в конфиге).""" +def _raise_for_status_verbose(resp: httpx.Response) -> None: + """Как raise_for_status(), но с телом ответа в сообщении — у Google/Яндекс + там error/error_description с точной причиной (invalid_client и т.п.), + без этого в логе только код статуса, причина не видна.""" + try: + resp.raise_for_status() + except httpx.HTTPStatusError as e: + raise httpx.HTTPStatusError( + f"{e}: {resp.text[:500]}", request=e.request, response=e.response + ) from e + + @dataclass class OAuthUserInfo: provider_id: str @@ -86,14 +98,14 @@ async def exchange_code(provider: str, code: str) -> OAuthUserInfo: "grant_type": "authorization_code", }, ) - token_resp.raise_for_status() + _raise_for_status_verbose(token_resp) access_token = token_resp.json()["access_token"] info_resp = await client.get( "https://www.googleapis.com/oauth2/v3/userinfo", headers={"Authorization": f"Bearer {access_token}"}, ) - info_resp.raise_for_status() + _raise_for_status_verbose(info_resp) info = info_resp.json() email = info["email"] return OAuthUserInfo( @@ -113,7 +125,7 @@ async def exchange_code(provider: str, code: str) -> OAuthUserInfo: "client_secret": client_secret, }, ) - token_resp.raise_for_status() + _raise_for_status_verbose(token_resp) access_token = token_resp.json()["access_token"] info_resp = await client.get( @@ -121,7 +133,7 @@ async def exchange_code(provider: str, code: str) -> OAuthUserInfo: params={"format": "json"}, headers={"Authorization": f"OAuth {access_token}"}, ) - info_resp.raise_for_status() + _raise_for_status_verbose(info_resp) info = info_resp.json() email = info.get("default_email") or next(iter(info.get("emails") or []), None)