diff --git a/backend/ee/danswer/auth/api_key.py b/backend/danswer/auth/api_key.py similarity index 97% rename from backend/ee/danswer/auth/api_key.py rename to backend/danswer/auth/api_key.py index 9ea827d27dc..aef557960f6 100644 --- a/backend/ee/danswer/auth/api_key.py +++ b/backend/danswer/auth/api_key.py @@ -8,7 +8,7 @@ from pydantic import BaseModel from danswer.auth.schemas import UserRole -from ee.danswer.configs.app_configs import API_KEY_HASH_ROUNDS +from danswer.configs.app_configs import API_KEY_HASH_ROUNDS _API_KEY_HEADER_NAME = "Authorization" diff --git a/backend/danswer/auth/users.py b/backend/danswer/auth/users.py index 6c162dfb88c..9e2761622c8 100644 --- a/backend/danswer/auth/users.py +++ b/backend/danswer/auth/users.py @@ -52,6 +52,7 @@ from sqlalchemy.orm import attributes from sqlalchemy.orm import Session +from danswer.auth.api_key import get_hashed_api_key_from_request from danswer.auth.invited_users import get_invited_users from danswer.auth.schemas import UserCreate from danswer.auth.schemas import UserRole @@ -74,6 +75,7 @@ from danswer.configs.constants import DANSWER_API_KEY_DUMMY_EMAIL_DOMAIN from danswer.configs.constants import DANSWER_API_KEY_PREFIX from danswer.configs.constants import UNNAMED_KEY_PLACEHOLDER +from danswer.db.api_key import fetch_user_for_api_key from danswer.db.auth import get_access_token_db from danswer.db.auth import get_default_admin_user_emails from danswer.db.auth import get_user_count @@ -89,9 +91,9 @@ from danswer.utils.logger import setup_logger from danswer.utils.telemetry import optional_telemetry from danswer.utils.telemetry import RecordType +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop from danswer.utils.variable_functionality import fetch_versioned_implementation -from ee.danswer.server.tenants.provisioning import get_or_create_tenant_id -from ee.danswer.server.tenants.user_mapping import get_tenant_id_for_email +from shared_configs.configs import async_return_default_schema from shared_configs.configs import MULTI_TENANT from shared_configs.contextvars import CURRENT_TENANT_ID_CONTEXTVAR @@ -221,7 +223,13 @@ async def create( safe: bool = False, request: Optional[Request] = None, ) -> User: - tenant_id = await get_or_create_tenant_id(user_create.email) + tenant_id = await fetch_ee_implementation_or_noop( + "danswer.server.tenants.provisioning", + "get_or_create_tenant_id", + async_return_default_schema, + )( + email=user_create.email, + ) async with get_async_session_with_tenant(tenant_id) as db_session: token = CURRENT_TENANT_ID_CONTEXTVAR.set(tenant_id) @@ -281,7 +289,13 @@ async def oauth_callback( associate_by_email: bool = False, is_verified_by_default: bool = False, ) -> models.UOAP: - tenant_id = await get_or_create_tenant_id(account_email) + tenant_id = await fetch_ee_implementation_or_noop( + "danswer.server.tenants.provisioning", + "get_or_create_tenant_id", + async_return_default_schema, + )( + email=account_email, + ) if not tenant_id: raise HTTPException(status_code=401, detail="User not found") @@ -419,7 +433,13 @@ async def authenticate( email = credentials.username # Get tenant_id from mapping table - tenant_id = get_tenant_id_for_email(email) + tenant_id = await fetch_ee_implementation_or_noop( + "danswer.server.tenants.provisioning", + "get_or_create_tenant_id", + async_return_default_schema, + )( + email=email, + ) if not tenant_id: # User not found in mapping self.password_helper.hash(credentials.password) @@ -477,7 +497,14 @@ async def get_user_manager( # This strategy is used to add tenant_id to the JWT token class TenantAwareJWTStrategy(JWTStrategy): async def _create_token_data(self, user: User, impersonate: bool = False) -> dict: - tenant_id = get_tenant_id_for_email(user.email) + tenant_id = await fetch_ee_implementation_or_noop( + "danswer.server.tenants.provisioning", + "get_or_create_tenant_id", + async_return_default_schema, + )( + email=user.email, + ) + data = { "sub": str(user.id), "aud": self.token_audience, @@ -851,3 +878,22 @@ async def callback( return redirect_response return router + + +def api_key_dep( + request: Request, db_session: Session = Depends(get_session) +) -> User | None: + if AUTH_TYPE == AuthType.DISABLED: + return None + + hashed_api_key = get_hashed_api_key_from_request(request) + if not hashed_api_key: + raise HTTPException(status_code=401, detail="Missing API key") + + if hashed_api_key: + user = fetch_user_for_api_key(hashed_api_key, db_session) + + if user is None: + raise HTTPException(status_code=401, detail="Invalid API key") + + return user diff --git a/backend/danswer/background/task_name_builders.py b/backend/danswer/background/task_name_builders.py new file mode 100644 index 00000000000..3e24f2d2afe --- /dev/null +++ b/backend/danswer/background/task_name_builders.py @@ -0,0 +1,4 @@ +def name_sync_external_doc_permissions_task( + cc_pair_id: int, tenant_id: str | None = None +) -> str: + return f"sync_external_doc_permissions_task__{cc_pair_id}" diff --git a/backend/danswer/configs/app_configs.py b/backend/danswer/configs/app_configs.py index 3e2695072a2..5ea132a70a1 100644 --- a/backend/danswer/configs/app_configs.py +++ b/backend/danswer/configs/app_configs.py @@ -493,3 +493,13 @@ # Super Users SUPER_USERS = json.loads(os.environ.get("SUPER_USERS", '["pablo@danswer.ai"]')) SUPER_CLOUD_API_KEY = os.environ.get("SUPER_CLOUD_API_KEY", "api_key") + + +##### +# API Key Configs +##### +# refers to the rounds described here: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html +_API_KEY_HASH_ROUNDS_RAW = os.environ.get("API_KEY_HASH_ROUNDS") +API_KEY_HASH_ROUNDS = ( + int(_API_KEY_HASH_ROUNDS_RAW) if _API_KEY_HASH_ROUNDS_RAW else None +) diff --git a/backend/ee/danswer/db/api_key.py b/backend/danswer/db/api_key.py similarity index 95% rename from backend/ee/danswer/db/api_key.py rename to backend/danswer/db/api_key.py index db876067bd1..1a16d73a97d 100644 --- a/backend/ee/danswer/db/api_key.py +++ b/backend/danswer/db/api_key.py @@ -5,16 +5,16 @@ from sqlalchemy.orm import joinedload from sqlalchemy.orm import Session +from danswer.auth.api_key import ApiKeyDescriptor +from danswer.auth.api_key import build_displayable_api_key +from danswer.auth.api_key import generate_api_key +from danswer.auth.api_key import hash_api_key from danswer.configs.constants import DANSWER_API_KEY_DUMMY_EMAIL_DOMAIN from danswer.configs.constants import DANSWER_API_KEY_PREFIX from danswer.configs.constants import UNNAMED_KEY_PLACEHOLDER from danswer.db.models import ApiKey from danswer.db.models import User -from ee.danswer.auth.api_key import ApiKeyDescriptor -from ee.danswer.auth.api_key import build_displayable_api_key -from ee.danswer.auth.api_key import generate_api_key -from ee.danswer.auth.api_key import hash_api_key -from ee.danswer.server.api_key.models import APIKeyArgs +from danswer.server.api_key.models import APIKeyArgs from shared_configs.configs import MULTI_TENANT from shared_configs.contextvars import CURRENT_TENANT_ID_CONTEXTVAR diff --git a/backend/danswer/db/auth.py b/backend/danswer/db/auth.py index 0a794f3c963..95d15b259bd 100644 --- a/backend/danswer/db/auth.py +++ b/backend/danswer/db/auth.py @@ -14,6 +14,7 @@ from danswer.auth.invited_users import get_invited_users from danswer.auth.schemas import UserRole +from danswer.db.api_key import get_api_key_email_pattern from danswer.db.engine import get_async_session from danswer.db.engine import get_async_session_with_tenant from danswer.db.models import AccessToken @@ -22,7 +23,6 @@ from danswer.utils.variable_functionality import ( fetch_versioned_implementation_with_fallback, ) -from ee.danswer.db.api_key import get_api_key_email_pattern def get_default_admin_user_emails() -> list[str]: diff --git a/backend/danswer/db/connector_credential_pair.py b/backend/danswer/db/connector_credential_pair.py index b333dd78603..a44182d254b 100644 --- a/backend/danswer/db/connector_credential_pair.py +++ b/backend/danswer/db/connector_credential_pair.py @@ -25,8 +25,8 @@ from danswer.db.models import UserRole from danswer.server.models import StatusResponse from danswer.utils.logger import setup_logger -from ee.danswer.db.external_perm import delete_user__ext_group_for_cc_pair__no_commit -from ee.danswer.external_permissions.sync_params import check_if_valid_sync_source +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop + logger = setup_logger() @@ -351,7 +351,11 @@ def add_credential_to_connector( raise HTTPException(status_code=404, detail="Connector does not exist") if access_type == AccessType.SYNC: - if not check_if_valid_sync_source(connector.source): + if not fetch_ee_implementation_or_noop( + "danswer.external_permissions.sync_params", + "check_if_valid_sync_source", + noop_return_value=True, + )(connector.source): raise HTTPException( status_code=400, detail=f"Connector of type {connector.source} does not support SYNC access type", @@ -438,7 +442,10 @@ def remove_credential_from_connector( ) if association is not None: - delete_user__ext_group_for_cc_pair__no_commit( + fetch_ee_implementation_or_noop( + "danswer.db.external_perm", + "delete_user__ext_group_for_cc_pair__no_commit", + )( db_session=db_session, cc_pair_id=association.id, ) diff --git a/backend/danswer/db/token_limit.py b/backend/danswer/db/token_limit.py new file mode 100644 index 00000000000..24b2433cc1a --- /dev/null +++ b/backend/danswer/db/token_limit.py @@ -0,0 +1,111 @@ +from collections.abc import Sequence + +from sqlalchemy import select +from sqlalchemy.orm import Session + +from danswer.configs.constants import TokenRateLimitScope +from danswer.db.models import TokenRateLimit +from danswer.db.models import TokenRateLimit__UserGroup +from danswer.server.token_rate_limits.models import TokenRateLimitArgs + + +def fetch_all_user_token_rate_limits( + db_session: Session, + enabled_only: bool = False, + ordered: bool = True, +) -> Sequence[TokenRateLimit]: + query = select(TokenRateLimit).where( + TokenRateLimit.scope == TokenRateLimitScope.USER + ) + + if enabled_only: + query = query.where(TokenRateLimit.enabled.is_(True)) + + if ordered: + query = query.order_by(TokenRateLimit.created_at.desc()) + + return db_session.scalars(query).all() + + +def fetch_all_global_token_rate_limits( + db_session: Session, + enabled_only: bool = False, + ordered: bool = True, +) -> Sequence[TokenRateLimit]: + query = select(TokenRateLimit).where( + TokenRateLimit.scope == TokenRateLimitScope.GLOBAL + ) + + if enabled_only: + query = query.where(TokenRateLimit.enabled.is_(True)) + + if ordered: + query = query.order_by(TokenRateLimit.created_at.desc()) + + token_rate_limits = db_session.scalars(query).all() + return token_rate_limits + + +def insert_user_token_rate_limit( + db_session: Session, + token_rate_limit_settings: TokenRateLimitArgs, +) -> TokenRateLimit: + token_limit = TokenRateLimit( + enabled=token_rate_limit_settings.enabled, + token_budget=token_rate_limit_settings.token_budget, + period_hours=token_rate_limit_settings.period_hours, + scope=TokenRateLimitScope.USER, + ) + db_session.add(token_limit) + db_session.commit() + + return token_limit + + +def insert_global_token_rate_limit( + db_session: Session, + token_rate_limit_settings: TokenRateLimitArgs, +) -> TokenRateLimit: + token_limit = TokenRateLimit( + enabled=token_rate_limit_settings.enabled, + token_budget=token_rate_limit_settings.token_budget, + period_hours=token_rate_limit_settings.period_hours, + scope=TokenRateLimitScope.GLOBAL, + ) + db_session.add(token_limit) + db_session.commit() + + return token_limit + + +def update_token_rate_limit( + db_session: Session, + token_rate_limit_id: int, + token_rate_limit_settings: TokenRateLimitArgs, +) -> TokenRateLimit: + token_limit = db_session.get(TokenRateLimit, token_rate_limit_id) + if token_limit is None: + raise ValueError(f"TokenRateLimit with id '{token_rate_limit_id}' not found") + + token_limit.enabled = token_rate_limit_settings.enabled + token_limit.token_budget = token_rate_limit_settings.token_budget + token_limit.period_hours = token_rate_limit_settings.period_hours + db_session.commit() + + return token_limit + + +def delete_token_rate_limit( + db_session: Session, + token_rate_limit_id: int, +) -> None: + token_limit = db_session.get(TokenRateLimit, token_rate_limit_id) + if token_limit is None: + raise ValueError(f"TokenRateLimit with id '{token_rate_limit_id}' not found") + + db_session.query(TokenRateLimit__UserGroup).filter( + TokenRateLimit__UserGroup.rate_limit_id == token_rate_limit_id + ).delete() + + db_session.delete(token_limit) + db_session.commit() diff --git a/backend/danswer/one_shot_answer/answer_question.py b/backend/danswer/one_shot_answer/answer_question.py index f3cbe2b60af..c331eebe764 100644 --- a/backend/danswer/one_shot_answer/answer_question.py +++ b/backend/danswer/one_shot_answer/answer_question.py @@ -65,7 +65,7 @@ from danswer.tools.tool_runner import ToolCallKickoff from danswer.utils.logger import setup_logger from danswer.utils.timing import log_generator_function_time -from ee.danswer.server.query_and_chat.utils import create_temporary_persona +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop logger = setup_logger() @@ -125,11 +125,11 @@ def stream_answer_objects( ) temporary_persona: Persona | None = None + if query_req.persona_config is not None: - new_persona = create_temporary_persona( - db_session=db_session, persona_config=query_req.persona_config, user=user - ) - temporary_persona = new_persona + temporary_persona = fetch_ee_implementation_or_noop( + "danswer.server.query_and_chat.utils", "create_temporary_persona", None + )(db_session=db_session, persona_config=query_req.persona_config, user=user) persona = temporary_persona if temporary_persona else chat_session.persona diff --git a/backend/ee/danswer/server/api_key/api.py b/backend/danswer/server/api_key/api.py similarity index 81% rename from backend/ee/danswer/server/api_key/api.py rename to backend/danswer/server/api_key/api.py index c7353f055fb..cc21af616c7 100644 --- a/backend/ee/danswer/server/api_key/api.py +++ b/backend/danswer/server/api_key/api.py @@ -3,15 +3,15 @@ from sqlalchemy.orm import Session from danswer.auth.users import current_admin_user +from danswer.db.api_key import ApiKeyDescriptor +from danswer.db.api_key import fetch_api_keys +from danswer.db.api_key import insert_api_key +from danswer.db.api_key import regenerate_api_key +from danswer.db.api_key import remove_api_key +from danswer.db.api_key import update_api_key from danswer.db.engine import get_session from danswer.db.models import User -from ee.danswer.db.api_key import ApiKeyDescriptor -from ee.danswer.db.api_key import fetch_api_keys -from ee.danswer.db.api_key import insert_api_key -from ee.danswer.db.api_key import regenerate_api_key -from ee.danswer.db.api_key import remove_api_key -from ee.danswer.db.api_key import update_api_key -from ee.danswer.server.api_key.models import APIKeyArgs +from danswer.server.api_key.models import APIKeyArgs router = APIRouter(prefix="/admin/api-key") diff --git a/backend/ee/danswer/server/api_key/models.py b/backend/danswer/server/api_key/models.py similarity index 100% rename from backend/ee/danswer/server/api_key/models.py rename to backend/danswer/server/api_key/models.py diff --git a/backend/danswer/server/auth_check.py b/backend/danswer/server/auth_check.py index 4300bc464cb..64550b09861 100644 --- a/backend/danswer/server/auth_check.py +++ b/backend/danswer/server/auth_check.py @@ -10,8 +10,7 @@ from danswer.auth.users import current_user_with_expired_token from danswer.configs.app_configs import APP_API_PREFIX from danswer.server.danswer_api.ingestion import api_key_dep -from ee.danswer.auth.users import current_cloud_superuser -from ee.danswer.server.tenants.access import control_plane_dep +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop PUBLIC_ENDPOINT_SPECS = [ @@ -81,6 +80,14 @@ def check_router_auth( (1) have auth enabled OR (2) are explicitly marked as a public endpoint """ + + control_plane_dep = fetch_ee_implementation_or_noop( + "danswer.server.tenants.access", "control_plane_dep" + ) + current_cloud_superuser = fetch_ee_implementation_or_noop( + "danswer.auth.users", "current_cloud_superuser" + ) + for route in application.routes: # explicitly marked as public if is_route_in_spec_list(route, public_endpoint_specs): diff --git a/backend/danswer/server/danswer_api/ingestion.py b/backend/danswer/server/danswer_api/ingestion.py index bae316535c7..c65c870d461 100644 --- a/backend/danswer/server/danswer_api/ingestion.py +++ b/backend/danswer/server/danswer_api/ingestion.py @@ -3,6 +3,7 @@ from fastapi import HTTPException from sqlalchemy.orm import Session +from danswer.auth.users import api_key_dep from danswer.configs.constants import DocumentSource from danswer.connectors.models import Document from danswer.connectors.models import IndexAttemptMetadata @@ -22,7 +23,6 @@ from danswer.server.danswer_api.models import IngestionDocument from danswer.server.danswer_api.models import IngestionResult from danswer.utils.logger import setup_logger -from ee.danswer.auth.users import api_key_dep logger = setup_logger() diff --git a/backend/danswer/server/documents/cc_pair.py b/backend/danswer/server/documents/cc_pair.py index 68b48b85b0f..cc64ac563f6 100644 --- a/backend/danswer/server/documents/cc_pair.py +++ b/backend/danswer/server/documents/cc_pair.py @@ -16,6 +16,9 @@ try_creating_prune_generator_task, ) from danswer.background.celery.versioned_apps.primary import app as primary_app +from danswer.background.task_name_builders import ( + name_sync_external_doc_permissions_task, +) from danswer.db.connector_credential_pair import add_credential_to_connector from danswer.db.connector_credential_pair import get_connector_credential_pair_from_id from danswer.db.connector_credential_pair import remove_credential_from_connector @@ -47,11 +50,7 @@ from danswer.server.documents.models import PaginatedIndexAttempts from danswer.server.models import StatusResponse from danswer.utils.logger import setup_logger -from ee.danswer.background.task_name_builders import ( - name_sync_external_doc_permissions_task, -) -from ee.danswer.db.user_group import validate_user_creation_permissions - +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop logger = setup_logger() router = APIRouter(prefix="/manage") @@ -332,9 +331,6 @@ def sync_cc_pair( db_session: Session = Depends(get_session), ) -> StatusResponse[list[int]]: # avoiding circular refs - from ee.danswer.background.celery.apps.primary import ( - sync_external_doc_permissions_task, - ) cc_pair = get_connector_credential_pair_from_id( cc_pair_id=cc_pair_id, @@ -360,12 +356,19 @@ def sync_cc_pair( ) logger.info(f"Syncing the {cc_pair.connector.name} connector.") - sync_external_doc_permissions_task.apply_async( - kwargs=dict( - cc_pair_id=cc_pair_id, tenant_id=CURRENT_TENANT_ID_CONTEXTVAR.get() - ), + sync_external_doc_permissions_task = fetch_ee_implementation_or_noop( + "danswer.background.celery.apps.primary", + "sync_external_doc_permissions_task", + None, ) + if sync_external_doc_permissions_task: + sync_external_doc_permissions_task.apply_async( + kwargs=dict( + cc_pair_id=cc_pair_id, tenant_id=CURRENT_TENANT_ID_CONTEXTVAR.get() + ), + ) + return StatusResponse( success=True, message="Successfully created the sync task.", @@ -380,7 +383,9 @@ def associate_credential_to_connector( user: User | None = Depends(current_curator_or_admin_user), db_session: Session = Depends(get_session), ) -> StatusResponse[int]: - validate_user_creation_permissions( + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=metadata.groups, diff --git a/backend/danswer/server/documents/connector.py b/backend/danswer/server/documents/connector.py index 0bddcef605d..24f9920ac74 100644 --- a/backend/danswer/server/documents/connector.py +++ b/backend/danswer/server/documents/connector.py @@ -108,7 +108,7 @@ from danswer.server.documents.models import RunConnectorRequest from danswer.server.models import StatusResponse from danswer.utils.logger import setup_logger -from ee.danswer.db.user_group import validate_user_creation_permissions +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop logger = setup_logger() @@ -658,7 +658,10 @@ def create_connector_from_model( ) -> ObjectCreationIdResponse: try: _validate_connector_allowed(connector_data.source) - validate_user_creation_permissions( + + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=connector_data.groups, @@ -732,7 +735,9 @@ def update_connector_from_model( ) -> ConnectorSnapshot | StatusResponse[int]: try: _validate_connector_allowed(connector_data.source) - validate_user_creation_permissions( + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=connector_data.groups, diff --git a/backend/danswer/server/documents/credential.py b/backend/danswer/server/documents/credential.py index 42c72b8f34f..602ca27ee5c 100644 --- a/backend/danswer/server/documents/credential.py +++ b/backend/danswer/server/documents/credential.py @@ -28,7 +28,7 @@ from danswer.server.documents.models import ObjectCreationIdResponse from danswer.server.models import StatusResponse from danswer.utils.logger import setup_logger -from ee.danswer.db.user_group import validate_user_creation_permissions +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop logger = setup_logger() @@ -121,7 +121,9 @@ def create_credential_from_model( db_session: Session = Depends(get_session), ) -> ObjectCreationIdResponse: if not _ignore_credential_permissions(credential_info.source): - validate_user_creation_permissions( + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=credential_info.groups, diff --git a/backend/danswer/server/features/document_set/api.py b/backend/danswer/server/features/document_set/api.py index c9cea2cf2a2..26287d3f6e4 100644 --- a/backend/danswer/server/features/document_set/api.py +++ b/backend/danswer/server/features/document_set/api.py @@ -18,7 +18,7 @@ from danswer.server.features.document_set.models import DocumentSet from danswer.server.features.document_set.models import DocumentSetCreationRequest from danswer.server.features.document_set.models import DocumentSetUpdateRequest -from ee.danswer.db.user_group import validate_user_creation_permissions +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop router = APIRouter(prefix="/manage") @@ -30,7 +30,9 @@ def create_document_set( user: User = Depends(current_curator_or_admin_user), db_session: Session = Depends(get_session), ) -> int: - validate_user_creation_permissions( + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=document_set_creation_request.groups, @@ -53,7 +55,9 @@ def patch_document_set( user: User = Depends(current_curator_or_admin_user), db_session: Session = Depends(get_session), ) -> None: - validate_user_creation_permissions( + fetch_ee_implementation_or_noop( + "danswer.db.user_group", "validate_user_creation_permissions", None + )( db_session=db_session, user=user, target_group_ids=document_set_update_request.groups, diff --git a/backend/danswer/server/manage/users.py b/backend/danswer/server/manage/users.py index 5bfbce20902..b61e8db3e72 100644 --- a/backend/danswer/server/manage/users.py +++ b/backend/danswer/server/manage/users.py @@ -37,6 +37,7 @@ from danswer.configs.app_configs import SUPER_USERS from danswer.configs.app_configs import VALID_EMAIL_DOMAINS from danswer.configs.constants import AuthType +from danswer.db.api_key import is_api_key_email_address from danswer.db.auth import get_total_users_count from danswer.db.engine import CURRENT_TENANT_ID_CONTEXTVAR from danswer.db.engine import get_session @@ -60,13 +61,7 @@ from danswer.server.models import MinimalUserSnapshot from danswer.server.utils import send_user_email_invite from danswer.utils.logger import setup_logger -from ee.danswer.db.api_key import is_api_key_email_address -from ee.danswer.db.external_perm import delete_user__ext_group_for_user__no_commit -from ee.danswer.db.user_group import remove_curator_status__no_commit -from ee.danswer.server.tenants.billing import register_tenant_users -from ee.danswer.server.tenants.provisioning import add_users_to_tenant -from ee.danswer.server.tenants.user_mapping import get_tenant_id_for_email -from ee.danswer.server.tenants.user_mapping import remove_users_from_tenant +from danswer.utils.variable_functionality import fetch_ee_implementation_or_noop from shared_configs.configs import MULTI_TENANT logger = setup_logger() @@ -105,7 +100,10 @@ def set_user_role( ) if user_to_update.role == UserRole.CURATOR: - remove_curator_status__no_commit(db_session, user_to_update) + fetch_ee_implementation_or_noop( + "danswer.db.user_group", + "remove_curator_status__no_commit", + )(db_session, user_to_update) user_to_update.role = user_role_update_request.new_role.value @@ -205,7 +203,9 @@ def bulk_invite_users( if MULTI_TENANT: try: - add_users_to_tenant(normalized_emails, tenant_id) + fetch_ee_implementation_or_noop( + "danswer.server.tenants.provisioning", "add_users_to_tenant", None + )(normalized_emails, tenant_id) except IntegrityError as e: if isinstance(e.orig, UniqueViolation): @@ -226,9 +226,9 @@ def bulk_invite_users( return number_of_invited_users try: logger.info("Registering tenant users") - register_tenant_users( - CURRENT_TENANT_ID_CONTEXTVAR.get(), get_total_users_count(db_session) - ) + fetch_ee_implementation_or_noop( + "danswer.server.tenants.billing", "register_tenant_users", None + )(CURRENT_TENANT_ID_CONTEXTVAR.get(), get_total_users_count(db_session)) if ENABLE_EMAIL_INVITES: try: for email in all_emails: @@ -243,7 +243,9 @@ def bulk_invite_users( "Reverting changes: removing users from tenant and resetting invited users" ) write_invited_users(initial_invited_users) # Reset to original state - remove_users_from_tenant(normalized_emails, tenant_id) + fetch_ee_implementation_or_noop( + "danswer.server.tenants.user_mapping", "remove_users_from_tenant", None + )(normalized_emails, tenant_id) raise e @@ -257,14 +259,16 @@ def remove_invited_user( remaining_users = [user for user in user_emails if user != user_email.user_email] tenant_id = CURRENT_TENANT_ID_CONTEXTVAR.get() - remove_users_from_tenant([user_email.user_email], tenant_id) + fetch_ee_implementation_or_noop( + "danswer.server.tenants.user_mapping", "remove_users_from_tenant", None + )([user_email.user_email], tenant_id) number_of_invited_users = write_invited_users(remaining_users) try: if MULTI_TENANT: - register_tenant_users( - CURRENT_TENANT_ID_CONTEXTVAR.get(), get_total_users_count(db_session) - ) + fetch_ee_implementation_or_noop( + "danswer.server.tenants.billing", "register_tenant_users", None + )(CURRENT_TENANT_ID_CONTEXTVAR.get(), get_total_users_count(db_session)) except Exception: logger.error( "Request to update number of seats taken in control plane failed. " @@ -331,7 +335,10 @@ async def delete_user( for oauth_account in user_to_delete.oauth_accounts: db_session.delete(oauth_account) - delete_user__ext_group_for_user__no_commit( + fetch_ee_implementation_or_noop( + "danswer.db.external_perm", + "delete_user__ext_group_for_user__no_commit", + )( db_session=db_session, user_id=user_to_delete.id, ) @@ -498,7 +505,9 @@ def verify_user_logged_in( token_created_at = ( None if MULTI_TENANT else get_current_token_creation(user, db_session) ) - organization_name = get_tenant_id_for_email(user.email) + organization_name = fetch_ee_implementation_or_noop( + "danswer.server.tenants.user_mapping", "get_tenant_id_for_email", None + )(user.email) user_info = UserInfo.from_model( user, diff --git a/backend/danswer/server/query_and_chat/token_limit.py b/backend/danswer/server/query_and_chat/token_limit.py index d439e15a379..0f47ef7266f 100644 --- a/backend/danswer/server/query_and_chat/token_limit.py +++ b/backend/danswer/server/query_and_chat/token_limit.py @@ -18,9 +18,9 @@ from danswer.db.models import ChatSession from danswer.db.models import TokenRateLimit from danswer.db.models import User +from danswer.db.token_limit import fetch_all_global_token_rate_limits from danswer.utils.logger import setup_logger from danswer.utils.variable_functionality import fetch_versioned_implementation -from ee.danswer.db.token_limit import fetch_all_global_token_rate_limits from shared_configs.contextvars import CURRENT_TENANT_ID_CONTEXTVAR diff --git a/backend/danswer/server/token_rate_limits/api.py b/backend/danswer/server/token_rate_limits/api.py index 245e3391410..16755e06e7d 100644 --- a/backend/danswer/server/token_rate_limits/api.py +++ b/backend/danswer/server/token_rate_limits/api.py @@ -5,13 +5,13 @@ from danswer.auth.users import current_admin_user from danswer.db.engine import get_session from danswer.db.models import User +from danswer.db.token_limit import delete_token_rate_limit +from danswer.db.token_limit import fetch_all_global_token_rate_limits +from danswer.db.token_limit import insert_global_token_rate_limit +from danswer.db.token_limit import update_token_rate_limit from danswer.server.query_and_chat.token_limit import any_rate_limit_exists from danswer.server.token_rate_limits.models import TokenRateLimitArgs from danswer.server.token_rate_limits.models import TokenRateLimitDisplay -from ee.danswer.db.token_limit import delete_token_rate_limit -from ee.danswer.db.token_limit import fetch_all_global_token_rate_limits -from ee.danswer.db.token_limit import insert_global_token_rate_limit -from ee.danswer.db.token_limit import update_token_rate_limit router = APIRouter(prefix="/admin/token-rate-limits") diff --git a/backend/danswer/utils/variable_functionality.py b/backend/danswer/utils/variable_functionality.py index dfe6def2a56..e542d8fd362 100644 --- a/backend/danswer/utils/variable_functionality.py +++ b/backend/danswer/utils/variable_functionality.py @@ -119,3 +119,30 @@ def noop_fallback(*args: Any, **kwargs: Any) -> None: Returns: None """ + + +def fetch_ee_implementation_or_noop( + module: str, attribute: str, noop_return_value: Any = None +) -> Any: + """ + Fetches an EE implementation if EE is enabled, otherwise returns a no-op function. + Raises an exception if EE is enabled but the fetch fails. + + Args: + module (str): The name of the module from which to fetch the attribute. + attribute (str): The name of the attribute to fetch from the module. + + Returns: + Any: The fetched EE implementation if successful and EE is enabled, otherwise a no-op function. + + Raises: + Exception: If EE is enabled but the fetch fails. + """ + if not global_version.is_ee_version(): + return lambda *args, **kwargs: noop_return_value + + try: + return fetch_versioned_implementation(module, attribute) + except Exception as e: + logger.error(f"Failed to fetch implementation for {module}.{attribute}: {e}") + raise diff --git a/backend/ee/danswer/auth/users.py b/backend/ee/danswer/auth/users.py index 1ad384555c1..1db90e649d7 100644 --- a/backend/ee/danswer/auth/users.py +++ b/backend/ee/danswer/auth/users.py @@ -4,16 +4,15 @@ from fastapi import status from sqlalchemy.orm import Session +from danswer.auth.api_key import get_hashed_api_key_from_request from danswer.auth.users import current_admin_user from danswer.configs.app_configs import AUTH_TYPE from danswer.configs.app_configs import SUPER_CLOUD_API_KEY from danswer.configs.app_configs import SUPER_USERS from danswer.configs.constants import AuthType -from danswer.db.engine import get_session +from danswer.db.api_key import fetch_user_for_api_key from danswer.db.models import User from danswer.utils.logger import setup_logger -from ee.danswer.auth.api_key import get_hashed_api_key_from_request -from ee.danswer.db.api_key import fetch_user_for_api_key from ee.danswer.db.saml import get_saml_account from ee.danswer.server.seeding import get_seed_config from ee.danswer.utils.secrets import extract_hashed_cookie @@ -48,25 +47,6 @@ async def optional_user_( return user -def api_key_dep( - request: Request, db_session: Session = Depends(get_session) -) -> User | None: - if AUTH_TYPE == AuthType.DISABLED: - return None - - hashed_api_key = get_hashed_api_key_from_request(request) - if not hashed_api_key: - raise HTTPException(status_code=401, detail="Missing API key") - - if hashed_api_key: - user = fetch_user_for_api_key(hashed_api_key, db_session) - - if user is None: - raise HTTPException(status_code=401, detail="Invalid API key") - - return user - - def get_default_admin_user_emails_() -> list[str]: seed_config = get_seed_config() if seed_config and seed_config.admin_user_emails: diff --git a/backend/ee/danswer/background/celery/apps/primary.py b/backend/ee/danswer/background/celery/apps/primary.py index fecc21b58ef..98dd66be0a3 100644 --- a/backend/ee/danswer/background/celery/apps/primary.py +++ b/backend/ee/danswer/background/celery/apps/primary.py @@ -1,4 +1,7 @@ from danswer.background.celery.apps.primary import celery_app +from danswer.background.task_name_builders import ( + name_sync_external_doc_permissions_task, +) from danswer.background.task_utils import build_celery_task_wrapper from danswer.configs.app_configs import JOB_TIMEOUT from danswer.db.chat import delete_chat_sessions_older_than @@ -14,9 +17,6 @@ should_perform_external_group_permissions_check, ) from ee.danswer.background.task_name_builders import name_chat_ttl_task -from ee.danswer.background.task_name_builders import ( - name_sync_external_doc_permissions_task, -) from ee.danswer.background.task_name_builders import ( name_sync_external_group_permissions_task, ) diff --git a/backend/ee/danswer/background/celery_utils.py b/backend/ee/danswer/background/celery_utils.py index 80278d8c433..facad66db23 100644 --- a/backend/ee/danswer/background/celery_utils.py +++ b/backend/ee/danswer/background/celery_utils.py @@ -3,15 +3,15 @@ from sqlalchemy.orm import Session +from danswer.background.task_name_builders import ( + name_sync_external_doc_permissions_task, +) from danswer.db.enums import AccessType from danswer.db.models import ConnectorCredentialPair from danswer.db.tasks import check_task_is_live_and_not_timed_out from danswer.db.tasks import get_latest_task from danswer.utils.logger import setup_logger from ee.danswer.background.task_name_builders import name_chat_ttl_task -from ee.danswer.background.task_name_builders import ( - name_sync_external_doc_permissions_task, -) from ee.danswer.background.task_name_builders import ( name_sync_external_group_permissions_task, ) diff --git a/backend/ee/danswer/background/task_name_builders.py b/backend/ee/danswer/background/task_name_builders.py index aea6648a02d..39e2cf252ed 100644 --- a/backend/ee/danswer/background/task_name_builders.py +++ b/backend/ee/danswer/background/task_name_builders.py @@ -2,12 +2,6 @@ def name_chat_ttl_task(retention_limit_days: int, tenant_id: str | None = None) return f"chat_ttl_{retention_limit_days}_days" -def name_sync_external_doc_permissions_task( - cc_pair_id: int, tenant_id: str | None = None -) -> str: - return f"sync_external_doc_permissions_task__{cc_pair_id}" - - def name_sync_external_group_permissions_task( cc_pair_id: int, tenant_id: str | None = None ) -> str: diff --git a/backend/ee/danswer/configs/app_configs.py b/backend/ee/danswer/configs/app_configs.py index d36cef3daf8..7e1ade5f3a2 100644 --- a/backend/ee/danswer/configs/app_configs.py +++ b/backend/ee/danswer/configs/app_configs.py @@ -7,16 +7,6 @@ SAML_CONF_DIR = os.environ.get("SAML_CONF_DIR") or "/app/ee/danswer/configs/saml_config" -##### -# API Key Configs -##### -# refers to the rounds described here: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html -_API_KEY_HASH_ROUNDS_RAW = os.environ.get("API_KEY_HASH_ROUNDS") -API_KEY_HASH_ROUNDS = ( - int(_API_KEY_HASH_ROUNDS_RAW) if _API_KEY_HASH_ROUNDS_RAW else None -) - - ##### # Auto Permission Sync ##### diff --git a/backend/ee/danswer/db/token_limit.py b/backend/ee/danswer/db/token_limit.py index 95dd0011853..46f5e2d5e73 100644 --- a/backend/ee/danswer/db/token_limit.py +++ b/backend/ee/danswer/db/token_limit.py @@ -65,64 +65,6 @@ def _add_user_filters( return stmt.where(where_clause) -def fetch_all_user_token_rate_limits( - db_session: Session, - enabled_only: bool = False, - ordered: bool = True, -) -> Sequence[TokenRateLimit]: - query = select(TokenRateLimit).where( - TokenRateLimit.scope == TokenRateLimitScope.USER - ) - - if enabled_only: - query = query.where(TokenRateLimit.enabled.is_(True)) - - if ordered: - query = query.order_by(TokenRateLimit.created_at.desc()) - - return db_session.scalars(query).all() - - -def fetch_all_global_token_rate_limits( - db_session: Session, - enabled_only: bool = False, - ordered: bool = True, -) -> Sequence[TokenRateLimit]: - query = select(TokenRateLimit).where( - TokenRateLimit.scope == TokenRateLimitScope.GLOBAL - ) - - if enabled_only: - query = query.where(TokenRateLimit.enabled.is_(True)) - - if ordered: - query = query.order_by(TokenRateLimit.created_at.desc()) - - token_rate_limits = db_session.scalars(query).all() - return token_rate_limits - - -def fetch_user_group_token_rate_limits( - db_session: Session, - group_id: int, - user: User | None = None, - enabled_only: bool = False, - ordered: bool = True, - get_editable: bool = True, -) -> Sequence[TokenRateLimit]: - stmt = select(TokenRateLimit) - stmt = stmt.where(User__UserGroup.user_group_id == group_id) - stmt = _add_user_filters(stmt, user, get_editable) - - if enabled_only: - stmt = stmt.where(TokenRateLimit.enabled.is_(True)) - - if ordered: - stmt = stmt.order_by(TokenRateLimit.created_at.desc()) - - return db_session.scalars(stmt).all() - - def fetch_all_user_group_token_rate_limits_by_group( db_session: Session, ) -> Sequence[Row[tuple[TokenRateLimit, str]]]: @@ -138,38 +80,6 @@ def fetch_all_user_group_token_rate_limits_by_group( return db_session.execute(query).all() -def insert_user_token_rate_limit( - db_session: Session, - token_rate_limit_settings: TokenRateLimitArgs, -) -> TokenRateLimit: - token_limit = TokenRateLimit( - enabled=token_rate_limit_settings.enabled, - token_budget=token_rate_limit_settings.token_budget, - period_hours=token_rate_limit_settings.period_hours, - scope=TokenRateLimitScope.USER, - ) - db_session.add(token_limit) - db_session.commit() - - return token_limit - - -def insert_global_token_rate_limit( - db_session: Session, - token_rate_limit_settings: TokenRateLimitArgs, -) -> TokenRateLimit: - token_limit = TokenRateLimit( - enabled=token_rate_limit_settings.enabled, - token_budget=token_rate_limit_settings.token_budget, - period_hours=token_rate_limit_settings.period_hours, - scope=TokenRateLimitScope.GLOBAL, - ) - db_session.add(token_limit) - db_session.commit() - - return token_limit - - def insert_user_group_token_rate_limit( db_session: Session, token_rate_limit_settings: TokenRateLimitArgs, @@ -193,34 +103,22 @@ def insert_user_group_token_rate_limit( return token_limit -def update_token_rate_limit( +def fetch_user_group_token_rate_limits( db_session: Session, - token_rate_limit_id: int, - token_rate_limit_settings: TokenRateLimitArgs, -) -> TokenRateLimit: - token_limit = db_session.get(TokenRateLimit, token_rate_limit_id) - if token_limit is None: - raise ValueError(f"TokenRateLimit with id '{token_rate_limit_id}' not found") - - token_limit.enabled = token_rate_limit_settings.enabled - token_limit.token_budget = token_rate_limit_settings.token_budget - token_limit.period_hours = token_rate_limit_settings.period_hours - db_session.commit() - - return token_limit - + group_id: int, + user: User | None = None, + enabled_only: bool = False, + ordered: bool = True, + get_editable: bool = True, +) -> Sequence[TokenRateLimit]: + stmt = select(TokenRateLimit) + stmt = stmt.where(User__UserGroup.user_group_id == group_id) + stmt = _add_user_filters(stmt, user, get_editable) -def delete_token_rate_limit( - db_session: Session, - token_rate_limit_id: int, -) -> None: - token_limit = db_session.get(TokenRateLimit, token_rate_limit_id) - if token_limit is None: - raise ValueError(f"TokenRateLimit with id '{token_rate_limit_id}' not found") + if enabled_only: + stmt = stmt.where(TokenRateLimit.enabled.is_(True)) - db_session.query(TokenRateLimit__UserGroup).filter( - TokenRateLimit__UserGroup.rate_limit_id == token_rate_limit_id - ).delete() + if ordered: + stmt = stmt.order_by(TokenRateLimit.created_at.desc()) - db_session.delete(token_limit) - db_session.commit() + return db_session.scalars(stmt).all() diff --git a/backend/ee/danswer/main.py b/backend/ee/danswer/main.py index affa5fd1cde..d09a2893d69 100644 --- a/backend/ee/danswer/main.py +++ b/backend/ee/danswer/main.py @@ -12,11 +12,11 @@ from danswer.configs.constants import AuthType from danswer.main import get_application as get_application_base from danswer.main import include_router_with_global_prefix_prepended +from danswer.server.api_key.api import router as api_key_router from danswer.utils.logger import setup_logger from danswer.utils.variable_functionality import global_version from ee.danswer.configs.app_configs import OPENID_CONFIG_URL from ee.danswer.server.analytics.api import router as analytics_router -from ee.danswer.server.api_key.api import router as api_key_router from ee.danswer.server.auth_check import check_ee_router_auth from ee.danswer.server.enterprise_settings.api import ( admin_router as enterprise_settings_admin_router, diff --git a/backend/ee/danswer/server/middleware/tenant_tracking.py b/backend/ee/danswer/server/middleware/tenant_tracking.py index 20c0ba0afe2..92cc37f10bf 100644 --- a/backend/ee/danswer/server/middleware/tenant_tracking.py +++ b/backend/ee/danswer/server/middleware/tenant_tracking.py @@ -8,9 +8,9 @@ from fastapi import Request from fastapi import Response +from danswer.auth.api_key import extract_tenant_from_api_key_header from danswer.configs.app_configs import USER_AUTH_SECRET from danswer.db.engine import is_valid_schema_name -from ee.danswer.auth.api_key import extract_tenant_from_api_key_header from shared_configs.configs import MULTI_TENANT from shared_configs.configs import POSTGRES_DEFAULT_SCHEMA from shared_configs.contextvars import CURRENT_TENANT_ID_CONTEXTVAR diff --git a/backend/ee/danswer/server/query_and_chat/token_limit.py b/backend/ee/danswer/server/query_and_chat/token_limit.py index b4c588dc416..5c11a0a8e69 100644 --- a/backend/ee/danswer/server/query_and_chat/token_limit.py +++ b/backend/ee/danswer/server/query_and_chat/token_limit.py @@ -12,6 +12,7 @@ from sqlalchemy import select from sqlalchemy.orm import Session +from danswer.db.api_key import is_api_key_email_address from danswer.db.engine import get_session_with_tenant from danswer.db.models import ChatMessage from danswer.db.models import ChatSession @@ -20,12 +21,11 @@ from danswer.db.models import User from danswer.db.models import User__UserGroup from danswer.db.models import UserGroup +from danswer.db.token_limit import fetch_all_user_token_rate_limits from danswer.server.query_and_chat.token_limit import _get_cutoff_time from danswer.server.query_and_chat.token_limit import _is_rate_limited from danswer.server.query_and_chat.token_limit import _user_is_rate_limited_by_global from danswer.utils.threadpool_concurrency import run_functions_tuples_in_parallel -from ee.danswer.db.api_key import is_api_key_email_address -from ee.danswer.db.token_limit import fetch_all_user_token_rate_limits def _check_token_rate_limits(user: User | None, tenant_id: str | None) -> None: diff --git a/backend/ee/danswer/server/token_rate_limits/api.py b/backend/ee/danswer/server/token_rate_limits/api.py index 97f1f15faed..5006b34cf11 100644 --- a/backend/ee/danswer/server/token_rate_limits/api.py +++ b/backend/ee/danswer/server/token_rate_limits/api.py @@ -8,14 +8,14 @@ from danswer.auth.users import current_curator_or_admin_user from danswer.db.engine import get_session from danswer.db.models import User +from danswer.db.token_limit import fetch_all_user_token_rate_limits +from danswer.db.token_limit import insert_user_token_rate_limit from danswer.server.query_and_chat.token_limit import any_rate_limit_exists from danswer.server.token_rate_limits.models import TokenRateLimitArgs from danswer.server.token_rate_limits.models import TokenRateLimitDisplay from ee.danswer.db.token_limit import fetch_all_user_group_token_rate_limits_by_group -from ee.danswer.db.token_limit import fetch_all_user_token_rate_limits from ee.danswer.db.token_limit import fetch_user_group_token_rate_limits from ee.danswer.db.token_limit import insert_user_group_token_rate_limit -from ee.danswer.db.token_limit import insert_user_token_rate_limit router = APIRouter(prefix="/admin/token-rate-limits") diff --git a/backend/shared_configs/configs.py b/backend/shared_configs/configs.py index d4378251aa5..940bbdc4a96 100644 --- a/backend/shared_configs/configs.py +++ b/backend/shared_configs/configs.py @@ -1,4 +1,5 @@ import os +from typing import Any from typing import List from urllib.parse import urlparse @@ -133,6 +134,11 @@ def validate_cors_origin(origin: str) -> None: POSTGRES_DEFAULT_SCHEMA = os.environ.get("POSTGRES_DEFAULT_SCHEMA") or "public" + +async def async_return_default_schema(*args: Any, **kwargs: Any) -> str: + return POSTGRES_DEFAULT_SCHEMA + + # Prefix used for all tenant ids TENANT_ID_PREFIX = "tenant_" diff --git a/backend/tests/integration/common_utils/managers/api_key.py b/backend/tests/integration/common_utils/managers/api_key.py index 2a90c22fd76..9df6b1c4f2f 100644 --- a/backend/tests/integration/common_utils/managers/api_key.py +++ b/backend/tests/integration/common_utils/managers/api_key.py @@ -3,7 +3,7 @@ import requests from danswer.db.models import UserRole -from ee.danswer.server.api_key.models import APIKeyArgs +from danswer.server.api_key.models import APIKeyArgs from tests.integration.common_utils.constants import API_SERVER_URL from tests.integration.common_utils.constants import GENERAL_HEADERS from tests.integration.common_utils.test_models import DATestAPIKey diff --git a/web/src/app/ee/admin/api-key/DanswerApiKeyForm.tsx b/web/src/app/admin/api-key/DanswerApiKeyForm.tsx similarity index 100% rename from web/src/app/ee/admin/api-key/DanswerApiKeyForm.tsx rename to web/src/app/admin/api-key/DanswerApiKeyForm.tsx diff --git a/web/src/app/ee/admin/api-key/lib.ts b/web/src/app/admin/api-key/lib.ts similarity index 100% rename from web/src/app/ee/admin/api-key/lib.ts rename to web/src/app/admin/api-key/lib.ts diff --git a/web/src/app/ee/admin/api-key/page.tsx b/web/src/app/admin/api-key/page.tsx similarity index 100% rename from web/src/app/ee/admin/api-key/page.tsx rename to web/src/app/admin/api-key/page.tsx diff --git a/web/src/app/ee/admin/api-key/types.ts b/web/src/app/admin/api-key/types.ts similarity index 100% rename from web/src/app/ee/admin/api-key/types.ts rename to web/src/app/admin/api-key/types.ts diff --git a/web/src/components/admin/ClientLayout.tsx b/web/src/components/admin/ClientLayout.tsx index 7ca8eb75283..a1570022d35 100644 --- a/web/src/components/admin/ClientLayout.tsx +++ b/web/src/components/admin/ClientLayout.tsx @@ -289,20 +289,20 @@ export function ClientLayout({ ), link: "/admin/groups", }, - { - name: ( -
- -
API Keys
-
- ), - link: "/admin/api-key", - }, ] : []), + { + name: ( +
+ +
API Keys
+
+ ), + link: "/admin/api-key", + }, { name: (
diff --git a/web/src/middleware.ts b/web/src/middleware.ts index a98f24b5f52..835c1b86237 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -7,7 +7,6 @@ import { SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED } from "./lib/consta export const config = { matcher: [ "/admin/groups/:path*", - "/admin/api-key/:path*", "/admin/performance/usage/:path*", "/admin/performance/query-history/:path*", "/admin/whitelabeling/:path*",