mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-24 21:38:09 +02:00
feat: enforce API access for integration routes
This commit is contained in:
parent
70a0828b95
commit
7ec6fa4d1f
8 changed files with 125 additions and 74 deletions
|
|
@ -18,6 +18,7 @@ from fastapi.responses import StreamingResponse
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.config import config as app_config
|
||||
from app.db import (
|
||||
Permission,
|
||||
|
|
@ -42,7 +43,7 @@ from app.podcasts.voices import (
|
|||
provider_from_service,
|
||||
render_voice_preview,
|
||||
)
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
from .schemas import (
|
||||
|
|
@ -63,8 +64,9 @@ async def list_podcasts(
|
|||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
if skip < 0 or limit < 1:
|
||||
raise HTTPException(status_code=400, detail="Invalid pagination parameters")
|
||||
|
||||
|
|
@ -132,8 +134,9 @@ async def list_languages():
|
|||
@router.get("/podcasts/voices/{voice_id}/preview")
|
||||
async def preview_voice(
|
||||
voice_id: str,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""A short audio sample of a voice, so users pick by sound."""
|
||||
if not app_config.TTS_SERVICE:
|
||||
raise HTTPException(status_code=503, detail="No TTS provider configured")
|
||||
|
|
@ -156,8 +159,9 @@ async def preview_voice(
|
|||
async def create_podcast(
|
||||
body: CreatePodcastRequest,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
await _require(session, user, body.search_space_id, Permission.PODCASTS_CREATE)
|
||||
|
||||
service = PodcastService(session)
|
||||
|
|
@ -185,8 +189,9 @@ async def create_podcast(
|
|||
async def get_podcast(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_READ)
|
||||
return PodcastDetail.of(podcast)
|
||||
|
||||
|
|
@ -196,8 +201,9 @@ async def update_spec(
|
|||
podcast_id: int,
|
||||
body: UpdateSpecRequest,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_UPDATE)
|
||||
async with _lifecycle_errors():
|
||||
await PodcastService(session).update_spec(
|
||||
|
|
@ -211,8 +217,9 @@ async def update_spec(
|
|||
async def approve_brief(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Approve the brief and start drafting the transcript."""
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_UPDATE)
|
||||
async with _lifecycle_errors():
|
||||
|
|
@ -228,8 +235,9 @@ async def approve_brief(
|
|||
async def regenerate_transcript(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Reopen the brief gate for a fresh take; drafting waits for re-approval."""
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_UPDATE)
|
||||
async with _lifecycle_errors():
|
||||
|
|
@ -242,8 +250,9 @@ async def regenerate_transcript(
|
|||
async def revert_regeneration(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Back out of a regeneration and return to the finished episode."""
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_UPDATE)
|
||||
async with _lifecycle_errors():
|
||||
|
|
@ -256,8 +265,9 @@ async def revert_regeneration(
|
|||
async def cancel_podcast(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_UPDATE)
|
||||
async with _lifecycle_errors():
|
||||
await PodcastService(session).cancel(podcast)
|
||||
|
|
@ -269,8 +279,9 @@ async def cancel_podcast(
|
|||
async def delete_podcast(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_DELETE)
|
||||
await purge_audio(podcast)
|
||||
await session.delete(podcast)
|
||||
|
|
@ -282,8 +293,9 @@ async def delete_podcast(
|
|||
async def stream_podcast(
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
podcast = await _load(session, user, podcast_id, Permission.PODCASTS_READ)
|
||||
|
||||
if podcast.storage_key:
|
||||
|
|
@ -323,13 +335,14 @@ async def stream_podcast(
|
|||
|
||||
async def _require(
|
||||
session: AsyncSession,
|
||||
user: User,
|
||||
auth: AuthContext,
|
||||
search_space_id: int,
|
||||
permission: Permission,
|
||||
) -> None:
|
||||
user = auth.user
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
permission.value,
|
||||
"You don't have permission for podcasts in this search space",
|
||||
|
|
@ -338,10 +351,11 @@ async def _require(
|
|||
|
||||
async def _load(
|
||||
session: AsyncSession,
|
||||
user: User,
|
||||
auth: AuthContext,
|
||||
podcast_id: int,
|
||||
permission: Permission,
|
||||
) -> Podcast:
|
||||
user = auth.user
|
||||
podcast = await PodcastRepository(session).get(podcast_id)
|
||||
if podcast is None:
|
||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
|||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Permission, User, get_async_session
|
||||
from app.services.export_service import build_export_zip
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -24,12 +25,13 @@ async def export_knowledge_base(
|
|||
None, description="Export only this folder's subtree"
|
||||
),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Export documents as a ZIP of markdown files preserving folder structure."""
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
Permission.DOCUMENTS_READ.value,
|
||||
"You don't have permission to export documents in this search space",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from sqlalchemy import or_, select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.responses import JSONResponse, RedirectResponse, Response
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.config import config
|
||||
from app.db import (
|
||||
ExternalChatAccount,
|
||||
|
|
@ -51,7 +52,7 @@ from app.observability.metrics import (
|
|||
record_gateway_inbox_write,
|
||||
record_gateway_webhook_parse_error,
|
||||
)
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.oauth_security import OAuthStateManager, TokenEncryption
|
||||
from app.utils.rbac import check_search_space_access
|
||||
|
||||
|
|
@ -250,14 +251,15 @@ def _telegram_message(payload: dict[str, Any]) -> dict[str, Any] | None:
|
|||
@router.get("/slack/install")
|
||||
async def install_slack_gateway(
|
||||
search_space_id: int,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, str]:
|
||||
user = auth.user
|
||||
if not _slack_gateway_enabled():
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Slack gateway OAuth is not configured"
|
||||
)
|
||||
await check_search_space_access(session, user, search_space_id)
|
||||
await check_search_space_access(session, auth, search_space_id)
|
||||
state = _get_state_manager().generate_secure_state(search_space_id, user.id)
|
||||
auth_params = {
|
||||
"client_id": config.GATEWAY_SLACK_CLIENT_ID,
|
||||
|
|
@ -409,14 +411,15 @@ async def slack_gateway_callback(
|
|||
@router.get("/discord/install")
|
||||
async def install_discord_gateway(
|
||||
search_space_id: int,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, str]:
|
||||
user = auth.user
|
||||
if not _discord_gateway_enabled():
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Discord gateway OAuth is not configured"
|
||||
)
|
||||
await check_search_space_access(session, user, search_space_id)
|
||||
await check_search_space_access(session, auth, search_space_id)
|
||||
state = _get_state_manager().generate_secure_state(search_space_id, user.id)
|
||||
auth_params = {
|
||||
"client_id": config.DISCORD_CLIENT_ID,
|
||||
|
|
@ -712,10 +715,11 @@ async def telegram_webhook(
|
|||
@router.post("/bindings/start", response_model=StartBindingResponse)
|
||||
async def start_binding(
|
||||
body: StartBindingRequest,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> StartBindingResponse:
|
||||
await check_search_space_access(session, user, body.search_space_id)
|
||||
user = auth.user
|
||||
await check_search_space_access(session, auth, body.search_space_id)
|
||||
code = generate_pairing_code()
|
||||
if body.platform == ExternalChatPlatform.TELEGRAM:
|
||||
if not _telegram_gateway_enabled():
|
||||
|
|
@ -774,9 +778,10 @@ async def start_binding(
|
|||
|
||||
@router.get("/bindings")
|
||||
async def list_bindings(
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> list[dict[str, Any]]:
|
||||
user = auth.user
|
||||
result = await session.execute(
|
||||
select(ExternalChatBinding, ExternalChatAccount)
|
||||
.join(
|
||||
|
|
@ -803,9 +808,10 @@ async def list_bindings(
|
|||
@router.get("/connections")
|
||||
async def list_connections(
|
||||
platform: ExternalChatPlatform | None = None,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> list[dict[str, Any]]:
|
||||
user = auth.user
|
||||
active_whatsapp_mode = _active_whatsapp_account_mode()
|
||||
if platform == ExternalChatPlatform.WHATSAPP and active_whatsapp_mode is None:
|
||||
return []
|
||||
|
|
@ -946,9 +952,10 @@ async def list_connections(
|
|||
|
||||
@router.get("/platforms")
|
||||
async def list_platforms(
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> list[dict[str, Any]]:
|
||||
user = auth.user
|
||||
result = await session.execute(
|
||||
select(ExternalChatAccount).where(
|
||||
(ExternalChatAccount.owner_user_id == user.id)
|
||||
|
|
@ -970,8 +977,9 @@ async def list_platforms(
|
|||
|
||||
@config_router.get("/config")
|
||||
async def get_gateway_config(
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> dict[str, bool | str]:
|
||||
user = auth.user
|
||||
if not config.GATEWAY_ENABLED:
|
||||
return {
|
||||
"enabled": False,
|
||||
|
|
@ -993,9 +1001,10 @@ async def get_gateway_config(
|
|||
async def update_binding_search_space(
|
||||
binding_id: int,
|
||||
body: UpdateBindingSearchSpaceRequest,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, bool]:
|
||||
user = auth.user
|
||||
binding = await session.get(ExternalChatBinding, binding_id)
|
||||
if binding is None or binding.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
|
|
@ -1010,7 +1019,7 @@ async def update_binding_search_space(
|
|||
if account is None or _is_inactive_whatsapp_account(account):
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
|
||||
await check_search_space_access(session, user, body.search_space_id)
|
||||
await check_search_space_access(session, auth, body.search_space_id)
|
||||
if binding.search_space_id != body.search_space_id:
|
||||
binding.search_space_id = body.search_space_id
|
||||
binding.new_chat_thread_id = None
|
||||
|
|
@ -1023,9 +1032,10 @@ async def update_binding_search_space(
|
|||
async def update_gateway_account_search_space(
|
||||
account_id: int,
|
||||
body: UpdateAccountSearchSpaceRequest,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, bool]:
|
||||
user = auth.user
|
||||
account = await session.get(ExternalChatAccount, account_id)
|
||||
if (
|
||||
account is None
|
||||
|
|
@ -1036,7 +1046,7 @@ async def update_gateway_account_search_space(
|
|||
):
|
||||
raise HTTPException(status_code=404, detail="Gateway account not found")
|
||||
|
||||
await check_search_space_access(session, user, body.search_space_id)
|
||||
await check_search_space_access(session, auth, body.search_space_id)
|
||||
account.owner_search_space_id = body.search_space_id
|
||||
account.updated_at = datetime.now(UTC)
|
||||
|
||||
|
|
@ -1061,9 +1071,10 @@ async def update_gateway_account_search_space(
|
|||
@router.delete("/bindings/{binding_id}")
|
||||
async def delete_binding(
|
||||
binding_id: int,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, bool]:
|
||||
user = auth.user
|
||||
binding = await session.get(ExternalChatBinding, binding_id)
|
||||
if binding is None or binding.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
|
|
@ -1078,9 +1089,10 @@ async def delete_binding(
|
|||
@router.delete("/accounts/{account_id}")
|
||||
async def delete_gateway_account(
|
||||
account_id: int,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, bool]:
|
||||
user = auth.user
|
||||
account = await session.get(ExternalChatAccount, account_id)
|
||||
if (
|
||||
account is None
|
||||
|
|
@ -1114,9 +1126,10 @@ async def delete_gateway_account(
|
|||
@router.post("/bindings/{binding_id}/resume")
|
||||
async def resume_external_chat_binding(
|
||||
binding_id: int,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, bool]:
|
||||
user = auth.user
|
||||
binding = await session.get(ExternalChatBinding, binding_id)
|
||||
if binding is None or binding.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from pydantic import BaseModel
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.config import config
|
||||
from app.db import (
|
||||
ExternalChatAccount,
|
||||
|
|
@ -20,7 +21,7 @@ from app.db import (
|
|||
get_async_session,
|
||||
)
|
||||
from app.gateway.whatsapp.adapter_baileys import WhatsAppBaileysAdapter
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_search_space_access
|
||||
|
||||
router = APIRouter(prefix="/gateway/whatsapp/baileys", tags=["gateway"])
|
||||
|
|
@ -60,11 +61,12 @@ async def _get_user_whatsapp_account(
|
|||
@router.post("/pair")
|
||||
async def request_pairing_code(
|
||||
body: BaileysPairRequest,
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> dict[str, Any]:
|
||||
user = auth.user
|
||||
_ensure_baileys_enabled()
|
||||
await check_search_space_access(session, user, body.search_space_id)
|
||||
await check_search_space_access(session, auth, body.search_space_id)
|
||||
adapter = WhatsAppBaileysAdapter()
|
||||
try:
|
||||
pairing = await adapter.request_pairing_code(phone_number=body.phone_number)
|
||||
|
|
@ -97,8 +99,9 @@ async def request_pairing_code(
|
|||
|
||||
@router.get("/health")
|
||||
async def bridge_health(
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> dict[str, Any]:
|
||||
user = auth.user
|
||||
_ensure_baileys_enabled()
|
||||
adapter = WhatsAppBaileysAdapter()
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.config import config
|
||||
from app.db import (
|
||||
ImageGeneration,
|
||||
|
|
@ -46,7 +47,7 @@ from app.services.image_gen_router_service import (
|
|||
)
|
||||
from app.services.model_capabilities import has_capability
|
||||
from app.services.model_resolver import to_litellm
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
from app.utils.signed_image_urls import verify_image_token
|
||||
|
||||
|
|
@ -231,8 +232,9 @@ async def _execute_image_generation(
|
|||
async def create_image_generation(
|
||||
data: ImageGenerationCreate,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Create and execute an image generation request.
|
||||
|
||||
Premium configs are gated by the user's shared premium credit pool.
|
||||
|
|
@ -256,7 +258,7 @@ async def create_image_generation(
|
|||
try:
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
data.search_space_id,
|
||||
Permission.IMAGE_GENERATIONS_CREATE.value,
|
||||
"You don't have permission to create image generations in this search space",
|
||||
|
|
@ -351,8 +353,9 @@ async def list_image_generations(
|
|||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""List image generations."""
|
||||
if skip < 0 or limit < 1:
|
||||
raise HTTPException(status_code=400, detail="Invalid pagination parameters")
|
||||
|
|
@ -363,7 +366,7 @@ async def list_image_generations(
|
|||
if search_space_id is not None:
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
Permission.IMAGE_GENERATIONS_READ.value,
|
||||
"You don't have permission to read image generations in this search space",
|
||||
|
|
@ -403,8 +406,9 @@ async def list_image_generations(
|
|||
async def get_image_generation(
|
||||
image_gen_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Get a specific image generation by ID."""
|
||||
try:
|
||||
result = await session.execute(
|
||||
|
|
@ -416,7 +420,7 @@ async def get_image_generation(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
image_gen.search_space_id,
|
||||
Permission.IMAGE_GENERATIONS_READ.value,
|
||||
"You don't have permission to read image generations in this search space",
|
||||
|
|
@ -435,8 +439,9 @@ async def get_image_generation(
|
|||
async def delete_image_generation(
|
||||
image_gen_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Delete an image generation record."""
|
||||
try:
|
||||
result = await session.execute(
|
||||
|
|
@ -448,7 +453,7 @@ async def delete_image_generation(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
db_image_gen.search_space_id,
|
||||
Permission.IMAGE_GENERATIONS_DELETE.value,
|
||||
"You don't have permission to delete image generations in this search space",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from sqlalchemy import and_, desc
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
Log,
|
||||
LogLevel,
|
||||
|
|
@ -16,7 +17,7 @@ from app.db import (
|
|||
get_async_session,
|
||||
)
|
||||
from app.schemas import LogCreate, LogRead, LogUpdate
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
router = APIRouter()
|
||||
|
|
@ -26,8 +27,9 @@ router = APIRouter()
|
|||
async def create_log(
|
||||
log: LogCreate,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Create a new log entry.
|
||||
Note: This is typically called internally. Requires LOGS_READ permission (since logs are usually system-generated).
|
||||
|
|
@ -36,7 +38,7 @@ async def create_log(
|
|||
# Check if the user has access to the search space
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
log.search_space_id,
|
||||
Permission.LOGS_READ.value,
|
||||
"You don't have permission to access logs in this search space",
|
||||
|
|
@ -67,8 +69,9 @@ async def read_logs(
|
|||
start_date: datetime | None = None,
|
||||
end_date: datetime | None = None,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get logs with optional filtering.
|
||||
Requires LOGS_READ permission for the search space(s).
|
||||
|
|
@ -81,7 +84,7 @@ async def read_logs(
|
|||
# Check permission for specific search space
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
Permission.LOGS_READ.value,
|
||||
"You don't have permission to read logs in this search space",
|
||||
|
|
@ -136,8 +139,9 @@ async def read_logs(
|
|||
async def read_log(
|
||||
log_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific log by ID.
|
||||
Requires LOGS_READ permission for the search space.
|
||||
|
|
@ -152,7 +156,7 @@ async def read_log(
|
|||
# Check permission for the search space
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
log.search_space_id,
|
||||
Permission.LOGS_READ.value,
|
||||
"You don't have permission to read logs in this search space",
|
||||
|
|
@ -172,8 +176,9 @@ async def update_log(
|
|||
log_id: int,
|
||||
log_update: LogUpdate,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a log entry.
|
||||
Requires LOGS_READ permission (logs are typically updated by system).
|
||||
|
|
@ -188,7 +193,7 @@ async def update_log(
|
|||
# Check permission for the search space
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
db_log.search_space_id,
|
||||
Permission.LOGS_READ.value,
|
||||
"You don't have permission to access logs in this search space",
|
||||
|
|
@ -215,8 +220,9 @@ async def update_log(
|
|||
async def delete_log(
|
||||
log_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a log entry.
|
||||
Requires LOGS_DELETE permission for the search space.
|
||||
|
|
@ -231,7 +237,7 @@ async def delete_log(
|
|||
# Check permission for the search space
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
db_log.search_space_id,
|
||||
Permission.LOGS_DELETE.value,
|
||||
"You don't have permission to delete logs in this search space",
|
||||
|
|
@ -254,8 +260,9 @@ async def get_logs_summary(
|
|||
search_space_id: int,
|
||||
hours: int = 24,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a summary of logs for a search space in the last X hours.
|
||||
Requires LOGS_READ permission for the search space.
|
||||
|
|
@ -264,7 +271,7 @@ async def get_logs_summary(
|
|||
# Check permission
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
Permission.LOGS_READ.value,
|
||||
"You don't have permission to read logs in this search space",
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ from fastapi.responses import Response
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import NewChatThread, Permission, User, get_async_session
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -47,8 +48,9 @@ async def download_sandbox_file(
|
|||
thread_id: int,
|
||||
path: str = Query(..., description="Absolute path of the file inside the sandbox"),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Download a file from the Daytona sandbox associated with a chat thread."""
|
||||
|
||||
from app.agents.chat.multi_agent_chat.shared.middleware.filesystem.sandbox import (
|
||||
|
|
@ -68,7 +70,7 @@ async def download_sandbox_file(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
thread.search_space_id,
|
||||
Permission.CHATS_READ.value,
|
||||
"You don't have permission to access files in this thread",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from sqlalchemy import select
|
|||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
Permission,
|
||||
SearchSpace,
|
||||
|
|
@ -25,7 +26,7 @@ from app.db import (
|
|||
get_async_session,
|
||||
)
|
||||
from app.schemas import VideoPresentationRead
|
||||
from app.users import current_active_user
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
router = APIRouter()
|
||||
|
|
@ -37,8 +38,9 @@ async def read_video_presentations(
|
|||
limit: int = 100,
|
||||
search_space_id: int | None = None,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List video presentations the user has access to.
|
||||
Requires VIDEO_PRESENTATIONS_READ permission for the search space(s).
|
||||
|
|
@ -49,7 +51,7 @@ async def read_video_presentations(
|
|||
if search_space_id is not None:
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
search_space_id,
|
||||
Permission.VIDEO_PRESENTATIONS_READ.value,
|
||||
"You don't have permission to read video presentations in this search space",
|
||||
|
|
@ -89,8 +91,9 @@ async def read_video_presentations(
|
|||
async def read_video_presentation(
|
||||
video_presentation_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific video presentation by ID.
|
||||
Requires authentication with VIDEO_PRESENTATIONS_READ permission.
|
||||
|
|
@ -112,7 +115,7 @@ async def read_video_presentation(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
video_pres.search_space_id,
|
||||
Permission.VIDEO_PRESENTATIONS_READ.value,
|
||||
"You don't have permission to read video presentations in this search space",
|
||||
|
|
@ -132,8 +135,9 @@ async def read_video_presentation(
|
|||
async def delete_video_presentation(
|
||||
video_presentation_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a video presentation.
|
||||
Requires VIDEO_PRESENTATIONS_DELETE permission for the search space.
|
||||
|
|
@ -151,7 +155,7 @@ async def delete_video_presentation(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
db_video_pres.search_space_id,
|
||||
Permission.VIDEO_PRESENTATIONS_DELETE.value,
|
||||
"You don't have permission to delete video presentations in this search space",
|
||||
|
|
@ -175,8 +179,9 @@ async def stream_slide_audio(
|
|||
video_presentation_id: int,
|
||||
slide_number: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Stream the audio file for a specific slide in a video presentation.
|
||||
The slide_number is 1-based. Audio path is read from the slides JSONB.
|
||||
|
|
@ -194,7 +199,7 @@ async def stream_slide_audio(
|
|||
|
||||
await check_permission(
|
||||
session,
|
||||
user,
|
||||
auth,
|
||||
video_pres.search_space_id,
|
||||
Permission.VIDEO_PRESENTATIONS_READ.value,
|
||||
"You don't have permission to access video presentations in this search space",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue