fix(preview): authenticate markdown document preview requests (#15589)

## Summary

Fixes [#15585](https://github.com/infiniflow/ragflow/issues/15585).

- Route markdown preview through the shared `request` client (same as
txt/image previewers) so `Authorization` headers and interceptors are
applied consistently.
- Add a unit test covering `AUTH_BETA` token loading for embedded search
auth.

## Root cause

Search result preview for `.md`/`.mdx` used raw `fetch`, which did not
apply the same auth path as other preview types. That led to `401` on
`GET /api/v1/documents/{id}/preview` even when the user was logged in or
using an embedded search `auth` query param.

## Test plan

- [ ] Log in, run a search, open a markdown citation link — preview
loads (no 401).
- [ ] Open an embedded shared search URL with `auth` query param,
preview a markdown file — preview loads.
- [ ] Confirm PDF/txt preview still works in the same search UI.

---------

Co-authored-by: MkDev11 <89318445+bitloi@users.noreply.github.com>
Co-authored-by: Wang Qi <wangq8@outlook.com>
This commit is contained in:
monsterDavid
2026-06-11 00:46:20 -07:00
committed by GitHub
parent 47fb462e46
commit a851228ded
2 changed files with 89 additions and 13 deletions

View File

@@ -156,6 +156,12 @@ def test_load_user_api_token_fallback_and_fallback_exception(monkeypatch, caplog
monkeypatch.setattr(apps_module.Serializer, "loads", _raise_decode)
fallback_user_empty_token = SimpleNamespace(email="fallback@example.com", access_token="")
valid_token = "a" * 32
beta_user = SimpleNamespace(
id="tenant-1",
email="embed@example.com",
access_token=valid_token,
)
async def _case():
monkeypatch.setattr(apps_module.APIToken, "query", lambda **_kwargs: [SimpleNamespace(tenant_id="tenant-1")])
@@ -171,6 +177,29 @@ def test_load_user_api_token_fallback_and_fallback_exception(monkeypatch, caplog
with caplog.at_level(logging.WARNING):
assert apps_module._load_user() is None
def _query_api_token(**kwargs):
if kwargs.get("beta") == "embed-beta":
return [SimpleNamespace(tenant_id="tenant-1")]
return []
def _query_user(**kwargs):
if (
kwargs.get("id") == "tenant-1"
and kwargs.get("status") == apps_module.StatusEnum.VALID.value
):
return [beta_user]
return []
monkeypatch.setattr(apps_module.APIToken, "query", _query_api_token)
monkeypatch.setattr(apps_module.UserService, "query", _query_user)
async with quart_app.test_request_context("/", headers={"Authorization": "Bearer embed-beta"}):
user = apps_module._load_user(auth_types=[apps_module.AUTH_BETA])
assert user is beta_user
assert apps_module.g.auth_type == apps_module.AUTH_BETA
async with quart_app.test_request_context("/", headers={"Authorization": "Bearer invalid-beta"}):
assert apps_module._load_user(auth_types=[apps_module.AUTH_BETA]) is None
_run(_case())
assert "api token fallback failed" in caplog.text

View File

@@ -1,8 +1,9 @@
import { Authorization } from '@/constants/authorization';
import message from '@/components/ui/message';
import { Spin } from '@/components/ui/spin';
import { MarkdownRemarkPluginsLite } from '@/constants/markdown-remark-plugins';
import { cn } from '@/lib/utils';
import FileError from '@/pages/document-viewer/file-error';
import { getAuthorization } from '@/utils/authorization-util';
import request from '@/utils/request';
import React, { useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
@@ -15,16 +16,52 @@ interface MdProps {
export const Md: React.FC<MdProps> = ({ url, className }) => {
const [content, setContent] = useState<string>('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!url) {
setContent('');
setError(null);
setLoading(false);
return;
}
let cancelled = false;
setError(null);
fetch(url, { headers: { [Authorization]: getAuthorization() } })
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch markdown file');
return res.text();
})
.then((text) => setContent(text))
.catch((err) => setError(err.message));
setLoading(true);
const fetchMarkdown = async () => {
try {
const res = await request(url, {
method: 'GET',
responseType: 'blob',
onError: (err: unknown) => {
console.error('Error loading markdown file:', err);
},
});
if (cancelled) return;
const blob = res.data;
const text = await blob.text();
if (cancelled) return;
setContent(text);
} catch (err: unknown) {
if (cancelled) return;
const messageText =
err instanceof Error ? err.message : 'Failed to fetch markdown file';
setError(messageText);
message.error('Failed to load file');
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchMarkdown();
return () => {
cancelled = true;
};
}, [url]);
if (error) return <FileError>{error}</FileError>;
@@ -32,11 +69,21 @@ export const Md: React.FC<MdProps> = ({ url, className }) => {
return (
<div
style={{ padding: 4, overflow: 'scroll' }}
className={cn(className, 'markdown-body h-[calc(100vh - 200px)]')}
className={cn(
className,
'markdown-body relative h-[calc(100vh - 200px)]',
)}
>
<ReactMarkdown remarkPlugins={MarkdownRemarkPluginsLite}>
{content}
</ReactMarkdown>
{loading && (
<div className="absolute inset-0 flex items-center justify-center">
<Spin />
</div>
)}
{!loading && (
<ReactMarkdown remarkPlugins={MarkdownRemarkPluginsLite}>
{content}
</ReactMarkdown>
)}
</div>
);
};