Compare commits

...

2 Commits

Author SHA1 Message Date
Nik
3e8c76b9f2 fix: use SEAT_LIMIT_EXCEEDED for trial invite quota
UNAUTHORIZED implies auth failure, but hitting the invite limit on a
trial plan is a billing/quota concern — SEAT_LIMIT_EXCEEDED (402) is
semantically correct.
2026-03-10 19:37:50 -07:00
Nik
09b6ce6a00 refactor: replace HTTPException with OnyxError in server/manage 2026-03-05 11:26:02 -08:00
9 changed files with 186 additions and 186 deletions

View File

@@ -5,7 +5,6 @@ from typing import cast
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -28,6 +27,8 @@ from onyx.db.feedback import update_document_boost_for_user
from onyx.db.feedback import update_document_hidden_for_user
from onyx.db.index_attempt import cancel_indexing_attempts_for_ccpair
from onyx.db.models import User
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.file_store.file_store import get_default_file_store
from onyx.key_value_store.factory import get_kv_store
from onyx.key_value_store.interface import KvKeyNotFoundError
@@ -124,11 +125,11 @@ def validate_existing_genai_api_key(
try:
llm = get_default_llm(timeout=10)
except ValueError:
raise HTTPException(status_code=404, detail="LLM not setup")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "LLM not setup")
error = test_llm(llm)
if error:
raise HTTPException(status_code=400, detail=error)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error)
# Mark check as successful
curr_time = datetime.now(tz=timezone.utc)
@@ -159,10 +160,7 @@ def create_deletion_attempt_for_connector_id(
f"'{credential_id}' does not exist. Has it already been deleted?"
)
logger.error(error)
raise HTTPException(
status_code=404,
detail=error,
)
raise OnyxError(OnyxErrorCode.CONNECTOR_NOT_FOUND, error)
# Cancel any scheduled indexing attempts
cancel_indexing_attempts_for_ccpair(
@@ -178,9 +176,9 @@ def create_deletion_attempt_for_connector_id(
# connector_credential_pair=cc_pair, db_session=db_session
# )
# if deletion_attempt_disallowed_reason:
# raise HTTPException(
# status_code=400,
# detail=deletion_attempt_disallowed_reason,
# raise OnyxError(
# OnyxErrorCode.VALIDATION_ERROR,
# deletion_attempt_disallowed_reason,
# )
# mark as deleting

View File

@@ -2,8 +2,6 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import status
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -24,6 +22,8 @@ from onyx.db.discord_bot import update_discord_channel_config
from onyx.db.discord_bot import update_guild_config
from onyx.db.engine.sql_engine import get_session
from onyx.db.models import User
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.server.manage.discord_bot.models import DiscordBotConfigCreateRequest
from onyx.server.manage.discord_bot.models import DiscordBotConfigResponse
from onyx.server.manage.discord_bot.models import DiscordChannelConfigResponse
@@ -47,14 +47,14 @@ def _check_bot_config_api_access() -> None:
- When DISCORD_BOT_TOKEN env var is set (managed via env)
"""
if AUTH_TYPE == AuthType.CLOUD:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Discord bot configuration is managed by Onyx on Cloud.",
raise OnyxError(
OnyxErrorCode.UNAUTHORIZED,
"Discord bot configuration is managed by Onyx on Cloud.",
)
if DISCORD_BOT_TOKEN:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Discord bot is configured via environment variables. API access disabled.",
raise OnyxError(
OnyxErrorCode.UNAUTHORIZED,
"Discord bot is configured via environment variables. API access disabled.",
)
@@ -92,9 +92,9 @@ def create_bot_request(
bot_token=request.bot_token,
)
except ValueError:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Discord bot config already exists. Delete it first to create a new one.",
raise OnyxError(
OnyxErrorCode.CONFLICT,
"Discord bot config already exists. Delete it first to create a new one.",
)
db_session.commit()
@@ -117,7 +117,7 @@ def delete_bot_config_endpoint(
"""
deleted = delete_discord_bot_config(db_session)
if not deleted:
raise HTTPException(status_code=404, detail="Bot config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Bot config not found")
# Also delete the service API key used by the Discord bot
delete_discord_service_api_key(db_session)
@@ -144,7 +144,7 @@ def delete_service_api_key_endpoint(
"""
deleted = delete_discord_service_api_key(db_session)
if not deleted:
raise HTTPException(status_code=404, detail="Service API key not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Service API key not found")
db_session.commit()
return {"deleted": True}
@@ -189,7 +189,7 @@ def get_guild_config(
"""Get specific guild config."""
config = get_guild_config_by_internal_id(db_session, internal_id=config_id)
if not config:
raise HTTPException(status_code=404, detail="Guild config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Guild config not found")
return DiscordGuildConfigResponse.model_validate(config)
@@ -203,7 +203,7 @@ def update_guild_request(
"""Update guild config."""
config = get_guild_config_by_internal_id(db_session, internal_id=config_id)
if not config:
raise HTTPException(status_code=404, detail="Guild config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Guild config not found")
config = update_guild_config(
db_session,
@@ -228,7 +228,7 @@ def delete_guild_request(
"""
deleted = delete_guild_config(db_session, config_id)
if not deleted:
raise HTTPException(status_code=404, detail="Guild config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Guild config not found")
# On Cloud, delete service API key when all guilds are removed
if AUTH_TYPE == AuthType.CLOUD:
@@ -254,9 +254,9 @@ def list_channel_configs(
"""List whitelisted channels for a guild."""
guild_config = get_guild_config_by_internal_id(db_session, internal_id=config_id)
if not guild_config:
raise HTTPException(status_code=404, detail="Guild config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Guild config not found")
if not guild_config.guild_id:
raise HTTPException(status_code=400, detail="Guild not yet registered")
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Guild not yet registered")
configs = get_channel_configs(db_session, config_id)
return [DiscordChannelConfigResponse.model_validate(c) for c in configs]
@@ -278,7 +278,7 @@ def update_channel_request(
db_session, guild_config_id, channel_config_id
)
if not config:
raise HTTPException(status_code=404, detail="Channel config not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Channel config not found")
config = update_discord_channel_config(
db_session,

View File

@@ -3,7 +3,6 @@ import re
import requests
from fastapi import APIRouter
from fastapi import HTTPException
from onyx import __version__
from onyx.auth.users import anonymous_user_enabled
@@ -16,6 +15,8 @@ from onyx.configs.constants import DEV_VERSION_PATTERN
from onyx.configs.constants import PUBLIC_API_TAGS
from onyx.configs.constants import STABLE_VERSION_PATTERN
from onyx.db.auth import get_user_count
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.server.manage.models import AllVersions
from onyx.server.manage.models import AuthTypeResponse
from onyx.server.manage.models import ContainerVersions
@@ -104,14 +105,14 @@ def get_versions() -> AllVersions:
# Ensure we have at least one tag of each type
if not dev_tags:
raise HTTPException(
status_code=500,
detail="No valid dev versions found matching pattern v(number).(number).(number)-beta.(number)",
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR,
"No valid dev versions found matching pattern v(number).(number).(number)-beta.(number)",
)
if not stable_tags:
raise HTTPException(
status_code=500,
detail="No valid stable versions found matching pattern v(number).(number).(number)",
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR,
"No valid stable versions found matching pattern v(number).(number).(number)",
)
# Sort common tags and get the latest one

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -15,6 +14,8 @@ from onyx.db.llm import remove_llm_provider__no_commit
from onyx.db.models import LLMProvider as LLMProviderModel
from onyx.db.models import ModelConfiguration
from onyx.db.models import User
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.image_gen.exceptions import ImageProviderCredentialsError
from onyx.image_gen.factory import get_image_generation_provider
from onyx.image_gen.factory import validate_credentials
@@ -74,9 +75,9 @@ def _build_llm_provider_request(
# Clone mode: Only use API key from source provider
source_provider = db_session.get(LLMProviderModel, source_llm_provider_id)
if not source_provider:
raise HTTPException(
status_code=404,
detail=f"Source LLM provider with id {source_llm_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Source LLM provider with id {source_llm_provider_id} not found",
)
_validate_llm_provider_change(
@@ -110,9 +111,9 @@ def _build_llm_provider_request(
)
if not provider:
raise HTTPException(
status_code=400,
detail="No provider or source llm provided",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No provider or source llm provided",
)
credentials = ImageGenerationProviderCredentials(
@@ -124,9 +125,9 @@ def _build_llm_provider_request(
)
if not validate_credentials(provider, credentials):
raise HTTPException(
status_code=400,
detail=f"Incorrect credentials for {provider}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Incorrect credentials for {provider}",
)
return LLMProviderUpsertRequest(
@@ -215,9 +216,9 @@ def test_image_generation(
LLMProviderModel, test_request.source_llm_provider_id
)
if not source_provider:
raise HTTPException(
status_code=404,
detail=f"Source LLM provider with id {test_request.source_llm_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Source LLM provider with id {test_request.source_llm_provider_id} not found",
)
_validate_llm_provider_change(
@@ -236,9 +237,9 @@ def test_image_generation(
provider = source_provider.provider
if provider is None:
raise HTTPException(
status_code=400,
detail="No provider or source llm provided",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No provider or source llm provided",
)
try:
@@ -257,14 +258,14 @@ def test_image_generation(
),
)
except ValueError:
raise HTTPException(
status_code=404,
detail=f"Invalid image generation provider: {provider}",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Invalid image generation provider: {provider}",
)
except ImageProviderCredentialsError:
raise HTTPException(
status_code=401,
detail="Invalid image generation credentials",
raise OnyxError(
OnyxErrorCode.UNAUTHENTICATED,
"Invalid image generation credentials",
)
quality = _get_test_quality_for_model(test_request.model_name)
@@ -276,15 +277,15 @@ def test_image_generation(
n=1,
quality=quality,
)
except HTTPException:
except OnyxError:
raise
except Exception as e:
# Log only exception type to avoid exposing sensitive data
# (LiteLLM errors may contain URLs with API keys or auth tokens)
logger.warning(f"Image generation test failed: {type(e).__name__}")
raise HTTPException(
status_code=400,
detail=f"Image generation test failed: {type(e).__name__}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Image generation test failed: {type(e).__name__}",
)
@@ -309,9 +310,9 @@ def create_config(
db_session, config_create.image_provider_id
)
if existing_config:
raise HTTPException(
status_code=400,
detail=f"ImageGenerationConfig with image_provider_id '{config_create.image_provider_id}' already exists",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"ImageGenerationConfig with image_provider_id '{config_create.image_provider_id}' already exists",
)
try:
@@ -345,10 +346,10 @@ def create_config(
db_session.commit()
db_session.refresh(config)
return ImageGenerationConfigView.from_model(config)
except HTTPException:
except OnyxError:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@admin_router.get("/config")
@@ -373,9 +374,9 @@ def get_config_credentials(
"""
config = get_image_generation_config(db_session, image_provider_id)
if not config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
return ImageGenerationCredentials.from_model(config)
@@ -401,9 +402,9 @@ def update_config(
# 1. Get existing config
existing_config = get_image_generation_config(db_session, image_provider_id)
if not existing_config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
old_llm_provider_id = existing_config.model_configuration.llm_provider_id
@@ -472,10 +473,10 @@ def update_config(
db_session.refresh(existing_config)
return ImageGenerationConfigView.from_model(existing_config)
except HTTPException:
except OnyxError:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@admin_router.delete("/config/{image_provider_id}")
@@ -489,9 +490,9 @@ def delete_config(
# Get the config first to find the associated LLM provider
existing_config = get_image_generation_config(db_session, image_provider_id)
if not existing_config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
llm_provider_id = existing_config.model_configuration.llm_provider_id
@@ -503,10 +504,10 @@ def delete_config(
remove_llm_provider__no_commit(db_session, llm_provider_id)
db_session.commit()
except HTTPException:
except OnyxError:
raise
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))
@admin_router.post("/config/{image_provider_id}/default")
@@ -519,7 +520,7 @@ def set_config_as_default(
try:
set_default_image_generation_config(db_session, image_provider_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))
@admin_router.delete("/config/{image_provider_id}/default")
@@ -532,4 +533,4 @@ def unset_config_as_default(
try:
unset_default_image_generation_config(db_session, image_provider_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))

View File

@@ -1,7 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import status
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -27,6 +25,8 @@ from onyx.db.search_settings import update_current_search_settings
from onyx.db.search_settings import update_search_settings_status
from onyx.document_index.factory import get_all_document_indices
from onyx.document_index.factory import get_default_document_index
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.file_processing.unstructured import delete_unstructured_api_key
from onyx.file_processing.unstructured import get_unstructured_api_key
from onyx.file_processing.unstructured import update_unstructured_api_key
@@ -58,9 +58,9 @@ def set_new_search_settings(
# Disallow contextual RAG for cloud deployments.
if MULTI_TENANT and search_settings_new.enable_contextual_rag:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Contextual RAG disabled in Onyx Cloud",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Contextual RAG disabled in Onyx Cloud",
)
# Validate cloud provider exists or create new LiteLLM provider.
@@ -70,9 +70,9 @@ def set_new_search_settings(
)
if cloud_provider is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"No embedding provider exists for cloud embedding type {search_settings_new.provider_type}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"No embedding provider exists for cloud embedding type {search_settings_new.provider_type}",
)
validate_contextual_rag_model(
@@ -188,7 +188,7 @@ def delete_search_settings_endpoint(
search_settings_id=deletion_request.search_settings_id,
)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@router.get("/get-current-search-settings")
@@ -238,9 +238,9 @@ def update_saved_search_settings(
) -> None:
# Disallow contextual RAG for cloud deployments
if MULTI_TENANT and search_settings.enable_contextual_rag:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Contextual RAG disabled in Onyx Cloud",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Contextual RAG disabled in Onyx Cloud",
)
validate_contextual_rag_model(
@@ -294,7 +294,7 @@ def validate_contextual_rag_model(
model_name=model_name,
db_session=db_session,
):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error_msg)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_msg)
def _validate_contextual_rag_model(

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -21,6 +20,8 @@ from onyx.db.slack_channel_config import fetch_slack_channel_configs
from onyx.db.slack_channel_config import insert_slack_channel_config
from onyx.db.slack_channel_config import remove_slack_channel_config
from onyx.db.slack_channel_config import update_slack_channel_config
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.onyxbot.slack.config import validate_channel_name
from onyx.server.manage.models import SlackBot
from onyx.server.manage.models import SlackBotCreationRequest
@@ -63,10 +64,7 @@ def _form_channel_config(
current_slack_bot_id=slack_channel_config_creation_request.slack_bot_id,
)
except ValueError as e:
raise HTTPException(
status_code=400,
detail=str(e),
)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
if respond_tag_only and respond_member_group_list:
raise ValueError(
@@ -123,10 +121,7 @@ def create_slack_channel_config(
)
if channel_config["channel_name"] is None:
raise HTTPException(
status_code=400,
detail="Channel name is required",
)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Channel name is required")
persona_id = None
if slack_channel_config_creation_request.persona_id is not None:
@@ -171,10 +166,7 @@ def patch_slack_channel_config(
db_session=db_session, slack_channel_config_id=slack_channel_config_id
)
if existing_slack_channel_config is None:
raise HTTPException(
status_code=404,
detail="Slack channel config not found",
)
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Slack channel config not found")
existing_persona_id = existing_slack_channel_config.persona_id
if existing_persona_id is not None:

View File

@@ -13,7 +13,6 @@ from email_validator import validate_email
from fastapi import APIRouter
from fastapi import Body
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Query
from fastapi import Request
from fastapi.responses import StreamingResponse
@@ -73,6 +72,8 @@ from onyx.db.users import get_page_of_filtered_users
from onyx.db.users import get_total_filtered_users_count
from onyx.db.users import get_user_by_email
from onyx.db.users import validate_user_role_update
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.key_value_store.factory import get_kv_store
from onyx.redis.redis_pool import get_raw_redis_client
from onyx.server.documents.models import PaginatedReturn
@@ -124,7 +125,7 @@ def set_user_role(
email=user_role_update_request.user_email, db_session=db_session
)
if not user_to_update:
raise HTTPException(status_code=404, detail="User not found")
raise OnyxError(OnyxErrorCode.USER_NOT_FOUND, "User not found")
current_role = user_to_update.role
requested_role = user_role_update_request.new_role
@@ -139,9 +140,9 @@ def set_user_role(
)
if user_to_update.id == current_user.id:
raise HTTPException(
status_code=400,
detail="An admin cannot demote themselves from admin role!",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"An admin cannot demote themselves from admin role!",
)
if requested_role == UserRole.CURATOR:
@@ -386,9 +387,9 @@ def bulk_invite_users(
new_invited_emails.append(email_info.normalized)
except (EmailUndeliverableError, EmailNotValidError) as e:
raise HTTPException(
status_code=400,
detail=f"Invalid email address: {email} - {str(e)}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid email address: {email} - {str(e)}",
)
# Count only new users (not already invited or existing) that need seats
@@ -405,9 +406,9 @@ def bulk_invite_users(
if MULTI_TENANT and is_tenant_on_trial_fn(tenant_id):
current_invited = len(already_invited)
if current_invited + len(emails_needing_seats) > NUM_FREE_TRIAL_USER_INVITES:
raise HTTPException(
status_code=403,
detail="You have hit your invite limit. "
raise OnyxError(
OnyxErrorCode.SEAT_LIMIT_EXCEEDED,
"You have hit your invite limit. "
"Please upgrade for unlimited invites.",
)
@@ -502,14 +503,16 @@ def deactivate_user_api(
db_session: Session = Depends(get_session),
) -> None:
if current_user.email == user_email.user_email:
raise HTTPException(status_code=400, detail="You cannot deactivate yourself")
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR, "You cannot deactivate yourself"
)
user_to_deactivate = get_user_by_email(
email=user_email.user_email, db_session=db_session
)
if not user_to_deactivate:
raise HTTPException(status_code=404, detail="User not found")
raise OnyxError(OnyxErrorCode.USER_NOT_FOUND, "User not found")
if user_to_deactivate.is_active is False:
logger.warning("{} is already deactivated".format(user_to_deactivate.email))
@@ -534,14 +537,15 @@ async def delete_user(
email=user_email.user_email, db_session=db_session
)
if not user_to_delete:
raise HTTPException(status_code=404, detail="User not found")
raise OnyxError(OnyxErrorCode.USER_NOT_FOUND, "User not found")
if user_to_delete.is_active is True:
logger.warning(
"{} must be deactivated before deleting".format(user_to_delete.email)
)
raise HTTPException(
status_code=400, detail="User must be deactivated before deleting"
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"User must be deactivated before deleting",
)
# Detach the user from the current session
@@ -565,7 +569,7 @@ async def delete_user(
except Exception as e:
db_session.rollback()
logger.error(f"Error deleting user {user_to_delete.email}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting user")
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, "Error deleting user")
@router.patch("/manage/admin/activate-user", tags=PUBLIC_API_TAGS)
@@ -578,7 +582,7 @@ def activate_user_api(
email=user_email.user_email, db_session=db_session
)
if not user_to_activate:
raise HTTPException(status_code=404, detail="User not found")
raise OnyxError(OnyxErrorCode.USER_NOT_FOUND, "User not found")
if user_to_activate.is_active is True:
logger.warning("{} is already activated".format(user_to_activate.email))

View File

@@ -1,7 +1,8 @@
import requests
from fastapi import HTTPException
from onyx.configs.constants import SLACK_USER_TOKEN_PREFIX
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
SLACK_API_URL = "https://slack.com/api/auth.test"
SLACK_CONNECTIONS_OPEN_URL = "https://slack.com/api/apps.connections.open"
@@ -12,15 +13,15 @@ def validate_bot_token(bot_token: str) -> bool:
response = requests.post(SLACK_API_URL, headers=headers)
if response.status_code != 200:
raise HTTPException(
status_code=500, detail="Error communicating with Slack API."
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR, "Error communicating with Slack API."
)
data = response.json()
if not data.get("ok", False):
raise HTTPException(
status_code=400,
detail=f"Invalid bot token: {data.get('error', 'Unknown error')}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid bot token: {data.get('error', 'Unknown error')}",
)
return True
@@ -31,15 +32,15 @@ def validate_app_token(app_token: str) -> bool:
response = requests.post(SLACK_CONNECTIONS_OPEN_URL, headers=headers)
if response.status_code != 200:
raise HTTPException(
status_code=500, detail="Error communicating with Slack API."
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR, "Error communicating with Slack API."
)
data = response.json()
if not data.get("ok", False):
raise HTTPException(
status_code=400,
detail=f"Invalid app token: {data.get('error', 'Unknown error')}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid app token: {data.get('error', 'Unknown error')}",
)
return True
@@ -54,16 +55,16 @@ def validate_user_token(user_token: str | None) -> None:
Returns:
None is valid and will return successfully.
Raises:
HTTPException: If the token is invalid or missing required fields
OnyxError: If the token is invalid or missing required fields
"""
if not user_token:
# user_token is optional, so None or empty string is valid
return
if not user_token.startswith(SLACK_USER_TOKEN_PREFIX):
raise HTTPException(
status_code=400,
detail=f"Invalid user token format. User OAuth tokens must start with '{SLACK_USER_TOKEN_PREFIX}'",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid user token format. User OAuth tokens must start with '{SLACK_USER_TOKEN_PREFIX}'",
)
# Test the token with Slack API to ensure it's valid
@@ -71,13 +72,13 @@ def validate_user_token(user_token: str | None) -> None:
response = requests.post(SLACK_API_URL, headers=headers)
if response.status_code != 200:
raise HTTPException(
status_code=500, detail="Error communicating with Slack API."
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR, "Error communicating with Slack API."
)
data = response.json()
if not data.get("ok", False):
raise HTTPException(
status_code=400,
detail=f"Invalid user token: {data.get('error', 'Unknown error')}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid user token: {data.get('error', 'Unknown error')}",
)

View File

@@ -2,7 +2,6 @@ from __future__ import annotations
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Response
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.orm import Session
@@ -26,6 +25,8 @@ from onyx.db.web_search import set_active_web_content_provider
from onyx.db.web_search import set_active_web_search_provider
from onyx.db.web_search import upsert_web_content_provider
from onyx.db.web_search import upsert_web_search_provider
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.server.manage.web_search.models import WebContentProviderTestRequest
from onyx.server.manage.web_search.models import WebContentProviderUpsertRequest
from onyx.server.manage.web_search.models import WebContentProviderView
@@ -86,9 +87,9 @@ def upsert_search_provider_endpoint(
and request.id is not None
and existing_by_name.id != request.id
):
raise HTTPException(
status_code=400,
detail=f"A search provider named '{request.name}' already exists.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"A search provider named '{request.name}' already exists.",
)
provider = upsert_web_search_provider(
@@ -193,16 +194,16 @@ def test_search_provider(
request.provider_type, db_session
)
if existing_provider is None or not existing_provider.api_key:
raise HTTPException(
status_code=400,
detail="No stored API key found for this provider type.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No stored API key found for this provider type.",
)
api_key = existing_provider.api_key.get_value(apply_mask=False)
if requires_key and not api_key:
raise HTTPException(
status_code=400,
detail="API key is required. Either provide api_key or set use_stored_key to true.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"API key is required. Either provide api_key or set use_stored_key to true.",
)
try:
@@ -212,20 +213,21 @@ def test_search_provider(
config=request.config or {},
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(exc)) from exc
if provider is None:
raise HTTPException(
status_code=400, detail="Unable to build provider configuration."
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Unable to build provider configuration.",
)
# Run the API client's test_connection method to ensure the connection is valid.
try:
return provider.test_connection()
except HTTPException:
except OnyxError:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e)) from e
@admin_router.get("/content-providers", response_model=list[WebContentProviderView])
@@ -259,9 +261,9 @@ def upsert_content_provider_endpoint(
and request.id is not None
and existing_by_name.id != request.id
):
raise HTTPException(
status_code=400,
detail=f"A content provider named '{request.name}' already exists.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"A content provider named '{request.name}' already exists.",
)
provider = upsert_web_content_provider(
@@ -379,9 +381,9 @@ def test_content_provider(
request.provider_type, db_session
)
if existing_provider is None or not existing_provider.api_key:
raise HTTPException(
status_code=400,
detail="No stored API key found for this provider type.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No stored API key found for this provider type.",
)
if MULTI_TENANT:
stored_base_url = (
@@ -389,17 +391,17 @@ def test_content_provider(
)
request_base_url = request.config.base_url
if request_base_url != stored_base_url:
raise HTTPException(
status_code=400,
detail="Base URL cannot differ from stored provider when using stored API key",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Base URL cannot differ from stored provider when using stored API key",
)
api_key = existing_provider.api_key.get_value(apply_mask=False)
if not api_key:
raise HTTPException(
status_code=400,
detail="API key is required. Either provide api_key or set use_stored_key to true.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"API key is required. Either provide api_key or set use_stored_key to true.",
)
try:
@@ -409,11 +411,12 @@ def test_content_provider(
config=request.config,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(exc)) from exc
if provider is None:
raise HTTPException(
status_code=400, detail="Unable to build provider configuration."
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Unable to build provider configuration.",
)
# Actually test the API key by making a real content fetch call
@@ -425,11 +428,11 @@ def test_content_provider(
if not test_results or not any(
result.scrape_successful for result in test_results
):
raise HTTPException(
status_code=400,
detail="API key validation failed: content fetch returned no results.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"API key validation failed: content fetch returned no results.",
)
except HTTPException:
except OnyxError:
raise
except Exception as e:
error_msg = str(e)
@@ -438,13 +441,13 @@ def test_content_provider(
or "key" in error_msg.lower()
or "auth" in error_msg.lower()
):
raise HTTPException(
status_code=400,
detail=f"Invalid API key: {error_msg}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid API key: {error_msg}",
) from e
raise HTTPException(
status_code=400,
detail=f"API key validation failed: {error_msg}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"API key validation failed: {error_msg}",
) from e
logger.info(