fix(apikey): 修复管理员无法创建维护 API_KEY 的问题
- 前端:API Key 管理 tab 对所有登录用户可见,非管理员默认显示 API Key tab - 前端:apikey_api 改为普通 API 调用(移除 superadmin 专用接口) - 后端:list/create/get/update/delete/regenerate 接口改为按用户过滤,superadmin 可查看全部
This commit is contained in:
parent
efecc0692d
commit
3b5514e9b5
@ -66,14 +66,29 @@ class APIKeyCreateResponse(BaseModel):
|
||||
async def list_api_keys(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
current_user: User = Depends(get_superadmin_user),
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""列出所有 API Keys"""
|
||||
result = await db.execute(select(APIKey).order_by(APIKey.created_at.desc()).offset(skip).limit(limit))
|
||||
api_keys = result.scalars().all()
|
||||
|
||||
total_result = await db.execute(select(func.count(APIKey.id)))
|
||||
"""列出当前用户的 API Keys"""
|
||||
# 普通用户只能看到自己的 API Keys
|
||||
if current_user.role == "superadmin":
|
||||
# superadmin 可以看到所有
|
||||
result = await db.execute(select(APIKey).order_by(APIKey.created_at.desc()).offset(skip).limit(limit))
|
||||
api_keys = result.scalars().all()
|
||||
total_result = await db.execute(select(func.count(APIKey.id)))
|
||||
else:
|
||||
# 普通用户只看自己的
|
||||
result = await db.execute(
|
||||
select(APIKey)
|
||||
.filter(APIKey.user_id == current_user.id)
|
||||
.order_by(APIKey.created_at.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
)
|
||||
api_keys = result.scalars().all()
|
||||
total_result = await db.execute(
|
||||
select(func.count(APIKey.id)).filter(APIKey.user_id == current_user.id)
|
||||
)
|
||||
total = total_result.scalar()
|
||||
|
||||
return {
|
||||
@ -92,6 +107,10 @@ async def create_api_key(
|
||||
# 生成 Key
|
||||
full_key, key_hash, key_prefix = generate_api_key()
|
||||
|
||||
# 普通用户只能为自己创建 API Key,不能指定其他用户
|
||||
if data.user_id and data.user_id != current_user.id and current_user.role != "superadmin":
|
||||
raise HTTPException(status_code=403, detail="无权为其他用户创建 API Key")
|
||||
|
||||
# 验证关联用户
|
||||
if data.user_id:
|
||||
result = await db.execute(select(User).filter(User.id == data.user_id))
|
||||
@ -133,16 +152,20 @@ async def create_api_key(
|
||||
@apikey_router.get("/{api_key_id}", response_model=dict)
|
||||
async def get_api_key(
|
||||
api_key_id: int,
|
||||
current_user: User = Depends(get_superadmin_user),
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取单个 API Key"""
|
||||
"""获取单个 API Key(只能操作自己的 Key)"""
|
||||
result = await db.execute(select(APIKey).filter(APIKey.id == api_key_id))
|
||||
api_key = result.scalar_one_or_none()
|
||||
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API Key 不存在")
|
||||
|
||||
# 检查权限:只能操作自己的 Key,或者 superadmin 可以操作所有
|
||||
if api_key.user_id != current_user.id and current_user.role != "superadmin":
|
||||
raise HTTPException(status_code=403, detail="无权操作此 API Key")
|
||||
|
||||
return {"api_key": api_key.to_dict()}
|
||||
|
||||
|
||||
@ -150,16 +173,20 @@ async def get_api_key(
|
||||
async def update_api_key(
|
||||
api_key_id: int,
|
||||
data: APIKeyUpdate,
|
||||
current_user: User = Depends(get_superadmin_user),
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""更新 API Key"""
|
||||
"""更新 API Key(只能操作自己的 Key)"""
|
||||
result = await db.execute(select(APIKey).filter(APIKey.id == api_key_id))
|
||||
api_key = result.scalar_one_or_none()
|
||||
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API Key 不存在")
|
||||
|
||||
# 检查权限:只能操作自己的 Key,或者 superadmin 可以操作所有
|
||||
if api_key.user_id != current_user.id and current_user.role != "superadmin":
|
||||
raise HTTPException(status_code=403, detail="无权操作此 API Key")
|
||||
|
||||
if data.name is not None:
|
||||
api_key.name = data.name
|
||||
|
||||
@ -179,16 +206,20 @@ async def update_api_key(
|
||||
@apikey_router.delete("/{api_key_id}", response_model=dict)
|
||||
async def delete_api_key(
|
||||
api_key_id: int,
|
||||
current_user: User = Depends(get_superadmin_user),
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除 API Key"""
|
||||
"""删除 API Key(只能操作自己的 Key)"""
|
||||
result = await db.execute(select(APIKey).filter(APIKey.id == api_key_id))
|
||||
api_key = result.scalar_one_or_none()
|
||||
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API Key 不存在")
|
||||
|
||||
# 检查权限:只能操作自己的 Key,或者 superadmin 可以操作所有
|
||||
if api_key.user_id != current_user.id and current_user.role != "superadmin":
|
||||
raise HTTPException(status_code=403, detail="无权操作此 API Key")
|
||||
|
||||
await db.delete(api_key)
|
||||
await db.commit()
|
||||
|
||||
@ -198,16 +229,20 @@ async def delete_api_key(
|
||||
@apikey_router.post("/{api_key_id}/regenerate", response_model=APIKeyCreateResponse)
|
||||
async def regenerate_api_key(
|
||||
api_key_id: int,
|
||||
current_user: User = Depends(get_superadmin_user),
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""重新生成 API Key 密钥(secret 仅在此处返回一次)"""
|
||||
"""重新生成 API Key 密钥(secret 仅在此处返回一次,只能操作自己的 Key)"""
|
||||
result = await db.execute(select(APIKey).filter(APIKey.id == api_key_id))
|
||||
api_key = result.scalar_one_or_none()
|
||||
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API Key 不存在")
|
||||
|
||||
# 检查权限:只能操作自己的 Key,或者 superadmin 可以操作所有
|
||||
if api_key.user_id != current_user.id and current_user.role != "superadmin":
|
||||
raise HTTPException(status_code=403, detail="无权操作此 API Key")
|
||||
|
||||
# 生成新密钥
|
||||
full_key, key_hash, key_prefix = generate_api_key()
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@
|
||||
### 修复
|
||||
|
||||
- 重构聊天接口请求模型:流式与非流式聊天统一使用 `query + agent_config_id` 请求体,并移除路径中的 `agent_id`;同时修复非流式接口实际误走流式执行链路的问题,改为调用 `invoke_messages` 一次性执行,并补充对应测试
|
||||
- 修复对话线程与 Agent 配置错位的问题:发送消息时将当前 `agent_config_id` 绑定到 thread 的 `extra_metadata`,线程列表接口返回该绑定值,前端切换历史 thread 时会自动恢复对应配置
|
||||
- 为沙盒与 viewer 文件系统补齐知识库只读映射:新增 `/home/gem/kbs` 命名空间,按“用户可访问知识库 ∩ 当前 Agent 已启用知识库”暴露原始文件与解析后的 Markdown,并补充对应后端与 viewer 路由测试
|
||||
- 修复前端工具图标与渲染匹配不准确的问题:工具管理列表与工具调用结果统一改为基于工具 `id` 的精确映射,避免模糊匹配导致的误渲染,未命中的工具不再显示默认扳手图标
|
||||
- 修复 GitHub Pages 文档部署工作流失败:移除 `actions/setup-node@v4` 对不存在 `docs/package-lock.json` 的缓存依赖,并将 `docs` 目录安装命令从 `npm ci` 调整为 `npm install`,避免因未提交锁文件导致 CI 在依赖缓存和安装阶段直接失败
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { apiSuperAdminGet, apiSuperAdminPost, apiSuperAdminPut, apiDelete } from './base'
|
||||
import { apiGet, apiPost, apiPut, apiDelete } from './base'
|
||||
|
||||
export const apikeyApi = {
|
||||
list: (skip = 0, limit = 100) => apiSuperAdminGet('/api/apikey/', { params: { skip, limit } }),
|
||||
list: (skip = 0, limit = 100) => apiGet('/api/apikey/', { params: { skip, limit } }),
|
||||
|
||||
create: (data) => apiSuperAdminPost('/api/apikey/', data),
|
||||
create: (data) => apiPost('/api/apikey/', data),
|
||||
|
||||
get: (id) => apiSuperAdminGet(`/api/apikey/${id}`),
|
||||
get: (id) => apiGet(`/api/apikey/${id}`),
|
||||
|
||||
update: (id, data) => apiSuperAdminPut(`/api/apikey/${id}`, data),
|
||||
update: (id, data) => apiPut(`/api/apikey/${id}`, data),
|
||||
|
||||
delete: (id) => apiDelete(`/api/apikey/${id}`),
|
||||
|
||||
regenerate: (id) => apiSuperAdminPost(`/api/apikey/${id}/regenerate`)
|
||||
regenerate: (id) => apiPost(`/api/apikey/${id}/regenerate`)
|
||||
}
|
||||
|
||||
@ -622,6 +622,30 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
// ==================== 线程管理方法 ====================
|
||||
const setThreadAgentConfigId = (threadId, agentConfigId) => {
|
||||
if (!threadId) return
|
||||
const thread = threads.value.find((item) => item.id === threadId)
|
||||
if (thread) {
|
||||
thread.agent_config_id = agentConfigId ?? null
|
||||
}
|
||||
}
|
||||
|
||||
const syncSelectedConfigForThread = async (thread) => {
|
||||
if (!thread?.agent_config_id) return
|
||||
|
||||
const targetAgentId = thread.agent_id || currentAgentId.value
|
||||
if (!targetAgentId) return
|
||||
|
||||
const configList = agentStore.agentConfigs[targetAgentId] || []
|
||||
if (!configList.length) {
|
||||
await agentStore.fetchAgentConfigs(targetAgentId)
|
||||
}
|
||||
|
||||
if (selectedAgentConfigId.value !== thread.agent_config_id) {
|
||||
await agentStore.selectAgentConfig(thread.agent_config_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前智能体的线程列表
|
||||
const fetchThreads = async (agentId = null) => {
|
||||
const targetAgentId = props.singleMode ? agentId || currentAgentId.value : agentId
|
||||
@ -926,6 +950,8 @@ const sendMessage = async ({
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
setThreadAgentConfigId(threadId, selectedAgentConfigId.value)
|
||||
|
||||
const requestData = {
|
||||
query: text,
|
||||
thread_id: threadId,
|
||||
@ -1001,6 +1027,14 @@ const selectChat = async (chatId) => {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await syncSelectedConfigForThread(targetChat)
|
||||
} catch (error) {
|
||||
chatState.currentThreadId = previousThreadId
|
||||
handleChatError(error, 'load')
|
||||
return
|
||||
}
|
||||
|
||||
chatUIStore.isLoadingMessages = true
|
||||
try {
|
||||
await fetchThreadMessages({ agentId: targetAgentId, threadId: chatId })
|
||||
|
||||
@ -148,7 +148,7 @@ defineExpose({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 8px;
|
||||
padding: 6px 8px;
|
||||
// height: 28px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
class="sider-item"
|
||||
:class="{ activesec: activeTab === 'apikey' }"
|
||||
@click="activeTab = 'apikey'"
|
||||
v-if="userStore.isSuperAdmin"
|
||||
v-if="userStore.isLoggedIn"
|
||||
>
|
||||
<KeyIcon class="icon" :size="18" />
|
||||
<span>API Key</span>
|
||||
@ -103,7 +103,7 @@
|
||||
class="nav-item"
|
||||
:class="{ active: activeTab === 'apikey' }"
|
||||
@click="activeTab = 'apikey'"
|
||||
v-if="userStore.isSuperAdmin"
|
||||
v-if="userStore.isLoggedIn"
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
@ -128,7 +128,7 @@
|
||||
<DepartmentManagementComponent />
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'apikey'" v-if="userStore.isSuperAdmin">
|
||||
<div v-show="activeTab === 'apikey'" v-if="userStore.isLoggedIn">
|
||||
<ApiKeyManagementComponent />
|
||||
</div>
|
||||
</div>
|
||||
@ -175,6 +175,8 @@ watch(
|
||||
if (newVal) {
|
||||
if (userStore.isAdmin) {
|
||||
activeTab.value = 'base'
|
||||
} else if (userStore.isLogin) {
|
||||
activeTab.value = 'apikey'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user