diff --git a/navi/api/routes/sessions.py b/navi/api/routes/sessions.py index beb32d1..ae9ba80 100644 --- a/navi/api/routes/sessions.py +++ b/navi/api/routes/sessions.py @@ -270,13 +270,15 @@ async def get_session_content( session_id: str, store: Annotated[SessionStore, Depends(get_session_store)], - user: Annotated[User, Depends(require_user)], ) -> dict: - """Return published inline content records for this session.""" + """Return published inline content records for this session. + + Public — no auth required. The session ID acts as an unguessable + capability token for accessing shared/published files. + """ session = await store.get(session_id) if session is None: raise HTTPException(status_code=404, detail="Session not found") - check_session_access(session, user) return {"session_id": session.id, "content": await list_for_session(session_id)} @@ -357,14 +359,16 @@ session_id: str, filename: str, store: Annotated[SessionStore, Depends(get_session_store)], - user: Annotated[User, Depends(require_user)], download: bool = False, ) -> FileResponse: - """Download a file from the session's file directory.""" + """Download a file from the session's file directory. + + Public — no auth required. The session ID acts as an unguessable + capability token for accessing shared/published files. + """ session = await store.get(session_id) if session is None: raise HTTPException(status_code=404, detail="Session not found") - check_session_access(session, user, permission="navi.files.read_all") # Resolve and verify the file is within the session directory (no path traversal) base = session_dir(session_id).resolve() diff --git a/tests/unit/api/test_session_files.py b/tests/unit/api/test_session_files.py index eb83297..526859d 100644 --- a/tests/unit/api/test_session_files.py +++ b/tests/unit/api/test_session_files.py @@ -85,7 +85,7 @@ session_id = await _session_id(store) with pytest.raises(HTTPException) as exc: - await sessions_mod.download_file(session_id, "../secret.txt", store, _FAKE_USER) + await sessions_mod.download_file(session_id, "../secret.txt", store) assert exc.value.status_code == 403 @@ -95,7 +95,7 @@ dest = session_files_mod.ensure_session_dir(session_id) / "page.html" dest.write_text("

Hello

") - response = await sessions_mod.download_file(session_id, "page.html", store, _FAKE_USER) + response = await sessions_mod.download_file(session_id, "page.html", store) assert response.media_type == "text/html" assert "inline" in response.headers["content-disposition"] @@ -106,7 +106,7 @@ dest = session_files_mod.ensure_session_dir(session_id) / "page.html" dest.write_text("

Hello

") - response = await sessions_mod.download_file(session_id, "page.html", store, _FAKE_USER, download=True) + response = await sessions_mod.download_file(session_id, "page.html", store, download=True) assert response.media_type == "text/html" assert "attachment" in response.headers["content-disposition"] @@ -117,7 +117,7 @@ dest = session_files_mod.ensure_session_dir(session_id) / "bundle.zip" dest.write_bytes(b"zip") - response = await sessions_mod.download_file(session_id, "bundle.zip", store, _FAKE_USER) + response = await sessions_mod.download_file(session_id, "bundle.zip", store) assert response.media_type == "application/zip" assert "attachment" in response.headers["content-disposition"] @@ -132,6 +132,6 @@ monkeypatch.setattr(sessions_mod, "list_for_session", fake_list_for_session) - result = await sessions_mod.get_session_content(session_id, store, _FAKE_USER) + result = await sessions_mod.get_session_content(session_id, store) assert result == {"session_id": session_id, "content": [{"filename": "logo.svg"}]}