修复中控内嵌:跨站 Cookie 被拦时用 embed_sso 维持登录。

SSO 写入 sessionStorage,fetch 自动带 X-Hub-Embed-SSO,避免期权链 API 401。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-01 11:56:51 +08:00
parent e7a4934068
commit 193c02eda8
8 changed files with 131 additions and 2 deletions
+63
View File
@@ -0,0 +1,63 @@
/**
* 中控 iframe 嵌入鉴权:跨站时第三方 Cookie 常被拦截,改用 embed_sso 头维持登录.
* 须在其它业务脚本之前加载.
*/
(function () {
"use strict";
var KEY = "hub_embed_sso";
try {
var q = new URLSearchParams(location.search || "");
var fromUrl = (q.get("embed_sso") || "").trim();
if (fromUrl) {
sessionStorage.setItem(KEY, fromUrl);
q.delete("embed_sso");
var next = location.pathname;
var rest = q.toString();
if (rest) next += "?" + rest;
next += location.hash || "";
history.replaceState(null, "", next);
}
} catch (_) {}
function currentToken() {
try {
return (sessionStorage.getItem(KEY) || "").trim();
} catch (_) {
return "";
}
}
function withAuthHeaders(init) {
var tok = currentToken();
if (!tok) return init;
init = init ? Object.assign({}, init) : {};
var headers;
try {
headers = new Headers(init.headers || {});
} catch (_) {
headers = new Headers();
}
if (!headers.has("X-Hub-Embed-SSO")) {
headers.set("X-Hub-Embed-SSO", tok);
}
init.headers = headers;
if (!init.credentials) init.credentials = "same-origin";
return init;
}
if (typeof window.fetch === "function") {
var origFetch = window.fetch.bind(window);
window.fetch = function (input, init) {
return origFetch(input, withAuthHeaders(init));
};
}
window.HubEmbedAuth = {
token: currentToken,
clear: function () {
try {
sessionStorage.removeItem(KEY);
} catch (_) {}
},
};
})();
+19
View File
@@ -83,6 +83,25 @@ def build_embed_open_url(
return f"{root}/hub_sso?{q}"
def extract_embed_sso(request) -> str:
"""中控 iframe 内用的短期会话令牌(Cookie 被拦时的后备)."""
raw = (request.headers.get("X-Hub-Embed-SSO") or "").strip()
if raw:
return raw
return (request.args.get("embed_sso") or "").strip()
def hub_embed_sso_ok(request) -> bool:
tok = extract_embed_sso(request)
return bool(tok and verify_sso_token(tok))
def make_embed_session_token(bridge_token: str | None = None, *, ttl_sec: int = 604800) -> str:
"""嵌入会话令牌(默认 7 天),算法与 SSO 相同."""
secret = (bridge_token if bridge_token is not None else hub_bridge_token()).strip()
return make_sso_token(secret, ttl_sec=ttl_sec)
def frame_ancestors_header_value() -> Optional[str]:
"""
HUB_EMBED_FRAME_ANCESTORS: 空格分隔 Origin,如 https://hub.local http://192.168.1.10:5100
+8 -1
View File
@@ -4,10 +4,13 @@ from __future__ import annotations
from typing import Any
from flask import Flask, jsonify, redirect, request, session
from urllib.parse import quote
from lib.hub.hub_bridge_auth import (
frame_ancestors_header_value,
hub_bridge_token,
hub_bridge_token_ok,
make_embed_session_token,
verify_sso_token,
)
@@ -123,7 +126,11 @@ def register_hub_bridge_routes(app: Flask, app_module: Any) -> None:
)
session["logged_in"] = True
session.permanent = True
return redirect(next_path)
session.modified = True
# iframe 跨站时常丢 Cookie:附带可放入 sessionStorage 的 embed_sso
embed_tok = make_embed_session_token(hub_bridge_token(), ttl_sec=604800)
joiner = "&" if "?" in next_path else "?"
return redirect(f"{next_path}{joiner}embed_sso={quote(embed_tok, safe='')}")
@app.after_request
def _hub_frame_ancestors(resp):
@@ -44,6 +44,17 @@ def _api_login_required(hub_token_write_allowed: bool = False):
# crypto_okx 可无 lib.hub;鉴权与中控 request_allowed 对齐
allowed = bool(auth_disabled or logged_in)
if not allowed:
try:
from lib.hub.hub_bridge_auth import hub_embed_sso_ok
if hub_embed_sso_ok(request):
session["logged_in"] = True
session.permanent = True
session.modified = True
allowed = True
except Exception:
pass
if not allowed and hub_hdr and bridge and hub_hdr == bridge:
allowed = True
if not allowed:
+1
View File
@@ -28,6 +28,7 @@ _COMMON_STATIC_ASSETS: dict[str, str] = {
"journal_form_save.js": "application/javascript; charset=utf-8",
"instance_page.css": "text/css; charset=utf-8",
"instance_embed.js": "application/javascript; charset=utf-8",
"hub_embed_auth.js": "application/javascript; charset=utf-8",
"instance_mobile_nav.js": "application/javascript; charset=utf-8",
"instance_stats.js": "application/javascript; charset=utf-8",
"instance_live.js": "application/javascript; charset=utf-8",
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/hub_embed_auth.js?v=1"></script>
<script src="/static/instance_theme.js?v=51"></script>
<script src="/static/autofill_guard.js?v=1"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">