mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-11 04:50:11 +02:00
refac
This commit is contained in:
@@ -199,9 +199,7 @@ if frontend_loader.exists():
|
||||
logging.error(f'An error occurred: {e}')
|
||||
|
||||
|
||||
####################################
|
||||
# STORAGE PROVIDER
|
||||
####################################
|
||||
# --- Storage Provider ---
|
||||
|
||||
STORAGE_PROVIDER = os.getenv('STORAGE_PROVIDER', 'local') # defaults to local, s3
|
||||
STORAGE_LOCAL_CACHE = os.getenv('STORAGE_LOCAL_CACHE', 'true').lower() == 'true'
|
||||
|
||||
@@ -2562,9 +2562,7 @@ async def get_current_usage(user=Depends(get_verified_user)):
|
||||
raise HTTPException(status_code=500, detail='Internal Server Error')
|
||||
|
||||
|
||||
############################
|
||||
# OAuth Login & Callback
|
||||
############################
|
||||
# --- OAuth Login & Callback ---
|
||||
|
||||
|
||||
# Initialize OAuth client manager with any MCP tool servers using OAuth 2.1
|
||||
@@ -2753,12 +2751,6 @@ async def oauth_login(provider: str, request: Request):
|
||||
return await oauth_manager.handle_login(request, provider)
|
||||
|
||||
|
||||
# OAuth login logic is as follows:
|
||||
# 1. Attempt to find a user with matching subject ID, tied to the provider
|
||||
# 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth
|
||||
# - This is considered insecure in general, as OAuth providers do not always verify email addresses
|
||||
# 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user
|
||||
# - Email addresses are considered unique, so we fail registration if the email address is already taken
|
||||
@app.get('/oauth/{provider}/login/callback')
|
||||
@app.get('/oauth/{provider}/callback') # Legacy endpoint
|
||||
async def oauth_login_callback(
|
||||
@@ -2767,6 +2759,15 @@ async def oauth_login_callback(
|
||||
response: Response,
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Handle the OAuth provider callback.
|
||||
|
||||
Resolution order:
|
||||
1. Match by subject ID bound to the provider.
|
||||
2. If ``OAUTH_MERGE_ACCOUNTS_BY_EMAIL`` is enabled, match by email
|
||||
(note: some providers do not verify email addresses).
|
||||
3. If no match and ``ENABLE_OAUTH_SIGNUP`` is enabled, create a new user
|
||||
(fails if the email is already registered).
|
||||
"""
|
||||
return await oauth_manager.handle_callback(request, provider, response, db=db)
|
||||
|
||||
|
||||
|
||||
@@ -17,28 +17,20 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
####################
|
||||
# Models DB Schema
|
||||
# A misconfigured model wastes the time of everyone
|
||||
# who trusts it. Let what is set here be set with care.
|
||||
####################
|
||||
# --- Models DB Schema ---
|
||||
|
||||
|
||||
# ModelParams is a model for the data stored in the params field of the Model table
|
||||
class ModelParams(BaseModel):
|
||||
"""Parameters for model inference (temperature, top_p, etc.)."""
|
||||
|
||||
model_config = ConfigDict(extra='allow')
|
||||
pass
|
||||
|
||||
|
||||
# ModelMeta is a model for the data stored in the meta field of the Model table
|
||||
class ModelMeta(BaseModel):
|
||||
"""Metadata for a workspace model entry (profile, description, tags, capabilities)."""
|
||||
|
||||
profile_image_url: str | None = '/static/favicon.png'
|
||||
|
||||
description: str | None = None
|
||||
"""
|
||||
User-facing description of the model.
|
||||
"""
|
||||
|
||||
description: str | None = Field(default=None, description='User-facing description of the model.')
|
||||
capabilities: dict | None = None
|
||||
|
||||
model_config = ConfigDict(extra='allow')
|
||||
@@ -59,7 +51,8 @@ class ModelMeta(BaseModel):
|
||||
return data
|
||||
|
||||
|
||||
class Model(Base): # provider model config
|
||||
class Model(Base):
|
||||
|
||||
"""Workspace model entry — wraps an upstream LLM with custom params and metadata."""
|
||||
|
||||
__tablename__ = 'model'
|
||||
|
||||
@@ -286,8 +286,9 @@ async def update_password(
|
||||
session_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
# Trusted-header auth mode delegates passwords to the reverse proxy
|
||||
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
|
||||
if session_user:
|
||||
user = await Auths.authenticate_user(
|
||||
session_user.email,
|
||||
@@ -578,7 +579,7 @@ async def signin(
|
||||
|
||||
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
|
||||
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
|
||||
|
||||
email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower()
|
||||
name = email
|
||||
|
||||
@@ -519,10 +519,7 @@ async def get_user_chat_list_by_user_id(
|
||||
):
|
||||
"""List chat summaries for a given user (admin-only endpoint)."""
|
||||
if not ENABLE_ADMIN_CHAT_ACCESS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
|
||||
effective_page = page if page is not None else 1
|
||||
limit = 60
|
||||
@@ -757,10 +754,7 @@ async def get_all_user_tags(user=Depends(get_verified_user), db: AsyncSession =
|
||||
@router.get('/all/db', response_model=list[ChatResponse])
|
||||
async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if not ENABLE_ADMIN_EXPORT:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_chats(db=db)]
|
||||
|
||||
|
||||
@@ -1326,9 +1320,7 @@ async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: Async
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
|
||||
############################
|
||||
# ShareChatById
|
||||
############################
|
||||
# --- Share Chat ---
|
||||
|
||||
|
||||
@router.post('/{id}/share', response_model=ChatResponse | None)
|
||||
@@ -1338,51 +1330,35 @@ async def share_chat_by_id(
|
||||
user=Depends(get_verified_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if (user.role != 'admin') and (
|
||||
not await has_permission(user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS)
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if not chat:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
|
||||
if chat:
|
||||
if chat.share_id:
|
||||
# Re-snapshot existing share
|
||||
shared = await SharedChats.update(chat.share_id, db=db)
|
||||
if shared:
|
||||
# Re-fetch the original chat to return
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
# If a share already exists, re-snapshot it
|
||||
if chat.share_id:
|
||||
shared = await SharedChats.update(chat.share_id, db=db)
|
||||
if shared:
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
|
||||
# Create new share
|
||||
shared = await SharedChats.create(id, user.id, db=db)
|
||||
if not shared:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=ERROR_MESSAGES.DEFAULT(),
|
||||
)
|
||||
# Set share_id on the original chat
|
||||
chat = await Chats.update_chat_share_id_by_id(id, shared.id, db=db)
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=ERROR_MESSAGES.DEFAULT(),
|
||||
)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
# Create a new share
|
||||
shared = await SharedChats.create(id, user.id, db=db)
|
||||
if not shared:
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
chat = await Chats.update_chat_share_id_by_id(id, shared.id, db=db)
|
||||
if not chat:
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
return ChatResponse(**chat.model_dump())
|
||||
|
||||
|
||||
############################
|
||||
# DeleteSharedChatById
|
||||
############################
|
||||
# --- Delete Shared Chat ---
|
||||
|
||||
|
||||
@router.delete('/{id}/share', response_model=bool | None)
|
||||
@@ -1390,22 +1366,17 @@ async def delete_shared_chat_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
if not chat.share_id:
|
||||
return False
|
||||
if not chat:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
|
||||
await SharedChats.delete_by_chat_id(id, db=db)
|
||||
await Chats.update_chat_share_id_by_id(id, None, db=db)
|
||||
if not chat.share_id:
|
||||
return False
|
||||
|
||||
# Revoke all access grants for this shared chat
|
||||
await AccessGrants.set_access_grants('shared_chat', id, [], db=db)
|
||||
await SharedChats.delete_by_chat_id(id, db=db)
|
||||
await Chats.update_chat_share_id_by_id(id, None, db=db)
|
||||
await AccessGrants.set_access_grants('shared_chat', id, [], db=db)
|
||||
|
||||
return True
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
############################
|
||||
|
||||
@@ -2258,12 +2258,8 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
|
||||
log.debug(f'urls: {urls}')
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.WEB_SEARCH_ERROR(e),
|
||||
)
|
||||
log.exception('Web search failed')
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.WEB_SEARCH_ERROR(e))
|
||||
|
||||
if len(urls) == 0:
|
||||
raise HTTPException(
|
||||
@@ -2343,11 +2339,8 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
|
||||
'loaded_count': len(docs),
|
||||
}
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
)
|
||||
log.exception('Web search content loading failed')
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||
|
||||
|
||||
async def _validate_collection_access(collection_names: list[str], user, access_type: str = 'read') -> None:
|
||||
|
||||
Reference in New Issue
Block a user