From 8ca27eb573a1ae80c3d9d453d92211483c6a81e5 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Sun, 10 May 2026 14:47:09 +0900 Subject: [PATCH] =?UTF-8?q?fix(markdown):=20img=20auth=20via=20=3Ftoken=3D?= =?UTF-8?q?=20query=20param=20(Authorization=20header=20=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EC=9B=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `` 가 Authorization header 를 못 보내서 /api/documents/{id}/images/{key}/raw 가 401 반환 → 이미지 안 보임. 기존 /file?token= iframe 패턴과 동일하게 access token 쿼리 파라미터로 전달. backend: get_current_user 의존성 제거하고 token 쿼리 파라미터 직접 검증 (기존 /file 엔드포인트와 동일 흐름). frontend: MarkdownDoc 의 swap selector 가 img.src 에 ?token={getAccessToken()} 부여. 로그아웃 상태면 placeholder 유지. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/documents.py | 16 ++++++++++++++-- frontend/src/lib/components/MarkdownDoc.svelte | 10 +++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/api/documents.py b/app/api/documents.py index a59e10d..261781e 100644 --- a/app/api/documents.py +++ b/app/api/documents.py @@ -675,14 +675,26 @@ async def get_document_file( async def get_document_image_raw( doc_id: int, image_key: str, - user: Annotated[User, Depends(get_current_user)], session: Annotated[AsyncSession, Depends(get_session)], + token: str | None = Query(None, description="Bearer token (img 태그용)"), ): """marker 추출 이미지 raw bytes (Phase 1B.5). md_content 안의 `![alt](docimg:img_NNN)` ref 를 frontend selector 가 이 라우트로 변환. - 인증된 사용자만 응답 (단일 사용자 환경, ownership 컬럼 없음 — get_current_user 게이트 충분). + 인증된 사용자만 응답 (단일 사용자 환경, ownership 컬럼 없음). + + 인증: `` 는 Authorization header 를 못 보내므로 `?token=` 쿼리 파라미터 + 로 access token 을 전달 — 기존 `/{doc_id}/file?token=` 엔드포인트 (iframe 용) 와 + 동일 패턴. """ + from core.auth import decode_token + + if not token: + raise HTTPException(status_code=401, detail="토큰이 필요합니다") + payload = decode_token(token) + if not payload or payload.get("type") != "access": + raise HTTPException(status_code=401, detail="유효하지 않은 토큰") + # 문서 존재 확인 (image_key 만 있고 doc 가 사라진 케이스 차단) doc = await session.get(Document, doc_id) if doc is None: diff --git a/frontend/src/lib/components/MarkdownDoc.svelte b/frontend/src/lib/components/MarkdownDoc.svelte index 01df7c2..fcd7313 100644 --- a/frontend/src/lib/components/MarkdownDoc.svelte +++ b/frontend/src/lib/components/MarkdownDoc.svelte @@ -16,6 +16,7 @@ * - md_status badge (processing/success/skipped/failed) — MarkdownStatusBadge 위임 */ import { renderDocMarkdown } from '$lib/utils/docMarkdown'; + import { getAccessToken } from '$lib/api'; import MarkdownStatusBadge from '$lib/components/MarkdownStatusBadge.svelte'; type Props = { @@ -115,8 +116,15 @@ if (!key) continue; const alt = ph.getAttribute('data-md-image-alt') ?? ''; + // 는 Authorization header 를 못 보내므로 ?token= 쿼리 파라미터로 access + // token 전달 (기존 /api/documents/{id}/file?token= iframe 패턴과 동일). + const accessToken = getAccessToken(); + if (!accessToken) { + // 로그아웃 상태 — placeholder 유지 + continue; + } const img = document.createElement('img'); - img.src = `/api/documents/${documentId}/images/${encodeURIComponent(key)}/raw`; + img.src = `/api/documents/${documentId}/images/${encodeURIComponent(key)}/raw?token=${encodeURIComponent(accessToken)}`; img.alt = alt; img.loading = 'lazy'; img.className = 'md-image';