From ea633aedc1cf4bc1401655bae57250f9074b1f8f Mon Sep 17 00:00:00 2001 From: Daulet Amirkhanov Date: Wed, 20 Aug 2025 18:51:31 +0100 Subject: [PATCH] refactor: replace user authentication method with conditional authentication across multiple routers --- cognee/api/v1/add/routers/get_add_router.py | 8 +--- .../v1/cognify/routers/get_cognify_router.py | 8 +--- .../datasets/routers/get_datasets_router.py | 18 +++---- .../v1/delete/routers/get_delete_router.py | 4 +- .../routers/get_permissions_router.py | 12 ++--- .../responses/routers/get_responses_router.py | 4 +- .../v1/search/routers/get_search_router.py | 14 ++---- .../settings/routers/get_settings_router.py | 6 +-- .../v1/users/routers/get_visualize_router.py | 4 +- cognee/modules/users/methods/__init__.py | 3 +- .../users/methods/get_authenticated_user.py | 48 ------------------- .../get_conditional_authenticated_user.py | 35 ++++++++++++++ .../get_optional_authenticated_user.py | 8 ---- 13 files changed, 67 insertions(+), 105 deletions(-) delete mode 100644 cognee/modules/users/methods/get_authenticated_user.py create mode 100644 cognee/modules/users/methods/get_conditional_authenticated_user.py delete mode 100644 cognee/modules/users/methods/get_optional_authenticated_user.py diff --git a/cognee/api/v1/add/routers/get_add_router.py b/cognee/api/v1/add/routers/get_add_router.py index 056345c18..11a8c0cf4 100644 --- a/cognee/api/v1/add/routers/get_add_router.py +++ b/cognee/api/v1/add/routers/get_add_router.py @@ -9,7 +9,7 @@ from fastapi import Form, File, UploadFile, Depends from typing import List, Optional, Union, Literal from cognee.modules.users.models import User -from cognee.modules.users.methods import get_optional_authenticated_user, get_default_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.shared.utils import send_telemetry from cognee.modules.pipelines.models import PipelineRunErrored from cognee.shared.logging_utils import get_logger @@ -25,7 +25,7 @@ def get_add_router() -> APIRouter: data: List[UploadFile] = File(default=None), datasetName: Optional[str] = Form(default=None), datasetId: Union[UUID, Literal[""], None] = Form(default=None, examples=[""]), - user: Optional[User] = Depends(get_optional_authenticated_user), + user: User = Depends(get_conditional_authenticated_user), ): """ Add data to a dataset for processing and knowledge graph construction. @@ -62,10 +62,6 @@ def get_add_router() -> APIRouter: - The ALLOW_HTTP_REQUESTS environment variable controls URL processing - datasetId value can only be the UUID of an already existing dataset """ - # Use default user for anonymous requests - if user is None: - user = await get_default_user() - send_telemetry( "Add API Endpoint Invoked", user.id, diff --git a/cognee/api/v1/cognify/routers/get_cognify_router.py b/cognee/api/v1/cognify/routers/get_cognify_router.py index 68d756f0d..6adcab8e6 100644 --- a/cognee/api/v1/cognify/routers/get_cognify_router.py +++ b/cognee/api/v1/cognify/routers/get_cognify_router.py @@ -10,7 +10,7 @@ from starlette.status import WS_1000_NORMAL_CLOSURE, WS_1008_POLICY_VIOLATION from cognee.api.DTO import InDTO from cognee.modules.pipelines.methods import get_pipeline_run from cognee.modules.users.models import User -from cognee.modules.users.methods import get_optional_authenticated_user, get_default_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.modules.users.get_user_db import get_user_db_context from cognee.modules.graph.methods import get_formatted_graph_data from cognee.modules.users.get_user_manager import get_user_manager_context @@ -46,7 +46,7 @@ def get_cognify_router() -> APIRouter: router = APIRouter() @router.post("", response_model=dict) - async def cognify(payload: CognifyPayloadDTO, user: Optional[User] = Depends(get_optional_authenticated_user)): + async def cognify(payload: CognifyPayloadDTO, user: User = Depends(get_conditional_authenticated_user)): """ Transform datasets into structured knowledge graphs through cognitive processing. @@ -92,10 +92,6 @@ def get_cognify_router() -> APIRouter: ## Next Steps After successful processing, use the search endpoints to query the generated knowledge graph for insights, relationships, and semantic search. """ - # Use default user for anonymous requests - if user is None: - user = await get_default_user() - send_telemetry( "Cognify API Endpoint Invoked", user.id, diff --git a/cognee/api/v1/datasets/routers/get_datasets_router.py b/cognee/api/v1/datasets/routers/get_datasets_router.py index 8052e3864..985aac28d 100644 --- a/cognee/api/v1/datasets/routers/get_datasets_router.py +++ b/cognee/api/v1/datasets/routers/get_datasets_router.py @@ -15,7 +15,7 @@ from cognee.modules.data.methods import create_dataset, get_datasets_by_name from cognee.shared.logging_utils import get_logger from cognee.api.v1.exceptions import DataNotFoundError, DatasetNotFoundError from cognee.modules.users.models import User -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.modules.users.permissions.methods import ( get_all_user_permission_datasets, give_permission_on_dataset, @@ -74,7 +74,7 @@ def get_datasets_router() -> APIRouter: router = APIRouter() @router.get("", response_model=list[DatasetDTO]) - async def get_datasets(user: User = Depends(get_authenticated_user)): + async def get_datasets(user: User = Depends(get_conditional_authenticated_user)): """ Get all datasets accessible to the authenticated user. @@ -114,7 +114,7 @@ def get_datasets_router() -> APIRouter: @router.post("", response_model=DatasetDTO) async def create_new_dataset( - dataset_data: DatasetCreationPayload, user: User = Depends(get_authenticated_user) + dataset_data: DatasetCreationPayload, user: User = Depends(get_conditional_authenticated_user) ): """ Create a new dataset or return existing dataset with the same name. @@ -175,7 +175,7 @@ def get_datasets_router() -> APIRouter: @router.delete( "/{dataset_id}", response_model=None, responses={404: {"model": ErrorResponseDTO}} ) - async def delete_dataset(dataset_id: UUID, user: User = Depends(get_authenticated_user)): + async def delete_dataset(dataset_id: UUID, user: User = Depends(get_conditional_authenticated_user)): """ Delete a dataset by its ID. @@ -216,7 +216,7 @@ def get_datasets_router() -> APIRouter: responses={404: {"model": ErrorResponseDTO}}, ) async def delete_data( - dataset_id: UUID, data_id: UUID, user: User = Depends(get_authenticated_user) + dataset_id: UUID, data_id: UUID, user: User = Depends(get_conditional_authenticated_user) ): """ Delete a specific data item from a dataset. @@ -263,7 +263,7 @@ def get_datasets_router() -> APIRouter: await delete_data(data) @router.get("/{dataset_id}/graph", response_model=GraphDTO) - async def get_dataset_graph(dataset_id: UUID, user: User = Depends(get_authenticated_user)): + async def get_dataset_graph(dataset_id: UUID, user: User = Depends(get_conditional_authenticated_user)): """ Get the knowledge graph visualization for a dataset. @@ -293,7 +293,7 @@ def get_datasets_router() -> APIRouter: response_model=list[DataDTO], responses={404: {"model": ErrorResponseDTO}}, ) - async def get_dataset_data(dataset_id: UUID, user: User = Depends(get_authenticated_user)): + async def get_dataset_data(dataset_id: UUID, user: User = Depends(get_conditional_authenticated_user)): """ Get all data items in a dataset. @@ -348,7 +348,7 @@ def get_datasets_router() -> APIRouter: @router.get("/status", response_model=dict[str, PipelineRunStatus]) async def get_dataset_status( datasets: Annotated[List[UUID], Query(alias="dataset")] = [], - user: User = Depends(get_authenticated_user), + user: User = Depends(get_conditional_authenticated_user), ): """ Get the processing status of datasets. @@ -395,7 +395,7 @@ def get_datasets_router() -> APIRouter: @router.get("/{dataset_id}/data/{data_id}/raw", response_class=FileResponse) async def get_raw_data( - dataset_id: UUID, data_id: UUID, user: User = Depends(get_authenticated_user) + dataset_id: UUID, data_id: UUID, user: User = Depends(get_conditional_authenticated_user) ): """ Download the raw data file for a specific data item. diff --git a/cognee/api/v1/delete/routers/get_delete_router.py b/cognee/api/v1/delete/routers/get_delete_router.py index 9e6aa5799..173206b82 100644 --- a/cognee/api/v1/delete/routers/get_delete_router.py +++ b/cognee/api/v1/delete/routers/get_delete_router.py @@ -4,7 +4,7 @@ from fastapi import APIRouter from uuid import UUID from cognee.shared.logging_utils import get_logger from cognee.modules.users.models import User -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.shared.utils import send_telemetry logger = get_logger() @@ -18,7 +18,7 @@ def get_delete_router() -> APIRouter: data_id: UUID, dataset_id: UUID, mode: str = "soft", - user: User = Depends(get_authenticated_user), + user: User = Depends(get_conditional_authenticated_user), ): """Delete data by its ID from the specified dataset. diff --git a/cognee/api/v1/permissions/routers/get_permissions_router.py b/cognee/api/v1/permissions/routers/get_permissions_router.py index 89603ac46..7f34334e5 100644 --- a/cognee/api/v1/permissions/routers/get_permissions_router.py +++ b/cognee/api/v1/permissions/routers/get_permissions_router.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends from fastapi.responses import JSONResponse from cognee.modules.users.models import User -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.shared.utils import send_telemetry @@ -17,7 +17,7 @@ def get_permissions_router() -> APIRouter: permission_name: str, dataset_ids: List[UUID], principal_id: UUID, - user: User = Depends(get_authenticated_user), + user: User = Depends(get_conditional_authenticated_user), ): """ Grant permission on datasets to a principal (user or role). @@ -65,7 +65,7 @@ def get_permissions_router() -> APIRouter: ) @permissions_router.post("/roles") - async def create_role(role_name: str, user: User = Depends(get_authenticated_user)): + async def create_role(role_name: str, user: User = Depends(get_conditional_authenticated_user)): """ Create a new role. @@ -100,7 +100,7 @@ def get_permissions_router() -> APIRouter: @permissions_router.post("/users/{user_id}/roles") async def add_user_to_role( - user_id: UUID, role_id: UUID, user: User = Depends(get_authenticated_user) + user_id: UUID, role_id: UUID, user: User = Depends(get_conditional_authenticated_user) ): """ Add a user to a role. @@ -142,7 +142,7 @@ def get_permissions_router() -> APIRouter: @permissions_router.post("/users/{user_id}/tenants") async def add_user_to_tenant( - user_id: UUID, tenant_id: UUID, user: User = Depends(get_authenticated_user) + user_id: UUID, tenant_id: UUID, user: User = Depends(get_conditional_authenticated_user) ): """ Add a user to a tenant. @@ -183,7 +183,7 @@ def get_permissions_router() -> APIRouter: return JSONResponse(status_code=200, content={"message": "User added to tenant"}) @permissions_router.post("/tenants") - async def create_tenant(tenant_name: str, user: User = Depends(get_authenticated_user)): + async def create_tenant(tenant_name: str, user: User = Depends(get_conditional_authenticated_user)): """ Create a new tenant. diff --git a/cognee/api/v1/responses/routers/get_responses_router.py b/cognee/api/v1/responses/routers/get_responses_router.py index cf1f003c0..bba7e2410 100644 --- a/cognee/api/v1/responses/routers/get_responses_router.py +++ b/cognee/api/v1/responses/routers/get_responses_router.py @@ -21,7 +21,7 @@ from cognee.infrastructure.llm.config import ( get_llm_config, ) from cognee.modules.users.models import User -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user def get_responses_router() -> APIRouter: @@ -73,7 +73,7 @@ def get_responses_router() -> APIRouter: @router.post("/", response_model=ResponseBody) async def create_response( request: ResponseRequest, - user: User = Depends(get_authenticated_user), + user: User = Depends(get_conditional_authenticated_user), ) -> ResponseBody: """ OpenAI-compatible responses endpoint with function calling support. diff --git a/cognee/api/v1/search/routers/get_search_router.py b/cognee/api/v1/search/routers/get_search_router.py index 0f063f082..8a238286b 100644 --- a/cognee/api/v1/search/routers/get_search_router.py +++ b/cognee/api/v1/search/routers/get_search_router.py @@ -9,7 +9,7 @@ from cognee.api.DTO import InDTO, OutDTO from cognee.modules.users.exceptions.exceptions import PermissionDeniedError from cognee.modules.users.models import User from cognee.modules.search.operations import get_history -from cognee.modules.users.methods import get_optional_authenticated_user, get_default_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.shared.utils import send_telemetry @@ -33,7 +33,7 @@ def get_search_router() -> APIRouter: created_at: datetime @router.get("", response_model=list[SearchHistoryItem]) - async def get_search_history(user: Optional[User] = Depends(get_optional_authenticated_user)): + async def get_search_history(user: User = Depends(get_conditional_authenticated_user)): """ Get search history for the authenticated user. @@ -50,10 +50,6 @@ def get_search_router() -> APIRouter: ## Error Codes - **500 Internal Server Error**: Error retrieving search history """ - # Use default user for anonymous requests - if user is None: - user = await get_default_user() - send_telemetry( "Search API Endpoint Invoked", user.id, @@ -70,7 +66,7 @@ def get_search_router() -> APIRouter: return JSONResponse(status_code=500, content={"error": str(error)}) @router.post("", response_model=list) - async def search(payload: SearchPayloadDTO, user: Optional[User] = Depends(get_optional_authenticated_user)): + async def search(payload: SearchPayloadDTO, user: User = Depends(get_conditional_authenticated_user)): """ Search for nodes in the graph database. @@ -97,10 +93,6 @@ def get_search_router() -> APIRouter: - To search datasets not owned by the request sender, dataset UUID is needed - If permission is denied, returns empty list instead of error """ - # Use default user for anonymous requests - if user is None: - user = await get_default_user() - send_telemetry( "Search API Endpoint Invoked", user.id, diff --git a/cognee/api/v1/settings/routers/get_settings_router.py b/cognee/api/v1/settings/routers/get_settings_router.py index c85352746..5b650e46a 100644 --- a/cognee/api/v1/settings/routers/get_settings_router.py +++ b/cognee/api/v1/settings/routers/get_settings_router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from cognee.api.DTO import InDTO, OutDTO from typing import Union, Optional, Literal -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user from fastapi import Depends from cognee.modules.users.models import User from cognee.modules.settings.get_settings import LLMConfig, VectorDBConfig @@ -45,7 +45,7 @@ def get_settings_router() -> APIRouter: router = APIRouter() @router.get("", response_model=SettingsDTO) - async def get_settings(user: User = Depends(get_authenticated_user)): + async def get_settings(user: User = Depends(get_conditional_authenticated_user)): """ Get the current system settings. @@ -67,7 +67,7 @@ def get_settings_router() -> APIRouter: @router.post("", response_model=None) async def save_settings( - new_settings: SettingsPayloadDTO, user: User = Depends(get_authenticated_user) + new_settings: SettingsPayloadDTO, user: User = Depends(get_conditional_authenticated_user) ): """ Save or update system settings. diff --git a/cognee/api/v1/users/routers/get_visualize_router.py b/cognee/api/v1/users/routers/get_visualize_router.py index 95e79d3d5..2ff8a7207 100644 --- a/cognee/api/v1/users/routers/get_visualize_router.py +++ b/cognee/api/v1/users/routers/get_visualize_router.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends from fastapi.responses import HTMLResponse, JSONResponse from uuid import UUID from cognee.shared.logging_utils import get_logger -from cognee.modules.users.methods import get_authenticated_user +from cognee.modules.users.methods import get_conditional_authenticated_user from cognee.modules.data.methods import get_authorized_existing_datasets from cognee.modules.users.models import User @@ -16,7 +16,7 @@ def get_visualize_router() -> APIRouter: router = APIRouter() @router.get("", response_model=None) - async def visualize(dataset_id: UUID, user: User = Depends(get_authenticated_user)): + async def visualize(dataset_id: UUID, user: User = Depends(get_conditional_authenticated_user)): """ Generate an HTML visualization of the dataset's knowledge graph. diff --git a/cognee/modules/users/methods/__init__.py b/cognee/modules/users/methods/__init__.py index 7d83cc314..aee91b823 100644 --- a/cognee/modules/users/methods/__init__.py +++ b/cognee/modules/users/methods/__init__.py @@ -4,5 +4,4 @@ from .delete_user import delete_user from .get_default_user import get_default_user from .get_user_by_email import get_user_by_email from .create_default_user import create_default_user -from .get_authenticated_user import get_authenticated_user -from .get_optional_authenticated_user import get_optional_authenticated_user +from .get_conditional_authenticated_user import get_conditional_authenticated_user, REQUIRE_AUTHENTICATION diff --git a/cognee/modules/users/methods/get_authenticated_user.py b/cognee/modules/users/methods/get_authenticated_user.py deleted file mode 100644 index b60ddfe28..000000000 --- a/cognee/modules/users/methods/get_authenticated_user.py +++ /dev/null @@ -1,48 +0,0 @@ -from ..get_fastapi_users import get_fastapi_users - - -fastapi_users = get_fastapi_users() - -get_authenticated_user = fastapi_users.current_user(active=True) - -# from types import SimpleNamespace - -# from ..get_fastapi_users import get_fastapi_users -# from fastapi import HTTPException, Security -# from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -# import os -# import jwt - -# from uuid import UUID - -# fastapi_users = get_fastapi_users() - -# # Allows Swagger to understand authorization type and allow single sign on for the Swagger docs to test backend -# bearer_scheme = HTTPBearer(scheme_name="BearerAuth", description="Paste **Bearer <JWT>**") - - -# async def get_authenticated_user( -# creds: HTTPAuthorizationCredentials = Security(bearer_scheme), -# ) -> SimpleNamespace: -# """ -# Extract and validate the JWT presented in the Authorization header. -# """ -# if creds is None: # header missing -# raise HTTPException(status_code=401, detail="Not authenticated") - -# if creds.scheme.lower() != "bearer": # shouldn't happen extra guard -# raise HTTPException(status_code=401, detail="Invalid authentication scheme") - -# token = creds.credentials -# try: -# payload = jwt.decode( -# token, os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret"), algorithms=["HS256"] -# ) - -# auth_data = SimpleNamespace(id=UUID(payload["user_id"])) -# return auth_data - -# except jwt.ExpiredSignatureError: -# raise HTTPException(status_code=401, detail="Token has expired") -# except jwt.InvalidTokenError: -# raise HTTPException(status_code=401, detail="Invalid token") diff --git a/cognee/modules/users/methods/get_conditional_authenticated_user.py b/cognee/modules/users/methods/get_conditional_authenticated_user.py new file mode 100644 index 000000000..644d1aa54 --- /dev/null +++ b/cognee/modules/users/methods/get_conditional_authenticated_user.py @@ -0,0 +1,35 @@ +import os +from typing import Optional +from fastapi import Depends +from ..models import User +from ..get_fastapi_users import get_fastapi_users +from .get_default_user import get_default_user + +# Check environment variable to determine authentication requirement +REQUIRE_AUTHENTICATION = os.getenv("REQUIRE_AUTHENTICATION", "false").lower() == "true" + +fastapi_users = get_fastapi_users() + +if REQUIRE_AUTHENTICATION: + # When REQUIRE_AUTHENTICATION=true, enforce authentication (original behavior) + _auth_dependency = fastapi_users.current_user(active=True) +else: + # When REQUIRE_AUTHENTICATION=false (default), make authentication optional + _auth_dependency = fastapi_users.current_user( + optional=True, # Returns None instead of raising HTTPException(401) + active=True # Still require users to be active when authenticated + ) + +async def get_conditional_authenticated_user(user: Optional[User] = Depends(_auth_dependency)) -> User: + """ + Get authenticated user with environment-controlled behavior: + - If REQUIRE_AUTHENTICATION=true: Enforces authentication (raises 401 if not authenticated) + - If REQUIRE_AUTHENTICATION=false: Falls back to default user if not authenticated + + Always returns a User object for consistent typing. + """ + if user is None and not REQUIRE_AUTHENTICATION: + # When authentication is optional and user is None, use default user + user = await get_default_user() + + return user diff --git a/cognee/modules/users/methods/get_optional_authenticated_user.py b/cognee/modules/users/methods/get_optional_authenticated_user.py deleted file mode 100644 index 1b82e6051..000000000 --- a/cognee/modules/users/methods/get_optional_authenticated_user.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..get_fastapi_users import get_fastapi_users - -# Create optional authenticated user dependency using FastAPI Users' built-in optional parameter -fastapi_users = get_fastapi_users() -get_optional_authenticated_user = fastapi_users.current_user( - optional=True, # Returns None instead of raising HTTPException(401) - active=True # Still require users to be active when authenticated -)