107 lines
4.8 KiB
Python
107 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
from starlette.types import ASGIApp
|
|
|
|
from app.core.db.common import CommonSessionLocal
|
|
from app.modules.domain_management.services import normalize_request_host, resolve_domain_context
|
|
|
|
|
|
class DomainResolverMiddleware(BaseHTTPMiddleware):
|
|
"""Resolve request host to platform / tenant / consultant context.
|
|
|
|
Phase 7T.2 is intentionally read-only:
|
|
- It does not redirect users.
|
|
- It does not change database records.
|
|
- It does not override logged-in user permissions.
|
|
- It only exposes a trusted runtime context on request.state.
|
|
|
|
Later phases use this context for branding, marketplace mode, tenant subdomains,
|
|
consultant domains, and custom domain verification.
|
|
"""
|
|
|
|
def __init__(self, app: ASGIApp) -> None:
|
|
super().__init__(app)
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
host_header = request.headers.get("x-forwarded-host") or request.headers.get("host")
|
|
host = normalize_request_host(host_header)
|
|
|
|
# Safe defaults; every template/route can read these without checking existence.
|
|
request.state.request_host = host
|
|
request.state.domain_resolved = False
|
|
request.state.domain_mapping_id = None
|
|
request.state.domain_name = host
|
|
request.state.domain_type = None
|
|
request.state.domain_tenant_id = None
|
|
request.state.domain_tenant_code = None
|
|
request.state.domain_branch_id = None
|
|
request.state.domain_branch_code = None
|
|
request.state.domain_consultant_id = None
|
|
request.state.domain_parent_tenant_id = None
|
|
request.state.domain_is_verified = False
|
|
request.state.domain_status = None
|
|
request.state.domain_context = {
|
|
"is_resolved": False,
|
|
"host": host,
|
|
"mapping_id": None,
|
|
"domain_name": host,
|
|
"domain_type": None,
|
|
"tenant_id": None,
|
|
"tenant_code": None,
|
|
"branch_id": None,
|
|
"branch_code": None,
|
|
"consultant_id": None,
|
|
"parent_tenant_id": None,
|
|
"is_verified": False,
|
|
"status": None,
|
|
}
|
|
|
|
# Static files and empty/invalid host can proceed without DB lookup.
|
|
if host and not request.url.path.startswith("/static/"):
|
|
db = CommonSessionLocal()
|
|
try:
|
|
resolved = resolve_domain_context(db, host)
|
|
if resolved.is_resolved:
|
|
request.state.domain_resolved = True
|
|
request.state.domain_mapping_id = resolved.mapping_id
|
|
request.state.domain_name = resolved.domain_name
|
|
request.state.domain_type = resolved.domain_type
|
|
request.state.domain_tenant_id = resolved.tenant_id
|
|
request.state.domain_tenant_code = resolved.tenant_code
|
|
request.state.domain_branch_id = resolved.branch_id
|
|
request.state.domain_branch_code = resolved.branch_code
|
|
request.state.domain_consultant_id = resolved.consultant_id
|
|
request.state.domain_parent_tenant_id = resolved.parent_tenant_id
|
|
request.state.domain_is_verified = resolved.is_verified
|
|
request.state.domain_status = resolved.status
|
|
request.state.domain_context = {
|
|
"is_resolved": True,
|
|
"host": resolved.host,
|
|
"mapping_id": resolved.mapping_id,
|
|
"domain_name": resolved.domain_name,
|
|
"domain_type": resolved.domain_type,
|
|
"tenant_id": resolved.tenant_id,
|
|
"tenant_code": resolved.tenant_code,
|
|
"branch_id": resolved.branch_id,
|
|
"branch_code": resolved.branch_code,
|
|
"consultant_id": resolved.consultant_id,
|
|
"parent_tenant_id": resolved.parent_tenant_id,
|
|
"is_verified": resolved.is_verified,
|
|
"status": resolved.status,
|
|
}
|
|
except Exception:
|
|
# Domain resolution must never take the ERP down. If the domain table is
|
|
# missing during deployment or DB is temporarily unavailable, continue
|
|
# with the normal default context.
|
|
pass
|
|
finally:
|
|
db.close()
|
|
|
|
response = await call_next(request)
|
|
if getattr(request.state, "domain_resolved", False):
|
|
response.headers["X-AuditFirm-Domain-Resolved"] = "1"
|
|
response.headers["X-AuditFirm-Domain-Type"] = str(getattr(request.state, "domain_type", "") or "")
|
|
return response
|