Files
2026-05-30 16:01:35 +08:00

94 lines
2.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""允许本地导航(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:
"""跨站 iframeSameSite=Nonehttps_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)