Surface Dynadot's refusal instead of only its status code (#2)

A 400 from Dynadot arrived as "HTTP 400" and nothing else. Dynadot explains the
refusal in the response body and we were discarding it, so the operator was left
with a number that does not distinguish "the domain is not in this account" from
"the signature is wrong" from "the caller address is not allowlisted". All three
are 400 and all three have different fixes.

The body is bounded to 400 characters and quoted as remote text. No credential
can appear in it: the key travels in an Authorization header and the signature
is computed rather than echoed.

Reading the body is best-effort. A stream that cannot be read must not cost the
status code as well, so the failure to read is swallowed and the code is still
raised, which is what the second test pins.

Co-authored-by: jw <jw@jwmacbookm5.local>
This commit is contained in:
John Winter Murphy
2026-08-24 09:01:51 -04:00
committed by GitHub
co-authored by jw
parent 644652b186
commit a4b8a8729b
2 changed files with 57 additions and 1 deletions
+14 -1
View File
@@ -295,8 +295,21 @@ class DynadotDNSProvider:
with self._opener(request, timeout=30) as response:
raw = response.read().decode("utf-8")
except urllib.error.HTTPError as error:
# Dynadot explains the refusal in the body. Discarding it left the
# operator with a status code and nothing to act on, which for a 400
# is the difference between "the domain is not in this account" and
# "the signature is wrong". Bounded and quoted because it is remote
# text, and the credential is never in it: the key travels in a
# header and the signature is computed, not echoed.
detail = ""
try:
body = error.read().decode("utf-8", "replace").strip()
if body:
detail = f": {body[:400]}"
except Exception: # noqa: BLE001 - a body is a bonus, never required
detail = ""
raise RuntimeError(
f"Dynadot API rejected {method} {path} with HTTP {error.code}"
f"Dynadot API rejected {method} {path} with HTTP {error.code}{detail}"
) from error
except urllib.error.URLError as error:
raise RuntimeError(