放宽客户/分享接口权限:bot 主人即可使用,不再要求 is_admin
- _require_admin_user → _require_app_user:允许所有非客户账号调用 - end-user CRUD 按 parent_id 隔离,每个主账号只看到/管理自己的客户 - 子账号自动折叠到主账号 id 上(与 bot 体系一致) - masterkey 仍可看到全部,但不能创建客户账号
This commit is contained in:
parent
680dd02595
commit
05a19f59e9
@ -23,7 +23,6 @@ from agent.db_pool_manager import get_db_pool_manager
|
||||
from routes.bot_manager import (
|
||||
verify_user_auth,
|
||||
is_bot_owner,
|
||||
is_admin_user,
|
||||
hash_password,
|
||||
get_parent_user_id,
|
||||
)
|
||||
@ -93,16 +92,29 @@ class SimpleSuccessResponse(BaseModel):
|
||||
|
||||
# ============== 内部权限辅助 ==============
|
||||
|
||||
async def _require_admin_user(authorization: Optional[str]) -> str:
|
||||
"""要求当前请求是 admin(is_admin=True 或 masterkey),返回 user_id"""
|
||||
async def _require_app_user(authorization: Optional[str]) -> str:
|
||||
"""
|
||||
要求当前请求是任意应用内用户(admin / 普通用户 / 子账号 / masterkey),但**不允许**对外客户账号。
|
||||
子账号会被折叠到主账号 id 上(effective_user_id),便于按"主账号 = 资源所有者"做隔离。
|
||||
返回 effective_user_id(masterkey 时返回 '__masterkey__')。
|
||||
"""
|
||||
valid, user_id, _ = await verify_user_auth(authorization)
|
||||
if not valid or not user_id:
|
||||
raise HTTPException(status_code=401, detail="未登录或 token 无效")
|
||||
if user_id == "__masterkey__":
|
||||
return user_id
|
||||
if not await is_admin_user(authorization):
|
||||
raise HTTPException(status_code=403, detail="仅 admin 可执行此操作")
|
||||
return user_id
|
||||
pool = get_db_pool_manager().pool
|
||||
async with pool.connection() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"SELECT is_end_user FROM agent_user WHERE id = %s",
|
||||
(user_id,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if row and row[0]:
|
||||
raise HTTPException(status_code=403, detail="客户账号不能访问该接口")
|
||||
# 子账号折叠到主账号
|
||||
return await get_parent_user_id(user_id)
|
||||
|
||||
|
||||
async def _require_end_user(authorization: Optional[str]) -> str:
|
||||
@ -131,7 +143,7 @@ async def create_share_token(
|
||||
body: CreateShareTokenRequest,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
user_id = await _require_admin_user(authorization)
|
||||
user_id = await _require_app_user(authorization)
|
||||
if user_id != "__masterkey__" and not await is_bot_owner(bot_id, user_id):
|
||||
raise HTTPException(status_code=403, detail="您不是该 bot 的所有者")
|
||||
|
||||
@ -184,7 +196,7 @@ async def list_share_tokens(
|
||||
bot_id: str,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
user_id = await _require_admin_user(authorization)
|
||||
user_id = await _require_app_user(authorization)
|
||||
if user_id != "__masterkey__" and not await is_bot_owner(bot_id, user_id):
|
||||
raise HTTPException(status_code=403, detail="您不是该 bot 的所有者")
|
||||
|
||||
@ -221,7 +233,7 @@ async def revoke_share_token(
|
||||
share_token_id: str,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
user_id = await _require_admin_user(authorization)
|
||||
user_id = await _require_app_user(authorization)
|
||||
|
||||
pool = get_db_pool_manager().pool
|
||||
async with pool.connection() as conn:
|
||||
@ -245,14 +257,18 @@ async def revoke_share_token(
|
||||
return SimpleSuccessResponse(message="已吊销")
|
||||
|
||||
|
||||
# ============== End-user CRUD(admin 用) ==============
|
||||
# ============== End-user CRUD(bot 主人用) ==============
|
||||
# 客户账号的归属用 agent_user.parent_id 表示(end-user 的 parent_id 指向其主人 user_id)。
|
||||
# 注:子账号也用 parent_id 表示主账号;区分由 is_subaccount / is_end_user 标志位负责。
|
||||
|
||||
@router.post("/api/v1/end-users", response_model=EndUserItem)
|
||||
async def create_end_user(
|
||||
body: CreateEndUserRequest,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
await _require_admin_user(authorization)
|
||||
owner_id = await _require_app_user(authorization)
|
||||
if owner_id == "__masterkey__":
|
||||
raise HTTPException(status_code=400, detail="masterkey 不能拥有客户账号,请用普通账号登录")
|
||||
if not body.username or not body.password:
|
||||
raise HTTPException(status_code=400, detail="username 和 password 必填")
|
||||
|
||||
@ -268,11 +284,11 @@ async def create_end_user(
|
||||
|
||||
await cursor.execute(
|
||||
"""
|
||||
INSERT INTO agent_user (username, email, password_hash, is_admin, is_subaccount, is_end_user)
|
||||
VALUES (%s, %s, %s, FALSE, FALSE, TRUE)
|
||||
INSERT INTO agent_user (username, email, password_hash, is_admin, is_subaccount, is_end_user, parent_id)
|
||||
VALUES (%s, %s, %s, FALSE, FALSE, TRUE, %s)
|
||||
RETURNING id, created_at, is_active
|
||||
""",
|
||||
(body.username, body.email, hash_password(body.password)),
|
||||
(body.username, body.email, hash_password(body.password), owner_id),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
await conn.commit()
|
||||
@ -288,18 +304,29 @@ async def create_end_user(
|
||||
|
||||
@router.get("/api/v1/end-users", response_model=EndUserListResponse)
|
||||
async def list_end_users(authorization: Optional[str] = Header(None)):
|
||||
await _require_admin_user(authorization)
|
||||
owner_id = await _require_app_user(authorization)
|
||||
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_active, created_at, last_login
|
||||
FROM agent_user
|
||||
WHERE is_end_user = TRUE
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
)
|
||||
if owner_id == "__masterkey__":
|
||||
await cursor.execute(
|
||||
"""
|
||||
SELECT id, username, email, is_active, created_at, last_login
|
||||
FROM agent_user
|
||||
WHERE is_end_user = TRUE
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
)
|
||||
else:
|
||||
await cursor.execute(
|
||||
"""
|
||||
SELECT id, username, email, is_active, created_at, last_login
|
||||
FROM agent_user
|
||||
WHERE is_end_user = TRUE AND parent_id = %s
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
(owner_id,),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
items = [
|
||||
EndUserItem(
|
||||
@ -315,24 +342,32 @@ async def list_end_users(authorization: Optional[str] = Header(None)):
|
||||
return EndUserListResponse(items=items)
|
||||
|
||||
|
||||
async def _ensure_owns_end_user(cursor, end_user_id: str, owner_id: str):
|
||||
"""校验 end_user_id 存在且归属当前 owner_id(masterkey 通过)"""
|
||||
await cursor.execute(
|
||||
"SELECT parent_id FROM agent_user WHERE id = %s AND is_end_user = TRUE",
|
||||
(end_user_id,),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="客户账号不存在")
|
||||
if owner_id != "__masterkey__" and str(row[0]) != owner_id:
|
||||
raise HTTPException(status_code=403, detail="您不是该客户账号的所有者")
|
||||
|
||||
|
||||
@router.post("/api/v1/end-users/{end_user_id}/reset-password", response_model=SimpleSuccessResponse)
|
||||
async def reset_end_user_password(
|
||||
end_user_id: str,
|
||||
body: ResetEndUserPasswordRequest,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
await _require_admin_user(authorization)
|
||||
owner_id = await _require_app_user(authorization)
|
||||
if not body.password:
|
||||
raise HTTPException(status_code=400, detail="password 必填")
|
||||
pool = get_db_pool_manager().pool
|
||||
async with pool.connection() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"SELECT id FROM agent_user WHERE id = %s AND is_end_user = TRUE",
|
||||
(end_user_id,),
|
||||
)
|
||||
if not await cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="客户账号不存在")
|
||||
await _ensure_owns_end_user(cursor, end_user_id, owner_id)
|
||||
|
||||
await cursor.execute(
|
||||
"UPDATE agent_user SET password_hash = %s WHERE id = %s",
|
||||
@ -352,10 +387,11 @@ async def delete_end_user(
|
||||
end_user_id: str,
|
||||
authorization: Optional[str] = Header(None),
|
||||
):
|
||||
await _require_admin_user(authorization)
|
||||
owner_id = await _require_app_user(authorization)
|
||||
pool = get_db_pool_manager().pool
|
||||
async with pool.connection() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await _ensure_owns_end_user(cursor, end_user_id, owner_id)
|
||||
await cursor.execute(
|
||||
"DELETE FROM agent_user WHERE id = %s AND is_end_user = TRUE",
|
||||
(end_user_id,),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user