admin share
This commit is contained in:
parent
f110b26efc
commit
35f91a7d03
@ -144,6 +144,34 @@ async def get_user_id_from_token(authorization: Optional[str]) -> Optional[str]:
|
|||||||
|
|
||||||
# ============== 权限检查辅助函数 ==============
|
# ============== 权限检查辅助函数 ==============
|
||||||
|
|
||||||
|
async def is_admin_user(authorization: Optional[str]) -> bool:
|
||||||
|
"""
|
||||||
|
检查当前请求是否来自管理员(admin token 或 is_admin=True 的用户)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
authorization: Authorization header 值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否是管理员
|
||||||
|
"""
|
||||||
|
admin_valid, _ = await verify_admin_auth(authorization)
|
||||||
|
if admin_valid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
user_valid, user_id, _ = await verify_user_auth(authorization)
|
||||||
|
if not user_valid or not user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT is_admin FROM agent_user WHERE id = %s
|
||||||
|
""", (user_id,))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
return row and row[0]
|
||||||
|
|
||||||
|
|
||||||
async def check_bot_access(bot_id: str, user_id: str, required_permission: str) -> bool:
|
async def check_bot_access(bot_id: str, user_id: str, required_permission: str) -> bool:
|
||||||
"""
|
"""
|
||||||
检查用户对 Bot 的访问权限
|
检查用户对 Bot 的访问权限
|
||||||
@ -2246,7 +2274,7 @@ async def search_users(
|
|||||||
Returns:
|
Returns:
|
||||||
List[UserSearchResponse]: 用户列表
|
List[UserSearchResponse]: 用户列表
|
||||||
"""
|
"""
|
||||||
# 支持管理员认证和用户认证
|
# 支持管理员认证<EFBFBD><EFBFBD>用户认证
|
||||||
admin_valid, _ = await verify_admin_auth(authorization)
|
admin_valid, _ = await verify_admin_auth(authorization)
|
||||||
user_valid, user_id, _ = await verify_user_auth(authorization)
|
user_valid, user_id, _ = await verify_user_auth(authorization)
|
||||||
|
|
||||||
@ -2295,6 +2323,441 @@ async def search_users(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ============== 用户管理 API(仅管理员)=============
|
||||||
|
|
||||||
|
class UserListResponse(BaseModel):
|
||||||
|
"""用户列表响应"""
|
||||||
|
id: str
|
||||||
|
username: str
|
||||||
|
email: Optional[str] = None
|
||||||
|
is_admin: bool = False
|
||||||
|
is_active: bool = True
|
||||||
|
created_at: str
|
||||||
|
last_login: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateRequest(BaseModel):
|
||||||
|
"""创建用户请求"""
|
||||||
|
email: str
|
||||||
|
username: Optional[str] = None
|
||||||
|
password: str
|
||||||
|
is_admin: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdateRequest(BaseModel):
|
||||||
|
"""更新用户请求"""
|
||||||
|
email: Optional[str] = None
|
||||||
|
username: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPasswordRequest(BaseModel):
|
||||||
|
"""重置密码请求"""
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/users/list", response_model=List[UserListResponse])
|
||||||
|
async def get_all_users(authorization: Optional[str] = Header(None)):
|
||||||
|
"""
|
||||||
|
获取所有用户列表(<EFBFBD><EFBFBD>管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[UserListResponse]: 用户列表
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id, username, email, is_admin, is_active, created_at, last_login
|
||||||
|
FROM agent_user
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
""")
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
|
||||||
|
return [
|
||||||
|
UserListResponse(
|
||||||
|
id=str(row[0]),
|
||||||
|
username=row[1],
|
||||||
|
email=row[2],
|
||||||
|
is_admin=row[3] or False,
|
||||||
|
is_active=row[4],
|
||||||
|
created_at=row[5].isoformat() if row[5] else "",
|
||||||
|
last_login=row[6].isoformat() if row[6] else None
|
||||||
|
)
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/users/{user_id}", response_model=UserListResponse)
|
||||||
|
async def get_user(user_id: str, authorization: Optional[str] = Header(None)):
|
||||||
|
"""
|
||||||
|
获取单个用户信息(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户 ID
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserListResponse: 用户信息
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id, username, email, is_admin, is_active, created_at, last_login
|
||||||
|
FROM agent_user
|
||||||
|
WHERE id = %s
|
||||||
|
""", (user_id,))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
return UserListResponse(
|
||||||
|
id=str(row[0]),
|
||||||
|
username=row[1],
|
||||||
|
email=row[2],
|
||||||
|
is_admin=row[3] or False,
|
||||||
|
is_active=row[4],
|
||||||
|
created_at=row[5].isoformat() if row[5] else "",
|
||||||
|
last_login=row[6].isoformat() if row[6] else None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/users", response_model=UserListResponse)
|
||||||
|
async def create_user(
|
||||||
|
request: UserCreateRequest,
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
创建新用户(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: 创建用户请求
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserListResponse: 创建的用户信息
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# 如果提供了用户名,检查是否已存在
|
||||||
|
if request.username:
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id FROM agent_user WHERE username = %s
|
||||||
|
""", (request.username,))
|
||||||
|
if await cursor.fetchone():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="用户名已存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查邮箱是否已存在
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id FROM agent_user WHERE email = %s
|
||||||
|
""", (request.email,))
|
||||||
|
if await cursor.fetchone():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="邮箱已被注册"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建用户
|
||||||
|
password_hash = hash_password(request.password)
|
||||||
|
username = request.username or request.email.split('@')[0]
|
||||||
|
|
||||||
|
await cursor.execute("""
|
||||||
|
INSERT INTO agent_user (username, email, password_hash, is_admin)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
RETURNING id, created_at
|
||||||
|
""", (username, request.email, password_hash, request.is_admin))
|
||||||
|
user_id, created_at = await cursor.fetchone()
|
||||||
|
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
return UserListResponse(
|
||||||
|
id=str(user_id),
|
||||||
|
username=username,
|
||||||
|
email=request.email,
|
||||||
|
is_admin=request.is_admin,
|
||||||
|
is_active=True,
|
||||||
|
created_at=created_at.isoformat() if created_at else "",
|
||||||
|
last_login=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/v1/users/{user_id}", response_model=UserListResponse)
|
||||||
|
async def update_user(
|
||||||
|
user_id: str,
|
||||||
|
request: UserUpdateRequest,
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
更新用户信息(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户 ID
|
||||||
|
request: 更新请求
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserListResponse: 更新后的用户信息
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# 构建更新字段
|
||||||
|
update_fields = []
|
||||||
|
values = []
|
||||||
|
|
||||||
|
if request.username is not None:
|
||||||
|
# 检查用户名是否已被其他用户使用
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id FROM agent_user WHERE username = %s AND id != %s
|
||||||
|
""", (request.username, user_id))
|
||||||
|
if await cursor.fetchone():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="用户名已存在"
|
||||||
|
)
|
||||||
|
update_fields.append("username = %s")
|
||||||
|
values.append(request.username)
|
||||||
|
|
||||||
|
if request.email is not None:
|
||||||
|
# 检查邮箱是否已被其他用户使用
|
||||||
|
await cursor.execute("""
|
||||||
|
SELECT id FROM agent_user WHERE email = %s AND id != %s
|
||||||
|
""", (request.email, user_id))
|
||||||
|
if await cursor.fetchone():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="邮箱已被使用"
|
||||||
|
)
|
||||||
|
update_fields.append("email = %s")
|
||||||
|
values.append(request.email)
|
||||||
|
|
||||||
|
if not update_fields:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="No fields to update"
|
||||||
|
)
|
||||||
|
|
||||||
|
update_fields.append("updated_at = NOW()")
|
||||||
|
values.append(user_id)
|
||||||
|
|
||||||
|
await cursor.execute(f"""
|
||||||
|
UPDATE agent_user
|
||||||
|
SET {', '.join(update_fields)}
|
||||||
|
WHERE id = %s
|
||||||
|
RETURNING id, username, email, is_admin, is_active, created_at, last_login
|
||||||
|
""", values)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
return UserListResponse(
|
||||||
|
id=str(row[0]),
|
||||||
|
username=row[1],
|
||||||
|
email=row[2],
|
||||||
|
is_admin=row[3] or False,
|
||||||
|
is_active=row[4],
|
||||||
|
created_at=row[5].isoformat() if row[5] else "",
|
||||||
|
last_login=row[6].isoformat() if row[6] else None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/v1/users/{user_id}", response_model=SuccessResponse)
|
||||||
|
async def delete_user(user_id: str, authorization: Optional[str] = Header(None)):
|
||||||
|
"""
|
||||||
|
删除用户(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户 ID
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SuccessResponse: 删除结果
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 不允许删除默认 admin 用户
|
||||||
|
if user_id == '00000000-0000-0000-0000-000000000001':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Cannot delete default admin user"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("DELETE FROM agent_user WHERE id = %s RETURNING username", (user_id,))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
return SuccessResponse(
|
||||||
|
success=True,
|
||||||
|
message=f"User '{row[0]}' deleted successfully"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/api/v1/users/{user_id}/toggle-admin", response_model=SuccessResponse)
|
||||||
|
async def toggle_user_admin(user_id: str, authorization: Optional[str] = Header(None)):
|
||||||
|
"""
|
||||||
|
切换用户管理员状态(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户 ID
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SuccessResponse: 操作结果
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 不允许取消默认 admin 的管理员权限
|
||||||
|
if user_id == '00000000-0000-0000-0000-000000000001':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Cannot modify default admin user"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("""
|
||||||
|
UPDATE agent_user
|
||||||
|
SET is_admin = NOT is_admin,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = %s
|
||||||
|
RETURNING is_admin, username
|
||||||
|
""", (user_id,))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_status, username = row
|
||||||
|
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
return SuccessResponse(
|
||||||
|
success=True,
|
||||||
|
message=f"User '{username}' is now {'an admin' if new_status else 'a regular user'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/users/{user_id}/reset-password", response_model=SuccessResponse)
|
||||||
|
async def reset_user_password(
|
||||||
|
user_id: str,
|
||||||
|
request: ResetPasswordRequest,
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
重置用户密码(仅管理员)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户 ID
|
||||||
|
request: 重置密码请求
|
||||||
|
authorization: Bearer token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SuccessResponse: 操作结果
|
||||||
|
"""
|
||||||
|
if not await is_admin_user(authorization):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Admin access required"
|
||||||
|
)
|
||||||
|
|
||||||
|
pool = get_db_pool_manager().pool
|
||||||
|
|
||||||
|
async with pool.connection() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
password_hash = hash_password(request.new_password)
|
||||||
|
|
||||||
|
await cursor.execute("""
|
||||||
|
UPDATE agent_user
|
||||||
|
SET password_hash = %s,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = %s
|
||||||
|
RETURNING username
|
||||||
|
""", (password_hash, user_id))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
return SuccessResponse(
|
||||||
|
success=True,
|
||||||
|
message=f"Password reset for user '{row[0]}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/v1/auth/logout", response_model=SuccessResponse)
|
@router.post("/api/v1/auth/logout", response_model=SuccessResponse)
|
||||||
async def user_logout(authorization: Optional[str] = Header(None)):
|
async def user_logout(authorization: Optional[str] = Header(None)):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user