"""iframe 嵌入登录(LocalNav 代签 + /embed-auth 写 SameSite=None Cookie)。""" from __future__ import annotations import base64 import hashlib import hmac import json import os import time from secrets import compare_digest EMBED_BOOTSTRAP_TTL_SEC = int(os.getenv("NAV_EMBED_BOOTSTRAP_TTL_SEC", "120")) def _secret(explicit: str | None = None) -> bytes: raw = (explicit or os.getenv("NAV_SESSION_SECRET") or "").strip() if not raw: raw = "gate-scout-nav-embed-insecure" return raw.encode("utf-8") def _b64url_encode(data: bytes) -> str: return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=") def _b64url_decode(text: str) -> bytes: pad = "=" * (-len(text) % 4) return base64.urlsafe_b64decode(text + pad) def create_embed_bootstrap_token(username: str, *, secret: str | None = None) -> str: """短效 token,供 /embed-auth 在 iframe 内写入 session。""" payload = { "kind": "embed", "exp": int(time.time()) + max(30, EMBED_BOOTSTRAP_TTL_SEC), "u": (username or "admin").strip(), } body = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode("utf-8")) sig = hmac.new(_secret(secret), body.encode("ascii"), hashlib.sha256).hexdigest() return f"{body}.{sig}" def validate_embed_bootstrap_token(token: str | None, *, secret: str | None = None) -> tuple[bool, str]: raw = (token or "").strip() if not raw or "." not in raw: return False, "" body, sig = raw.rsplit(".", 1) try: expect = hmac.new(_secret(secret), body.encode("ascii"), hashlib.sha256).hexdigest() if not compare_digest(expect, sig): return False, "" payload = json.loads(_b64url_decode(body)) except Exception: return False, "" if not isinstance(payload, dict) or payload.get("kind") != "embed": return False, "" if int(payload.get("exp") or 0) <= int(time.time()): return False, "" return True, str(payload.get("u") or "admin").strip() def safe_next_path(raw: str | None) -> str: p = (raw or "/dashboard").strip() or "/dashboard" if not p.startswith("/") or p.startswith("//") or "://" in p: return "/dashboard" return p def request_is_https(request) -> bool: proto = ( (request.headers.get("x-forwarded-proto") or getattr(request.url, "scheme", "http") or "http") .split(",")[0] .strip() .lower() ) return proto == "https" def nav_embed_session_active() -> bool: raw = (os.getenv("NAV_EMBED_SESSION") or "auto").strip().lower() if raw in ("1", "true", "yes", "on"): return True if raw in ("0", "false", "no", "off"): return False origins = (os.getenv("NAV_EMBED_ORIGINS") or "").strip() return bool(origins and origins != "*")