"""Login IP allowlist helpers (shared by auth API precheck and AuthService.login).""" from __future__ import annotations import ipaddress from server.config import settings _TRUTHY = frozenset({"true", "1", "yes", "on"}) def is_allowlist_enforced() -> bool: """Whether LOGIN_ALLOWLIST_ENABLED is on.""" val = (getattr(settings, "LOGIN_ALLOWLIST_ENABLED", "false") or "false").lower() return val in _TRUTHY def get_allowed_login_ips() -> list[str]: """Combined subscription + manual IPs from settings.""" sub_ips_raw = (getattr(settings, "LOGIN_SUBSCRIPTION_IPS", "") or "").strip() manual_ips_raw = (getattr(settings, "LOGIN_MANUAL_IPS", "") or "").strip() all_ips_raw = ",".join(filter(None, [sub_ips_raw, manual_ips_raw])) return [ip.strip() for ip in all_ips_raw.split(",") if ip.strip()] def ip_in_allowlist(client_ip: str, allowed: list[str]) -> bool: """Check if client_ip matches any entry in the allowlist.""" try: client_addr = ipaddress.ip_address(client_ip) for entry in allowed: entry = entry.strip() try: if "/" in entry: if client_addr in ipaddress.ip_network(entry, strict=False): return True elif client_addr == ipaddress.ip_address(entry): return True except ValueError: if client_ip == entry: return True except ValueError: return client_ip in allowed return False def check_login_ip(client_ip: str) -> tuple[bool, bool, str | None]: """Evaluate login IP access. Returns: (allowlist_enabled, allowed, message) - allowlist off: (False, True, None) - allowlist on, empty list: (True, True, None) — nothing to enforce - allowlist on, IP ok: (True, True, None) - allowlist on, IP blocked: (True, False, message) """ if not is_allowlist_enforced(): return False, True, None if not client_ip or client_ip in ("", "unknown"): return True, True, None allowed_ips = get_allowed_login_ips() if not allowed_ips: return True, True, None if ip_in_allowlist(client_ip, allowed_ips): return True, True, None return ( True, False, f"当前 IP ({client_ip}) 不在登录白名单中,请检查代理或联系管理员", ) def is_whitelisted_login_ip(client_ip: str) -> bool: """True when allowlist is enforced, non-empty, and client IP matches.""" if not is_allowlist_enforced(): return False if not client_ip or client_ip in ("", "unknown"): return False allowed_ips = get_allowed_login_ips() return bool(allowed_ips) and ip_in_allowlist(client_ip, allowed_ips)