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)