diff --git a/api/router.py b/api/router.py index 9ac4ead..031284a 100644 --- a/api/router.py +++ b/api/router.py @@ -6,7 +6,7 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPExce from api.state import state, Status from config import load as load_config -from output import list_transcripts +from output import list_transcripts, read_transcript router = APIRouter() _ws_clients: list[WebSocket] = [] @@ -124,6 +124,16 @@ async def get_transcripts(user: dict = Depends(current_user)): return list_transcripts(user_dir) +@router.get("/transcripts/{filename}") +async def get_transcript(filename: str, user: dict = Depends(current_user)): + from fastapi.responses import PlainTextResponse + user_dir = os.path.join(user["output_dir"], user["username"]) + content = read_transcript(user_dir, filename) + if content is None: + raise HTTPException(status_code=404, detail="Nicht gefunden") + return PlainTextResponse(content) + + @router.get("/config") async def get_config(user: dict = Depends(current_user)): return load_config() diff --git a/output.py b/output.py index 46635da..fe82cf1 100644 --- a/output.py +++ b/output.py @@ -35,6 +35,17 @@ def save_transcript( return path +def read_transcript(output_dir: str, filename: str) -> str | None: + """Return file content if filename is a plain .md file inside output_dir.""" + if os.path.basename(filename) != filename or not filename.endswith(".md"): + return None + path = os.path.join(output_dir, filename) + if not os.path.exists(path): + return None + with open(path, encoding="utf-8") as f: + return f.read() + + def list_transcripts(output_dir: str, limit: int = 20) -> list[dict]: if not os.path.exists(output_dir): return [] diff --git a/tests/test_api.py b/tests/test_api.py index 96b8f50..30f215d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -45,6 +45,30 @@ def test_status_requires_auth(): assert r.status_code == 401 +def make_app_for_dir(output_dir: str): + from fastapi import FastAPI + from api.router import router, current_user + app = FastAPI() + app.dependency_overrides[current_user] = lambda: {"username": "", "output_dir": output_dir, "is_admin": False} + app.include_router(router) + return app + + +def test_get_transcript_returns_content(tmp_path): + f = tmp_path / "2026-01-01-0900-test.md" + f.write_text("# Hello\n\ncontent here\n") + client = TestClient(make_app_for_dir(str(tmp_path))) + r = client.get("/transcripts/2026-01-01-0900-test.md") + assert r.status_code == 200 + assert "Hello" in r.text + + +def test_get_transcript_rejects_path_traversal(tmp_path): + client = TestClient(make_app_for_dir(str(tmp_path))) + r = client.get("/transcripts/..%2Fsecret.md") + assert r.status_code == 404 + + def test_login_rejects_wrong_credentials(): import tempfile, os from unittest.mock import patch