From 381ea8f221f426c16aba5b585c0e79b09fc3aab7 Mon Sep 17 00:00:00 2001 From: ysj7705 Date: Tue, 13 Oct 2026 21:46:42 +0900 Subject: [PATCH] Support serving Document Server under an app sub-path. - toInternalOnlyOfficeUrl swaps the whole public prefix (e.g. https://office.example.com/ds) for the internal base, so saved-file downloads don't hit http://documentserver/ds/... and 404. - README: note the proxy setup (strip prefix, X-Forwarded-Host with path). Co-Authored-By: Claude Opus 5.5 --- README.md | 2 ++ packages/backend/src/services/onlyOffice.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 235d16d..946f424 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ volumes: documentserver_data: ``` +Document Server를 앱과 같은 도메인의 하위 경로(위 예시의 `/ds`)로 두면 편집기 iframe이 같은 출처가 되어 `X-Frame-Options: SAMEORIGIN`에 막히지 않습니다. 리버스 프록시는 접두어를 떼고 Document Server로 넘기면서 `X-Forwarded-Host`에 경로까지 넣어야 합니다(예: Caddy `handle_path /ds/*` + `header_up X-Forwarded-Host {host}/ds`). + ## CI/CD (Jenkins) [`Jenkinsfile`](Jenkinsfile) 이 `main` PR/브랜치에서 `npm ci` + `npm run build`를 돌리고, **`main` 브랜치 빌드**(PR 제외)에서만 이미지를 push한 뒤 서버에 SSH로 배포합니다. diff --git a/packages/backend/src/services/onlyOffice.ts b/packages/backend/src/services/onlyOffice.ts index 6b8d684..34bba7d 100644 --- a/packages/backend/src/services/onlyOffice.ts +++ b/packages/backend/src/services/onlyOffice.ts @@ -57,6 +57,20 @@ export function toInternalOnlyOfficeUrl(raw: string): string { const target = new URL(raw); const publicBase = new URL(config.onlyOfficeUrl); const internalBase = new URL(config.onlyOfficeInternalUrl); + // Public URL may be a sub-path of the app (e.g. https://office.example.com/ds): + // swap the whole prefix, not just the host, so /ds doesn't leak inward. + const publicPath = publicBase.pathname.replace(/\/$/, ''); + if ( + publicPath && + target.host === publicBase.host && + target.pathname.startsWith(`${publicPath}/`) + ) { + return ( + config.onlyOfficeInternalUrl + + target.pathname.slice(publicPath.length) + + target.search + ); + } if ( target.host === publicBase.host || target.host === internalBase.host