open-webui/backend/open_webui/routers/user_models.py

187 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
用户私有模型凭据管理 - API 路由层
提供的接口:
- GET /api/v1/user/models - 获取当前用户的所有私有模型凭据
- POST /api/v1/user/models - 创建新的私有模型凭据
- PUT /api/v1/user/models/{id} - 更新指定的私有模型凭据
- DELETE /api/v1/user/models/{id} - 删除指定的私有模型凭据
安全设计:
- 所有接口都需要用户认证get_verified_user
- 返回数据时自动掩码 API Key仅显示前 4 位和后 4 位)
- 更新/删除操作强制用户隔离(仅能操作自己的凭据)
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_verified_user
from open_webui.models.user_model_credentials import (
UserModelCredentialForm,
UserModelCredentialResponse,
UserModelCredentials,
mask_api_key,
)
log = logging.getLogger(__name__)
router = APIRouter()
def _mask_response(model):
"""
响应数据掩码处理 - 保护 API Key 不被前端完整获取
转换流程:
1. 将内部模型(包含明文 api_key转换为响应模型
2. 不回传明文 api_key只返回掩码字段 api_key_masked
Args:
model: UserModelCredentialModel包含明文 api_key
Returns:
UserModelCredentialResponse: 掩码后的响应对象
"""
return UserModelCredentialResponse(
**{
**{
k: v
for k, v in model.model_dump().items()
if k not in ["api_key"]
},
"api_key_masked": mask_api_key(model.api_key), # 掩码 API Key
}
)
@router.get("", response_model=list[UserModelCredentialResponse])
async def list_user_credentials(user=Depends(get_verified_user)):
"""
获取当前用户的所有私有模型凭据
用途:前端模型选择器初始化时调用,获取用户保存的所有私有 API 配置
返回掩码后的凭据列表api_key_masked 字段,不暴露明文)
权限:仅返回当前用户的凭据
Returns:
list[UserModelCredentialResponse]: 用户的所有私有模型凭据列表
"""
# 查询当前用户的所有凭据
creds = UserModelCredentials.get_credentials_by_user_id(user.id)
# 掩码 API Key 后返回
return [_mask_response(c) for c in creds]
@router.post("", response_model=UserModelCredentialResponse)
async def create_user_credential(
form_data: UserModelCredentialForm, user=Depends(get_verified_user)
):
"""
创建新的私有模型凭据
用途:用户在前端添加自己的 LLM API如自己的 OpenAI Key、Claude Key 等)
流程:
1. 接收前端提交的表单数据model_id、base_url、api_key 等)
2. 自动关联当前用户 ID
3. 生成唯一凭据 ID
4. 保存到数据库
5. 返回掩码后的凭据对象
Args:
form_data: 前端提交的凭据表单(不包含 id 和 user_id
user: 当前认证用户(由依赖注入自动获取)
Returns:
UserModelCredentialResponse: 创建成功的凭据对象API Key 已掩码)
Raises:
HTTPException(400): 创建失败时抛出
"""
# 创建新凭据,自动关联当前用户 ID
cred = UserModelCredentials.insert_new_credential(user.id, form_data)
if not cred:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(),
)
# 掩码 API Key 后返回
return _mask_response(cred)
@router.put("/{cred_id}", response_model=UserModelCredentialResponse)
async def update_user_credential(
cred_id: str, form_data: UserModelCredentialForm, user=Depends(get_verified_user)
):
"""
更新指定的私有模型凭据
用途:用户编辑自己保存的 API 凭据(修改 base_url、api_key、model_id 等)
安全校验:
- 凭据必须存在
- 凭据必须属于当前用户user_id 匹配)
- 不满足条件返回 404
更新策略:仅更新前端提交的字段(部分更新)
Args:
cred_id: 凭据 ID路径参数
form_data: 更新的数据
user: 当前认证用户(由依赖注入自动获取)
Returns:
UserModelCredentialResponse: 更新后的凭据对象API Key 已掩码)
Raises:
HTTPException(404): 凭据不存在或不属于当前用户
"""
# 更新凭据,自动校验用户权限
cred = UserModelCredentials.update_credential_by_id_and_user_id(
cred_id, user.id, form_data
)
if not cred:
# 凭据不存在或权限不足
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
# 掩码 API Key 后返回
return _mask_response(cred)
@router.delete("/{cred_id}", response_model=bool)
async def delete_user_credential(cred_id: str, user=Depends(get_verified_user)):
"""
删除指定的私有模型凭据
用途:用户删除自己保存的 API 凭据
安全校验:
- 凭据必须存在
- 凭据必须属于当前用户user_id 匹配)
- 不满足条件返回 404
Args:
cred_id: 凭据 ID路径参数
user: 当前认证用户(由依赖注入自动获取)
Returns:
bool: 删除成功返回 True
Raises:
HTTPException(404): 凭据不存在或不属于当前用户
"""
# 删除凭据,自动校验用户权限
result = UserModelCredentials.delete_credential_by_id_and_user_id(
cred_id, user.id
)
if not result:
# 凭据不存在或权限不足
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
return True