65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""Exempt loopback addresses (127.0.0.1 / ::1) from Home Assistant's HTTP login-ban handling.
|
|
|
|
Home Assistant has no built-in way to whitelist an IP from the ``http`` ban
|
|
system. Local tools running on the same host (e.g. a VS Code session that
|
|
queries ``/api/config`` without a token) therefore show up as failed login
|
|
attempts from ``127.0.0.1`` and can eventually get localhost banned, which
|
|
would break legitimate local access.
|
|
|
|
This integration wraps ``process_wrong_login`` so that any request whose remote
|
|
address is a loopback address is silently ignored. External addresses are still
|
|
processed normally, so ``login_attempts_threshold`` protection stays intact.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from ipaddress import ip_address
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DOMAIN = "loopback_ban_exempt"
|
|
|
|
# Modules that hold a reference to process_wrong_login (either the definition
|
|
# itself or an ``from ... import process_wrong_login``). All must be patched.
|
|
_TARGET_MODULES = (
|
|
"homeassistant.components.http.ban",
|
|
"homeassistant.components.auth.login_flow",
|
|
"homeassistant.components.websocket_api.auth",
|
|
)
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
"""Patch the ban handler to skip loopback addresses."""
|
|
from homeassistant.components.http import ban as ban_module
|
|
|
|
original = ban_module.process_wrong_login
|
|
|
|
async def process_wrong_login(request):
|
|
"""Ignore loopback, otherwise defer to the original handler."""
|
|
remote = getattr(request, "remote", None)
|
|
try:
|
|
if remote is not None and ip_address(remote).is_loopback:
|
|
return None
|
|
except ValueError:
|
|
pass
|
|
return await original(request)
|
|
|
|
patched = 0
|
|
for mod_path in _TARGET_MODULES:
|
|
try:
|
|
module = __import__(mod_path, fromlist=["process_wrong_login"])
|
|
except ImportError:
|
|
continue
|
|
if getattr(module, "process_wrong_login", None) is not None:
|
|
module.process_wrong_login = process_wrong_login
|
|
patched += 1
|
|
|
|
_LOGGER.info(
|
|
"Loopback ban exemption active (patched %s reference(s))", patched
|
|
)
|
|
return True
|