This commit is contained in:
dekun
2026-05-30 16:01:46 +08:00
parent 1993c7b4b1
commit e96a386d35
5 changed files with 240 additions and 8 deletions
+131 -4
View File
@@ -190,6 +190,85 @@ def _resolve_hub_embed_service(body: dict) -> tuple[str | None, str | None, str
return base, next_path, None
def _resolve_gate_scout_embed_service(
body: dict,
) -> tuple[str | None, str | None, str | None, str | None]:
"""返回 (base_origin, default_next_path, embed_kind, error_detail)。"""
sid = body.get("service_id")
base = (body.get("base_url") or "").strip().rstrip("/")
next_path = (body.get("next") or "/dashboard").strip() or "/dashboard"
embed_kind = (body.get("embed_kind") or "").strip().lower()
if not next_path.startswith("/"):
next_path = "/" + next_path
if sid:
svc = db.session.get(Service, int(sid))
if not svc or not svc.is_gate_scout_embed():
return None, None, None, "服务不存在或未标记为 Gate 扫单嵌入"
base = svc.build_origin()
embed_kind = (svc.embed_kind or "").strip().lower()
if not body.get("next") or next_path == "/dashboard":
p = (svc.path or "/dashboard").strip() or "/dashboard"
next_path = p if p.startswith("/") else "/" + p
if not base:
return None, None, None, "缺少 base_url"
if embed_kind in ("gate_exec", "exec"):
login_path = "/login"
else:
login_path = "/api/auth/login"
return base, next_path, login_path, None
def _gate_scout_api_login(
base: str, username: str, password: str, *, login_path: str, next_path: str
) -> tuple[str | None, str | None]:
"""服务端登录 Gate 扫单/执行器,返回 (完整 embed_auth_url, error_detail)。"""
payload = json.dumps(
{"username": username, "password": password, "embed": "1", "next": next_path}
).encode("utf-8")
req = urllib.request.Request(
f"{base.rstrip('/')}{login_path}",
data=payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"X-Nav-Embed": "1",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
raw = resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
err_body = e.read().decode("utf-8", errors="replace")
detail = json.loads(err_body).get("detail", err_body)
except Exception:
detail = str(e)
return None, detail or "Gate 扫单登录失败"
except Exception as e:
return None, f"无法连接 Gate 服务: {e}"
try:
data = json.loads(raw)
except json.JSONDecodeError:
return None, "Gate 服务返回非 JSON"
if not data.get("ok"):
return None, data.get("detail") or "登录失败"
embed_url = (data.get("embed_auth_url") or "").strip()
if embed_url.startswith("/"):
embed_url = base.rstrip("/") + embed_url
if not embed_url:
token = (data.get("session_token") or "").strip()
if token:
q = urlencode({"token": token, "next": next_path, "embed": "1"})
embed_url = f"{base.rstrip('/')}/embed-auth?{q}"
if not embed_url:
return None, "未返回 embed_auth_url,请确认云端已设置 NAV_EMBED_SESSION=1 并重启 PM2"
return embed_url, None
def create_app() -> Flask:
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ.get("NAV_SECRET_KEY") or secrets.token_hex(32)
@@ -265,6 +344,8 @@ def create_app() -> Flask:
"index.html",
grouped=grouped,
hub_auto_login=os.environ.get("NAV_HUB_AUTO_LOGIN", "").strip() == "1",
gate_scout_auto_login=os.environ.get("NAV_GATE_SCOUT_AUTO_LOGIN", "").strip()
== "1",
)
@app.route("/api/embed/hub-login", methods=["POST"])
@@ -297,6 +378,47 @@ def create_app() -> Flask:
embed_url = f"{base}/embed-auth?{q}"
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
@app.route("/api/embed/gate-scout-login", methods=["POST"])
@csrf.exempt
@login_required
def api_embed_gate_scout_login():
"""
本地导航代登录 Gate 扫描端/执行器:服务端请求 /api/auth/login 或 /login
再让 iframe 打开 /embed-auth 写入 SameSite=None Cookie。
"""
body = request.get_json(silent=True) or {}
username = (
body.get("username") or os.environ.get("NAV_GATE_SCOUT_USERNAME") or ""
).strip()
password = body.get("password")
if password is None:
password = os.environ.get("NAV_GATE_SCOUT_PASSWORD") or ""
password = str(password)
base, next_path, login_path, err = _resolve_gate_scout_embed_service(body)
if err:
return jsonify({"ok": False, "detail": err}), 400
if not username or not password:
return jsonify(
{
"ok": False,
"detail": "缺少 Gate 扫单用户名或密码(可配置 NAV_GATE_SCOUT_USERNAME / NAV_GATE_SCOUT_PASSWORD",
}
), 400
embed_url, err = _gate_scout_api_login(
base,
username,
password,
login_path=login_path,
next_path=next_path,
)
if err:
status = 401 if "登录" in err or "401" in err or "密码" in err else 502
return jsonify({"ok": False, "detail": err}), status
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
@app.route("/api/embed/hub-instance-url", methods=["POST"])
@csrf.exempt
@login_required
@@ -620,12 +742,12 @@ def _ensure_gate_scout_services() -> None:
db.session.flush()
defs = (
("Gate 扫描端", scout_host, scout_port, scout_path, 0),
("Gate 下单执行器", exec_host, exec_port, exec_path, 10),
("Gate 扫描端", scout_host, scout_port, scout_path, 0, "gate_scout"),
("Gate 下单执行器", exec_host, exec_port, exec_path, 10, "gate_exec"),
)
added = 0
updated = 0
for name, h, port, path, order in defs:
for name, h, port, path, order, embed_k in defs:
existing = Service.query.filter_by(group_id=g.id, name=name).first()
if existing:
if update_existing and (
@@ -633,11 +755,16 @@ def _ensure_gate_scout_services() -> None:
or existing.host != h
or existing.port != port
or existing.path != path
or (existing.embed_kind or "") != embed_k
):
existing.scheme = scheme
existing.host = h
existing.port = port
existing.path = path
existing.embed_kind = embed_k
updated += 1
elif not (existing.embed_kind or "").strip():
existing.embed_kind = embed_k
updated += 1
continue
db.session.add(
@@ -649,7 +776,7 @@ def _ensure_gate_scout_services() -> None:
path=path,
sort_order=order,
group_id=g.id,
embed_kind="",
embed_kind=embed_k,
)
)
added += 1