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

188 lines
5.8 KiB
Python
Raw Normal View History

"""
用户私有模型凭据管理 - API 路由层
提供的接口
- GET /api/user/models/credentials - 获取当前用户的所有私有模型凭据
- POST /api/user/models/credentials - 创建新的私有模型凭据
- PUT /api/user/models/credentials/{id} - 更新指定的私有模型凭据
- DELETE /api/user/models/credentials/{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 KeyClaude Key
流程
1. 接收前端提交的表单数据model_idbase_urlapi_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_urlapi_keymodel_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