"""Keep the short domain and Django admin off the public API hostname.""" from __future__ import annotations from django.conf import settings from django.http import Http404, HttpRequest def _normalize_host(host: str) -> str: return host.split(":")[0].lower().rstrip(".") def _host_in(host: str, configured: list[str]) -> bool: needle = _normalize_host(host) raw = host.lower() for entry in configured: if not entry: continue if raw == entry.lower() or needle == _normalize_host(entry): return True return False def is_api_host(host: str) -> bool: return _host_in(host, list(getattr(settings, "SHORT_API_HOSTS", []) or [])) def is_public_host(host: str) -> bool: return _host_in(host, list(getattr(settings, "SHORT_PUBLIC_HOSTS", []) or [])) def is_admin_host(host: str) -> bool: return _host_in(host, list(getattr(settings, "SHORT_ADMIN_HOSTS", []) or [])) class HostSplitMiddleware: """Short host = redirects only. API host = /api/ (Bearer). Admin = local only. A public DNS name may be listed in SHORT_API_HOSTS. Auth, not the network, keeps /api/ closed: missing/wrong Bearer is 401; empty token list is 503. """ def __init__(self, get_response): self.get_response = get_response def __call__(self, request: HttpRequest): path = request.path if path in {"/healthz", "/healthz/"}: return self.get_response(request) host = request.get_host() if path.startswith("/debug"): if not settings.DEBUG or is_public_host(host): raise Http404() return self.get_response(request) if path.startswith("/admin"): if not is_admin_host(host): raise Http404() return self.get_response(request) if path.startswith("/api/"): # Short redirect hostname never serves the API, even if mis-listed. if is_public_host(host) or not is_api_host(host): raise Http404() return self.get_response(request)