feat: enhance tenant resolution logic with host candidate handling

This commit is contained in:
2026-05-20 21:17:12 +02:00
parent c69231fa77
commit da17667d90
+40 -6
View File
@@ -66,6 +66,27 @@ def _parse_port_from_host(host):
return host, None return host, None
def _first_host_token(value):
raw = str(value or '').strip()
if not raw:
return ''
return raw.split(',', 1)[0].strip().lower()
def _request_host_candidates():
candidates = []
for header_name in ('X-Forwarded-Host', 'X-Original-Host', 'Host'):
token = _first_host_token(request.headers.get(header_name, ''))
if token and token not in candidates:
candidates.append(token)
direct_host = _first_host_token(getattr(request, 'host', ''))
if direct_host and direct_host not in candidates:
candidates.append(direct_host)
return candidates
def _tenant_id_for_port(port): def _tenant_id_for_port(port):
"""Map a host port to a registered tenant ID via tenant configs or env overrides.""" """Map a host port to a registered tenant ID via tenant configs or env overrides."""
for tenant_id, config in TENANT_REGISTRY.items(): for tenant_id, config in TENANT_REGISTRY.items():
@@ -241,12 +262,20 @@ class TenantContext:
session['tenant_id'] = matched_tenant session['tenant_id'] = matched_tenant
return self._get_db_name(matched_tenant) return self._get_db_name(matched_tenant)
# Priority 2: Port-based tenant mapping # Priority 2: Port/host based tenant mapping
host = request.host.lower() host_candidates = _request_host_candidates()
primary_host = host_candidates[0] if host_candidates else _first_host_token(getattr(request, 'host', ''))
logger.info(
"Tenant resolution start: request.host=%s host_candidates=%s request.headers=%s",
getattr(request, 'host', ''),
host_candidates,
dict(request.headers),
)
for host in host_candidates:
hostname, port = _parse_port_from_host(host) hostname, port = _parse_port_from_host(host)
self.port = port
logger.info(f"Tenant resolution start: request.host={host} request.headers={dict(request.headers)}")
if port: if port:
self.port = port
tenant_from_port = _tenant_id_for_port(port) tenant_from_port = _tenant_id_for_port(port)
if tenant_from_port: if tenant_from_port:
self.tenant_id = tenant_from_port self.tenant_id = tenant_from_port
@@ -259,7 +288,12 @@ class TenantContext:
logger.info(f"Tenant port not mapped: host={host} port={port}") logger.info(f"Tenant port not mapped: host={host} port={port}")
# Priority 3: Subdomain extraction # Priority 3: Subdomain extraction
host_without_port = (hostname or '').strip().lower() for host in host_candidates:
host_without_port, _ = _parse_port_from_host(host)
host_without_port = (host_without_port or '').strip().lower()
if not host_without_port:
continue
direct_host_match = _find_registered_tenant_id(host_without_port) direct_host_match = _find_registered_tenant_id(host_without_port)
if direct_host_match: if direct_host_match:
self.subdomain = host_without_port self.subdomain = host_without_port
@@ -296,7 +330,7 @@ class TenantContext:
self.tenant_id = session_tenant self.tenant_id = session_tenant
self.config = get_tenant_config(session_tenant) self.config = get_tenant_config(session_tenant)
logger.info( logger.info(
f"Tenant resolution by session: host={host} tenant={session_tenant} config={self.config}" f"Tenant resolution by session: host={primary_host} tenant={session_tenant} config={self.config}"
) )
return self._get_db_name(session_tenant) return self._get_db_name(session_tenant)