diff --git a/app/deps.py b/app/deps.py index a2b2f3c..48d68a8 100644 --- a/app/deps.py +++ b/app/deps.py @@ -237,7 +237,11 @@ def require_active_plugin(plugin_name: str): already have their own auth dependencies (require_permission, etc.). This check only verifies plugin activation status. """ - async def _check() -> None: + async def _check(request: Request) -> None: + # Skip plugin check for WebSocket connections — WS auth is handled + # inside the endpoint itself via session cookie verification. + if request.headers.get("upgrade", "").lower() == "websocket": + return from app.core.permission_registry import get_permission_registry try: registry = get_permission_registry() diff --git a/app/main.py b/app/main.py index eff748a..544698b 100644 --- a/app/main.py +++ b/app/main.py @@ -439,27 +439,17 @@ def create_app() -> FastAPI: # Skip WebSocket routes — the wrapper breaks WS parameter # resolution and returns JSONResponse instead of WS close. from starlette.routing import WebSocketRoute - http_routes = [] - ws_routes = [] for route in router.routes: if isinstance(route, WebSocketRoute): - ws_routes.append(route) - else: - if hasattr(route, 'endpoint'): - route.endpoint = wrap_plugin_route(route.endpoint) - http_routes.append(route) - # Register HTTP routes with active-plugin check - router.routes = http_routes + continue + if hasattr(route, 'endpoint'): + route.endpoint = wrap_plugin_route(route.endpoint) + # Add active-plugin check as a router-level dependency + # (require_active_plugin skips WebSocket requests internally) app.include_router( router, dependencies=[Depends(require_active_plugin(plugin_name))], ) - # Register WebSocket routes WITHOUT active-plugin check - # (WebSocket auth is handled inside the endpoint itself) - if ws_routes: - ws_router = APIRouter() - ws_router.routes = ws_routes - app.include_router(ws_router) except Exception as exc: logger.error(f"Failed to register route {route_def.module}.{route_def.router_attr}: {exc}") break