This commit is contained in:
dekun
2026-05-30 16:01:35 +08:00
parent 979054546c
commit cdbe087202
6 changed files with 235 additions and 20 deletions
+55 -13
View File
@@ -3,6 +3,9 @@
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:
@@ -19,26 +22,33 @@ def nav_embed_origins() -> str:
def nav_session_middleware_kwargs() -> dict:
"""
LocalNav 等跨站 iframe 内登录须 SameSite=None + Secure(仅 HTTPS 站点有效)。
NAV_EMBED_SESSION=1 强制开启;auto 时在配置了 NAV_EMBED_ORIGINS 时开启。
"""
raw = (os.getenv("NAV_EMBED_SESSION") or "auto").strip().lower()
if raw in ("0", "false", "no", "off"):
"""跨站 iframeSameSite=Nonehttps_only 保持 False,由响应 patch 补 Secure。"""
if not nav_embed_session_active():
return {"same_site": "lax", "https_only": False}
if raw in ("1", "true", "yes", "on"):
return {"same_site": "none", "https_only": True}
if raw == "auto":
origins = nav_embed_origins()
if origins and origins != "*":
return {"same_site": "none", "https_only": True}
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):
@@ -48,4 +58,36 @@ def install_nav_embed(app) -> None:
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)