You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(auth): raise typed ServiceUnavailableError on token endpoint 5xx (#2099)
Fixes#2097
## Problem
When a vendor OAuth token endpoint (e.g. the Atlantic/CozyTouch token
URL) is temporarily down, it can return an **HTTP 5xx with an HTML
body**. The auth strategies parse token responses with `await
response.json()` **without checking the HTTP status first**, so aiohttp
raises a raw `ContentTypeError: Attempt to decode JSON with unexpected
mimetype: text/html`.
This:
- leaks an aiohttp implementation detail to consumers, and
- isn't caught by any `retry_on_*` decorator (they only match typed
Overkiz exceptions),
so a transient hiccup becomes a hard failure. Reported downstream in
Home Assistant core #160761.
The same unguarded `response.json()` pattern existed at four token
sites: Somfy, Cozytouch, Nexity, and Rexel.
A secondary gap: `check_response()` only mapped **503** to
`ServiceUnavailableError`; 500/502/504 with a non-JSON body fell through
to a generic `OverkizError`.
## Fix
1. **`auth/strategies.py`** — add `_raise_for_server_error()` which
routes any 5xx token response through `check_response()` before JSON
decoding, applied at the Somfy, Cozytouch, Nexity and Rexel token
endpoints. 4xx vendor JSON error formats (e.g. `{"error":
"invalid_grant"}`) are still handled by each strategy as before.
2. **`response_handler.py`** — generalize the non-JSON branch from `==
503` to `>= 500`, so any 5xx HTML body maps to
`ServiceUnavailableError`.
Implements points 1 & 2 of the issue. Point 3 (auto-retry of transient
5xx) was intentionally left out — consumers now get a typed, catchable
`ServiceUnavailableError`, which HA already treats as a retryable update
failure.
## Tests
- New `TestTokenEndpointServerErrors` reproduces the exact
`ContentTypeError` leak for Somfy/Cozytouch/Rexel and asserts it now
raises `ServiceUnavailableError`.
- New `check_response` cases for 500/502/504 + a `502-bad-gateway.html`
fixture.
- Full suite: **497 passed**, ruff + mypy clean.
<html><head><title>502 Bad Gateway</title></head><body><h1>502 Bad Gateway</h1><p>The proxy server received an invalid response from an upstream server.</p></body></html>
0 commit comments