praca za proxy
This commit is contained in:
104
app/api.py
104
app/api.py
@@ -5,6 +5,7 @@ from .config import settings
|
||||
from .geo import reload_provider
|
||||
import secrets
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
router = APIRouter()
|
||||
security = HTTPBasic()
|
||||
@@ -19,9 +20,108 @@ def _check_admin(creds: HTTPBasicCredentials):
|
||||
raise HTTPException(status_code=401, detail='invalid credentials', headers={"WWW-Authenticate":"Basic"})
|
||||
return True
|
||||
|
||||
def _normalize_ip_str(ip_raw: str) -> str | None:
|
||||
"""Usuń port, whitespace i ewentualne cudzysłowy"""
|
||||
if not ip_raw:
|
||||
return None
|
||||
ip_raw = ip_raw.strip().strip('"').strip("'")
|
||||
# usuń port, np. 1.2.3.4:5678
|
||||
if ':' in ip_raw and ip_raw.count(':') == 1:
|
||||
# prawdopodobnie IPv4:port
|
||||
ip_raw = ip_raw.split(':')[0]
|
||||
# Pozostaw kwestie IPv6 z %zone
|
||||
return ip_raw
|
||||
|
||||
def _is_ip_trusted(ip_str: str) -> bool:
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip_str.split('%')[0])
|
||||
except Exception:
|
||||
return False
|
||||
for net in settings.trusted_proxies:
|
||||
try:
|
||||
if ip in net:
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
||||
def _extract_from_forwarded(header_value: str) -> list[str]:
|
||||
# Forwarded: for=192.0.2.43, for="[2001:db8:cafe::17]";proto=http;by=...
|
||||
ips = []
|
||||
parts = re.split(r',\s*(?=[fF]or=)', header_value)
|
||||
for part in parts:
|
||||
m = re.search(r'for=(?P<val>"[^"]+"|[^;,\s]+)', part)
|
||||
if m:
|
||||
val = m.group('val').strip('"').strip("'")
|
||||
ips.append(val)
|
||||
return ips
|
||||
|
||||
def get_client_ip(request: Request) -> str:
|
||||
# 1) X-Forwarded-For
|
||||
xff = request.headers.get("x-forwarded-for")
|
||||
if xff:
|
||||
# XFF: client, proxy1, proxy2
|
||||
raw_ips = [p.strip() for p in xff.split(",") if p.strip()]
|
||||
# Normalizuj i usuń porty
|
||||
norm_ips = []
|
||||
for raw in raw_ips:
|
||||
v = _normalize_ip_str(raw)
|
||||
if v:
|
||||
norm_ips.append(v)
|
||||
# jeśli mamy zaufane proxy -> zwracamy pierwsze IP, które NIE jest zaufane
|
||||
if settings.trusted_proxies:
|
||||
for ip in norm_ips:
|
||||
try:
|
||||
# ignoruj jeżeli to zaufane proxy
|
||||
if not _is_ip_trusted(ip):
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
# jeśli wszystkie były zaufane, zwróć pierwsze (najbardziej "client" lub lewo)
|
||||
if norm_ips:
|
||||
return norm_ips[0]
|
||||
else:
|
||||
# bez zaufanych proxy: przyjmujemy lewą (first) jako klienta
|
||||
if norm_ips:
|
||||
return norm_ips[0]
|
||||
|
||||
# 2) Forwarded (RFC7239)
|
||||
fwd = request.headers.get("forwarded")
|
||||
if fwd:
|
||||
fwd_ips = _extract_from_forwarded(fwd)
|
||||
norm_ips = [_normalize_ip_str(ip) for ip in fwd_ips if _normalize_ip_str(ip)]
|
||||
if norm_ips:
|
||||
if settings.trusted_proxies:
|
||||
for ip in norm_ips:
|
||||
if not _is_ip_trusted(ip):
|
||||
return ip
|
||||
return norm_ips[0]
|
||||
return norm_ips[0]
|
||||
|
||||
# 3) X-Real-IP
|
||||
xri = request.headers.get("x-real-ip")
|
||||
if xri:
|
||||
v = _normalize_ip_str(xri)
|
||||
if v:
|
||||
if settings.trusted_proxies and _is_ip_trusted(v):
|
||||
# jeśli header wskazuje zaufane proxy - nie używamy go jako klienta
|
||||
pass
|
||||
else:
|
||||
return v
|
||||
|
||||
# Fallback: request.client.host (np. bez reverse-proxy lub jeśli nic innego)
|
||||
try:
|
||||
host = request.client.host
|
||||
if host:
|
||||
return host.split('%')[0] if '%' in host else host
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "0.0.0.0"
|
||||
|
||||
@router.get('/ip')
|
||||
async def my_ip(request: Request, geo=Depends(get_geo)):
|
||||
ip = request.client.host
|
||||
ip = get_client_ip(request)
|
||||
# handle IPv6 mapped IPv4 like ::ffff:1.2.3.4
|
||||
try:
|
||||
ip = ip.split('%')[0]
|
||||
@@ -51,4 +151,4 @@ async def reload(creds: HTTPBasicCredentials = Depends(security)):
|
||||
|
||||
@router.get('/health')
|
||||
async def health():
|
||||
return {'status':'ok'}
|
||||
return {'status':'ok'}
|
Reference in New Issue
Block a user