diff --git a/.memory/worklog.json b/.memory/worklog.json index 29edafa..cfcce1b 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,12 +1,5 @@ { "entries": [ - { - "files_changed": 1, - "hash": "6f92814", - "message": "auto-save 2026-05-10 08:56 (~1)", - "ts": "2026-05-10T08:56:46+08:00", - "type": "commit" - }, { "files_changed": 1, "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-10 08:56 (~1)", @@ -3265,6 +3258,13 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 3 项未提交变更 · 最近提交:auto-save 2026-05-11 17:33 (~1)", "files_changed": 3 + }, + { + "ts": "2026-05-11T17:39:04+08:00", + "type": "commit", + "message": "auto-save 2026-05-11 17:39 (~4)", + "hash": "0ec86e8", + "files_changed": 4 } ] } diff --git a/server/feishu_bridge.py b/server/feishu_bridge.py index 9ca5954..84f044e 100644 --- a/server/feishu_bridge.py +++ b/server/feishu_bridge.py @@ -1114,16 +1114,28 @@ def handle_ui_config_post(headers: dict[str, str], body: dict[str, Any]) -> tupl return 200, {"code": 0, "msg": "ok", "config": config} +def admin_allowed_origins(headers: dict[str, str]) -> set[str]: + origins = _csv_set(_env("HERMES_ADMIN_ORIGINS", "https://hermes.kang-kang.com")) + host = (headers.get("x-forwarded-host") or headers.get("host") or "").split(",", 1)[0].strip() + proto = (headers.get("x-forwarded-proto") or "").split(",", 1)[0].strip() + if host: + origins.add(f"{proto or 'https'}://{host}") + origins.add(f"http://{host}") + return origins + + def is_admin_request(headers: dict[str, str]) -> tuple[bool, int, str]: cookie = headers.get("cookie", "") if "hermes_auth=ok" not in cookie: return False, 401, "login required" origin = headers.get("origin", "") referer = headers.get("referer", "") - allowed = "https://hermes.kang-kang.com" - if origin and origin != allowed: + allowed = admin_allowed_origins(headers) + if origin and origin not in allowed: return False, 403, "invalid origin" - if not origin and referer and not referer.startswith(allowed + "/"): + if not origin and referer and not any( + referer.startswith(item.rstrip("/") + "/") for item in allowed + ): return False, 403, "invalid referer" return True, 200, "ok" diff --git a/src/app.js b/src/app.js index 1295800..21ba635 100644 --- a/src/app.js +++ b/src/app.js @@ -678,7 +678,10 @@ async function refreshFeishuApps() { _feishuAppsLoading = true; box.innerHTML = '
正在读取飞书桥接服务...
'; try { - const res = await fetch("/feishu/apps", { cache: "no-store" }); + const res = await apiFetch("/feishu/apps", { + credentials: "same-origin", + cache: "no-store", + }); if (!res.ok) throw new Error("HTTP " + res.status); const data = await res.json(); const apps = Array.isArray(data.apps) ? data.apps : []; @@ -732,7 +735,7 @@ async function deleteFeishuApp(encodedAppId) { const ok = confirm(`删除飞书机器人 ${appId}?\n\n删除后这个 App ID 的回调地址会立刻失效,Secret / Token 也会从服务器环境文件移除。`); if (!ok) return; try { - const res = await fetch("/feishu/apps/" + encodeURIComponent(appId), { + const res = await apiFetch("/feishu/apps/" + encodeURIComponent(appId), { method: "DELETE", credentials: "same-origin", }); @@ -768,7 +771,7 @@ async function saveFeishuApp(event) { submit.textContent = "正在保存..."; } try { - const res = await fetch("/feishu/apps", { + const res = await apiFetch("/feishu/apps", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, @@ -899,7 +902,7 @@ async function refreshHermesConfig(force = false) { _hermesConfigLoading = true; setHermesConfigStatuses("正在读取线上配置..."); try { - const res = await fetch("/feishu/hermes-config", { + const res = await apiFetch("/feishu/hermes-config", { credentials: "same-origin", cache: "no-store", }); @@ -956,7 +959,7 @@ function snapshotModelOrFields() { } async function postHermesRuntimeConfig(payload) { - const res = await fetch("/feishu/hermes-config", { + const res = await apiFetch("/feishu/hermes-config", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, @@ -1014,7 +1017,7 @@ function applySharedAgents(agents) { } async function fetchUiConfig() { - const res = await fetch(UI_CONFIG_ENDPOINT, { + const res = await apiFetch(UI_CONFIG_ENDPOINT, { credentials: "same-origin", cache: "no-store", }); @@ -1029,7 +1032,7 @@ async function saveSharedConfig(options = {}) { modelProfiles: normalizeModelProfiles(state.modelProfiles), agents: agentsForSharedConfig(), }; - const res = await fetch(UI_CONFIG_ENDPOINT, { + const res = await apiFetch(UI_CONFIG_ENDPOINT, { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" },