"""允许本地导航(LocalNav)iframe 内嵌本服务。环境变量 NAV_ALLOW_EMBED / NAV_EMBED_ORIGINS。""" from __future__ import annotations import os import re from nav_session_auth import nav_embed_session_active, request_is_https def nav_embed_allowed() -> bool: return (os.getenv("NAV_ALLOW_EMBED") or "true").strip().lower() in ( "1", "true", "yes", "on", ) def nav_embed_origins() -> str: return (os.getenv("NAV_EMBED_ORIGINS") or "*").strip() or "*" def nav_session_middleware_kwargs() -> dict: """跨站 iframe:SameSite=None;https_only 保持 False,由响应 patch 补 Secure。""" if not nav_embed_session_active(): return {"same_site": "lax", "https_only": False} return {"same_site": "none", "https_only": False} def install_proxy_headers(app) -> None: if (os.getenv("NAV_TRUST_PROXY") or "").strip().lower() not in ( "1", "true", "yes", "on", ): return try: from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") except Exception: pass def install_nav_embed(app) -> None: if not nav_embed_allowed(): return origins = nav_embed_origins() install_proxy_headers(app) @app.middleware("http") async def _nav_embed_frame_headers(request, call_next): response = await call_next(request) if origins == "*": response.headers["Content-Security-Policy"] = "frame-ancestors *" else: parts = " ".join(o.strip() for o in origins.split(",") if o.strip()) response.headers["Content-Security-Policy"] = f"frame-ancestors 'self' {parts}" if nav_embed_session_active(): _patch_set_cookie_for_embed(response, request) return response def _patch_set_cookie_for_embed(response, request) -> None: """HTTPS 访问时强制 session Cookie 为 SameSite=None; Secure。""" force = (os.getenv("NAV_EMBED_FORCE_SECURE") or "1").strip().lower() in ( "1", "true", "yes", "on", ) if not request_is_https(request) and not force: return raw = response.headers.get("set-cookie") if not raw: return def _fix_one(part: str) -> str: p = part.strip() if not p or "session=" not in p.lower(): return p p = re.sub(r";\s*SameSite=[^;]*", "", p, flags=re.I) p = re.sub(r";\s*Secure(?=;|$)", "", p, flags=re.I) p += "; Secure; SameSite=none" return p if "," in raw and "session=" in raw.lower(): parts = re.split(r",(?=\s*[^;,]+=)", raw) response.headers["set-cookie"] = ", ".join(_fix_one(x) for x in parts) else: response.headers["set-cookie"] = _fix_one(raw)