fix: route WeKnora embed through backend prefix
This commit is contained in:
1 parent
6dbaf40f99
commit
54fd0e7316
5 files changed
+81
-17
No files matched your search
+18
-9
@@ -55,6 +55,23 @@ function postThemeToFrame(frame: HTMLIFrameElement | null) {
|
||||
frame.contentWindow.postMessage(collectThemePayload(), targetOrigin);
|
||||
}
|
||||
|
||||
const WEKNORA_BACKEND_PREFIX = "/deerflow";
|
||||
|
||||
function buildFrameUrl(framePath: string) {
|
||||
const backendBase = getBackendBaseURL().replace(/\/+$/, "");
|
||||
const backendOrigin = backendBase
|
||||
? new URL(backendBase, window.location.origin).origin
|
||||
: window.location.origin;
|
||||
if (/^https?:\/\//i.test(framePath)) return new URL(framePath);
|
||||
if (framePath.startsWith("/")) {
|
||||
const path = !framePath.startsWith(`${WEKNORA_BACKEND_PREFIX}/`)
|
||||
? `${WEKNORA_BACKEND_PREFIX}${framePath}`
|
||||
: framePath;
|
||||
return new URL(path, backendOrigin);
|
||||
}
|
||||
return new URL(`${backendBase || backendOrigin}/${framePath.replace(/^\/+/, "")}`);
|
||||
}
|
||||
|
||||
export function WeKnoraKnowledgeDetailPage() {
|
||||
const {
|
||||
knowledge_base_id: strategyKnowledgeBaseId,
|
||||
@@ -88,15 +105,7 @@ export function WeKnoraKnowledgeDetailPage() {
|
||||
.then((session) => {
|
||||
if (cancelled) return;
|
||||
const framePath = session.frame_path || "";
|
||||
const backendBase = getBackendBaseURL().replace(/\/+$/, "");
|
||||
const backendOrigin = backendBase
|
||||
? new URL(backendBase, window.location.origin).origin
|
||||
: window.location.origin;
|
||||
const nextUrl = /^https?:\/\//i.test(framePath)
|
||||
? new URL(framePath)
|
||||
: framePath.startsWith("/")
|
||||
? new URL(framePath, backendOrigin)
|
||||
: new URL(`${backendBase || backendOrigin}/${framePath.replace(/^\/+/, "")}`);
|
||||
const nextUrl = buildFrameUrl(framePath);
|
||||
nextUrl.searchParams.set("theme", collectThemePayload().theme);
|
||||
setFrameSrc(nextUrl.toString());
|
||||
})
|
||||
|
||||
@@ -1647,6 +1647,7 @@ This gateway provides custom endpoints for models, MCP configuration, skills, an
|
||||
# /assets, /locales and /api/v1/*. Register that proxy last so it stays a
|
||||
# true fallback and cannot intercept DeerFlow APIs.
|
||||
app.include_router(llmwiki.proxy_router)
|
||||
app.include_router(llmwiki.proxy_router, prefix="/deerflow")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ proxy_router = APIRouter(tags=["llmwiki-proxy"])
|
||||
|
||||
_MAX_UPLOAD_BYTES = 100 * 1024 * 1024
|
||||
_WEKNORA_ADMIN_SESSION_CACHE: dict[str, dict[str, Any]] = {}
|
||||
_WEKNORA_PUBLIC_PREFIX = "/deerflow"
|
||||
_HOP_BY_HOP_RESPONSE_HEADERS = {
|
||||
"connection",
|
||||
"content-encoding",
|
||||
@@ -517,12 +518,7 @@ def _normalize_public_prefix(value: str | None) -> str:
|
||||
|
||||
|
||||
def _request_public_prefix(request: Request) -> str:
|
||||
headers = request.headers
|
||||
for key in ("x-forwarded-prefix", "x-script-name"):
|
||||
prefix = _normalize_public_prefix(headers.get(key))
|
||||
if prefix:
|
||||
return prefix
|
||||
return _normalize_public_prefix(str(request.scope.get("root_path") or ""))
|
||||
return _WEKNORA_PUBLIC_PREFIX
|
||||
|
||||
|
||||
def _with_public_prefix(prefix: str, path: str) -> str:
|
||||
@@ -531,6 +527,14 @@ def _with_public_prefix(prefix: str, path: str) -> str:
|
||||
return f"{prefix}{path}" if prefix else path
|
||||
|
||||
|
||||
def _without_public_prefix(prefix: str, path: str) -> str:
|
||||
if prefix and path.startswith(f"{prefix}/"):
|
||||
return path[len(prefix):]
|
||||
if prefix and path == prefix:
|
||||
return "/"
|
||||
return path
|
||||
|
||||
|
||||
def _rewrite_weknora_html_public_paths(html: str, public_prefix: str) -> str:
|
||||
if not public_prefix:
|
||||
return html
|
||||
@@ -1538,7 +1542,10 @@ async def proxy_weknora_root_asset(request: Request) -> Response:
|
||||
return await _proxy_weknora_request(
|
||||
request,
|
||||
target_base_url=runtime.web_base_url,
|
||||
upstream_path=str(request.scope.get("path") or request.url.path),
|
||||
upstream_path=_without_public_prefix(
|
||||
_request_public_prefix(request),
|
||||
str(request.scope.get("path") or request.url.path),
|
||||
),
|
||||
ctx=ctx,
|
||||
is_api=False,
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ class WeKnoraEmbedContext:
|
||||
|
||||
|
||||
def is_weknora_embed_proxy_path(path: str) -> bool:
|
||||
path = path.removeprefix("/deerflow")
|
||||
return (
|
||||
path == "/platform"
|
||||
or path.startswith("/platform/")
|
||||
|
||||
@@ -173,15 +173,25 @@ def test_weknora_embed_cookie_bypasses_only_proxy_paths() -> None:
|
||||
cookies = {"deerflow_weknora_embed": raw_cookie}
|
||||
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(_request("/platform/knowledge-bases/kb", cookies=cookies))
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/deerflow/platform/knowledge-bases/kb", cookies=cookies)
|
||||
)
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(_request("/assets/index.js", cookies=cookies))
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(_request("/deerflow/assets/index.js", cookies=cookies))
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(_request("/config.js", cookies=cookies))
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/tdesign-icons/0.4.1/fonts/index.js", cookies=cookies)
|
||||
)
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(_request("/api/v1/knowledge-search", cookies=cookies))
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/deerflow/api/v1/knowledge-search", cookies=cookies)
|
||||
)
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/api/llmwiki/weknora-embed/api/v1/auth/me", cookies=cookies)
|
||||
)
|
||||
assert auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/deerflow/api/llmwiki/weknora-embed/api/v1/auth/me", cookies=cookies)
|
||||
)
|
||||
assert csrf_middleware.is_weknora_embed_proxy_request(_request("/api/v1/knowledge-search", cookies=cookies))
|
||||
assert not auth_middleware._is_weknora_embed_proxy_request(
|
||||
_request("/api/v1/knowledge-search", cookies={"deerflow_weknora_embed": "signed"})
|
||||
@@ -219,7 +229,7 @@ async def test_weknora_session_endpoint_sets_cookie_and_returns_platform_path(mo
|
||||
)
|
||||
|
||||
payload = json.loads(response.body)
|
||||
assert payload["frame_path"] == "/platform/knowledge-bases/remote-kb?tab=wiki"
|
||||
assert payload["frame_path"] == "/deerflow/platform/knowledge-bases/remote-kb?tab=wiki"
|
||||
cookie = response.headers["set-cookie"]
|
||||
assert "deerflow_weknora_embed=" in cookie
|
||||
assert "Secure" in cookie
|
||||
@@ -259,6 +269,36 @@ async def test_weknora_session_endpoint_preserves_reverse_proxy_prefix(monkeypat
|
||||
assert payload["frame_path"] == "/deerflow/platform/knowledge-bases/remote-kb?tab=wiki"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_weknora_session_endpoint_uses_backend_prefix_when_frontend_has_different_prefix(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from app.gateway.routers import llmwiki as llmwiki_router
|
||||
|
||||
async def authorized_mapping(*_: object, **__: object) -> tuple[dict[str, str], str, bool]:
|
||||
return {"id": "local-kb", "weknora_id": "remote-kb", "name": "KB"}, "user-a", False
|
||||
|
||||
class Store:
|
||||
async def get_authorized(self, *_: object, **__: object) -> dict[str, str]:
|
||||
return {"id": "local-kb", "weknora_id": "remote-kb"}
|
||||
|
||||
monkeypatch.setattr(llmwiki_router, "_runtime_for_iframe_proxy", lambda _: object())
|
||||
monkeypatch.setattr(llmwiki_router, "_authorized_mapping", authorized_mapping)
|
||||
monkeypatch.setattr(llmwiki_router, "_store", lambda _: Store())
|
||||
|
||||
response = await llmwiki_router.create_weknora_detail_session(
|
||||
_request(
|
||||
"/api/llmwiki/knowledge-bases/local-kb/weknora/session",
|
||||
request_headers={"Referer": "https://example.test/magentweb/page/workspace/knowledge/local-kb"},
|
||||
),
|
||||
"local-kb",
|
||||
tab="wiki",
|
||||
)
|
||||
|
||||
payload = json.loads(response.body)
|
||||
assert payload["frame_path"] == "/deerflow/platform/knowledge-bases/remote-kb?tab=wiki"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_weknora_client_keeps_anonymous_mode_without_admin_credentials() -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
@@ -1002,6 +1042,12 @@ def test_weknora_iframe_proxy_is_fallback_after_deerflow_api_routes() -> None:
|
||||
== "/api/llmwiki/weknora-embed/api/v1/{proxied_path:path}"
|
||||
)
|
||||
assert _first_gateway_match("GET", "/platform/knowledge-bases/remote-kb") == "/platform/{proxied_path:path}"
|
||||
assert (
|
||||
_first_gateway_match("GET", "/deerflow/platform/knowledge-bases/remote-kb")
|
||||
== "/deerflow/platform/{proxied_path:path}"
|
||||
)
|
||||
assert _first_gateway_match("GET", "/deerflow/assets/index.js") == "/deerflow/assets/{proxied_path:path}"
|
||||
assert _first_gateway_match("GET", "/deerflow/api/v1/knowledge-search") == "/deerflow/api/v1/{proxied_path:path}"
|
||||
|
||||
|
||||
def test_weknora_iframe_hides_current_outer_sidebar_shell() -> None:
|
||||
|
||||
Reference in new issue
Block a user