diff --git a/apps/api/app/api/routes/tts.py b/apps/api/app/api/routes/tts.py index 15dce80e1..eb9581e07 100755 --- a/apps/api/app/api/routes/tts.py +++ b/apps/api/app/api/routes/tts.py @@ -392,7 +392,12 @@ def preview_tts( actual_voice_id = request.voice_id profile = voice_clone_repo.get(request.voice_id) if profile is not None: - # 命中克隆音色 profile + # 命中克隆音色 profile — 校验归属权限 + if profile.user_id != authenticated_user.user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="无权访问该音色", + ) if not profile.voice_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/tests/unit/test_tts_preview.py b/tests/unit/test_tts_preview.py index 99b2a01d4..6ff37a799 100644 --- a/tests/unit/test_tts_preview.py +++ b/tests/unit/test_tts_preview.py @@ -309,6 +309,7 @@ class TestTTSPreviewEndpoint: # Mock voice clone profile with voice_id mock_profile = MagicMock() + mock_profile.user_id = "user-1" mock_profile.voice_id = "cosyvoice_actual_voice_123" mock_clone_repo = MagicMock() mock_clone_repo.get.return_value = mock_profile @@ -351,6 +352,7 @@ class TestTTSPreviewEndpoint: # Mock voice clone profile with empty voice_id (clone not finished) mock_profile = MagicMock() + mock_profile.user_id = "user-1" mock_profile.voice_id = "" mock_clone_repo = MagicMock() mock_clone_repo.get.return_value = mock_profile @@ -405,3 +407,35 @@ class TestTTSPreviewEndpoint: voice_id="longxiaoxia_v3", speed=1.0, ) + + def test_preview_clone_voice_wrong_user_returns_403(self): + """Accessing another user's clone profile returns 403.""" + from fastapi import FastAPI + + app = FastAPI() + from app.api.routes.tts import router + + app.include_router(router, prefix="/tts") + + from app.auth import get_current_user + from app.dependencies import get_voice_clone_profile_repository + + fake_user = MagicMock() + fake_user.user.id = "user-1" + app.dependency_overrides[get_current_user] = lambda: fake_user + + # Mock profile belonging to a different user + mock_profile = MagicMock() + mock_profile.user_id = "user-2" + mock_profile.voice_id = "cosyvoice_voice_xyz" + mock_clone_repo = MagicMock() + mock_clone_repo.get.return_value = mock_profile + app.dependency_overrides[get_voice_clone_profile_repository] = lambda: mock_clone_repo + + client = self._make_client(app) + resp = client.post( + "/tts/preview", + json={"text": "越权测试", "voice_id": "other-user-profile-uuid"}, + ) + assert resp.status_code == 403 + assert "无权访问该音色" in resp.json()["detail"]