Fix ipapi.is response parsing: handle flat string format
docker-build-push / build-push (push) Successful in 25s

ipapi.is free tier returns company/asn as flat strings ("Google LLC",
"AS15169 Google LLC") not nested dicts. The old code assumed nested
objects and crashed with AttributeError when trying to call .get() on
strings. This broke IP-based signup scrutiny for every new signup.

Now handles both formats (string and dict) for backward compat. Route
field handling also simplified since free tier doesn't nest asn data.

Fixes signup accounts falling through without IP classification.
This commit is contained in:
pmb
2026-09-05 23:24:25 -07:00
parent 65c9bd5a9e
commit 1a3e112946
+16 -3
View File
@@ -937,16 +937,29 @@ def classify_signup_ip(ip: str) -> tuple[str, str, bool, str]:
log.warning("ipapi.is lookup failed for ip=%s: %s", ip, exc) log.warning("ipapi.is lookup failed for ip=%s: %s", ip, exc)
bounds = _ip_range_bounds(ip) bounds = _ip_range_bounds(ip)
return "unknown", "", False, bounds[3] if bounds else f"{ip}/32" return "unknown", "", False, bounds[3] if bounds else f"{ip}/32"
# ipapi.is returns company/asn as flat strings, not nested dicts
company = data.get("company")
org_name = company if isinstance(company, str) else (company or {}).get("name", "")
if not org_name:
asn_str = data.get("asn", "")
if isinstance(asn_str, str) and " " in asn_str:
org_name = asn_str.split(" ", 1)[1] # "AS15169 Google LLC" → "Google LLC"
asn_obj = data.get("asn")
if isinstance(asn_obj, dict):
route = asn_obj.get("route", "")
else:
route = "" # ipapi.is free tier returns asn as string, not nested object
intel = { intel = {
"is_datacenter": bool(data.get("is_datacenter")), "is_datacenter": bool(data.get("is_datacenter")),
"is_vpn": bool(data.get("is_vpn")), "is_vpn": bool(data.get("is_vpn")),
"is_proxy": bool(data.get("is_proxy")), "is_proxy": bool(data.get("is_proxy")),
"is_tor": bool(data.get("is_tor")), "is_tor": bool(data.get("is_tor")),
"is_abuser": bool(data.get("is_abuser")), "is_abuser": bool(data.get("is_abuser")),
"org": ((data.get("company") or {}).get("name") "org": org_name,
or (data.get("asn") or {}).get("org") or ""),
} }
route = (data.get("asn") or {}).get("route") or ""
bounds = _ip_range_bounds(ip, route) bounds = _ip_range_bounds(ip, route)
intel["cidr"] = bounds[3] if bounds else f"{ip}/32" intel["cidr"] = bounds[3] if bounds else f"{ip}/32"
cache_ip_intel(ip, intel, route) cache_ip_intel(ip, intel, route)