From 7ec6fa4d1f77ce6e9af42ad2f9896bb0902a3b8a Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:28:12 +0530 Subject: [PATCH] feat: enforce API access for integration routes --- surfsense_backend/app/podcasts/api/routes.py | 44 +++++++++++------ surfsense_backend/app/routes/export_routes.py | 8 +-- .../app/routes/gateway_webhook_routes.py | 49 ++++++++++++------- .../routes/gateway_whatsapp_baileys_routes.py | 11 +++-- .../app/routes/image_generation_routes.py | 23 +++++---- surfsense_backend/app/routes/logs_routes.py | 33 ++++++++----- .../app/routes/sandbox_routes.py | 8 +-- .../app/routes/video_presentations_routes.py | 23 +++++---- 8 files changed, 125 insertions(+), 74 deletions(-) diff --git a/surfsense_backend/app/podcasts/api/routes.py b/surfsense_backend/app/podcasts/api/routes.py index cfcb2ede9..2f4c8e4d9 100644 --- a/surfsense_backend/app/podcasts/api/routes.py +++ b/surfsense_backend/app/podcasts/api/routes.py @@ -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") diff --git a/surfsense_backend/app/routes/export_routes.py b/surfsense_backend/app/routes/export_routes.py index 4f2b545a3..8e419157f 100644 --- a/surfsense_backend/app/routes/export_routes.py +++ b/surfsense_backend/app/routes/export_routes.py @@ -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", diff --git a/surfsense_backend/app/routes/gateway_webhook_routes.py b/surfsense_backend/app/routes/gateway_webhook_routes.py index 9b4af4b83..0d05f4baf 100644 --- a/surfsense_backend/app/routes/gateway_webhook_routes.py +++ b/surfsense_backend/app/routes/gateway_webhook_routes.py @@ -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") diff --git a/surfsense_backend/app/routes/gateway_whatsapp_baileys_routes.py b/surfsense_backend/app/routes/gateway_whatsapp_baileys_routes.py index 1fcf5c438..370b1cc8d 100644 --- a/surfsense_backend/app/routes/gateway_whatsapp_baileys_routes.py +++ b/surfsense_backend/app/routes/gateway_whatsapp_baileys_routes.py @@ -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: diff --git a/surfsense_backend/app/routes/image_generation_routes.py b/surfsense_backend/app/routes/image_generation_routes.py index cc3e51ed5..0d9841c4c 100644 --- a/surfsense_backend/app/routes/image_generation_routes.py +++ b/surfsense_backend/app/routes/image_generation_routes.py @@ -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", diff --git a/surfsense_backend/app/routes/logs_routes.py b/surfsense_backend/app/routes/logs_routes.py index b82e02077..16400ef0b 100644 --- a/surfsense_backend/app/routes/logs_routes.py +++ b/surfsense_backend/app/routes/logs_routes.py @@ -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", diff --git a/surfsense_backend/app/routes/sandbox_routes.py b/surfsense_backend/app/routes/sandbox_routes.py index fefe51997..e7974b993 100644 --- a/surfsense_backend/app/routes/sandbox_routes.py +++ b/surfsense_backend/app/routes/sandbox_routes.py @@ -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", diff --git a/surfsense_backend/app/routes/video_presentations_routes.py b/surfsense_backend/app/routes/video_presentations_routes.py index ed694b9bf..189a050e4 100644 --- a/surfsense_backend/app/routes/video_presentations_routes.py +++ b/surfsense_backend/app/routes/video_presentations_routes.py @@ -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",