From 8519393895bcf3ecbe6380891145ca381467abe9 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:10:36 +0800 Subject: [PATCH 1/3] update: admin valid --- backend/src/backend/admin.py | 30 ++++++++++++++++++- backend/src/backend/platform.py | 12 ++++++++ .../components/admin/UserManagementPage.tsx | 25 ++++++++++++++-- frontend/app/context/AuthContext.tsx | 1 + 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/backend/src/backend/admin.py b/backend/src/backend/admin.py index 9ad2d00..f0ffd5d 100644 --- a/backend/src/backend/admin.py +++ b/backend/src/backend/admin.py @@ -137,6 +137,11 @@ async def create_employee( email=payload.email.strip() if payload.email else None, password_hash=hash_password(payload.password), status="active", + # 不变量: workspace admin == 系统管理员, 因此 role_code == "admin" + # 时必须同步把 platform_role_id 指向 admin role, 否则 + # resolve_is_system_admin 会返回 False, 但前端的 role_code + # 判断仍然命中, 两端出现分歧。 + platform_role_id=role.role_id if role.role_code == "admin" else None, ) session.add(user) session.add( @@ -169,9 +174,32 @@ async def update_employee( user.display_name = payload.display_name.strip() if payload.email is not None: user.email = payload.email.strip() or None - if payload.status is not None: + if payload.status is not None and payload.status != user.status: + if user.user_id == context.user.user_id and payload.status != "active": + raise HTTPException( + status.HTTP_409_CONFLICT, + "不能停用当前登录账号", + ) + if role.role_code == "admin" and payload.status in ("disabled", "locked"): + raise HTTPException( + status.HTTP_409_CONFLICT, + "不能停用或锁定管理员账号", + ) user.status = payload.status if payload.role_code is not None and payload.role_code != role.role_code: + if role.role_code == "admin" and payload.role_code != "admin": + raise HTTPException( + status.HTTP_409_CONFLICT, + "不能降级管理员账号", + ) + if ( + user.user_id == context.user.user_id + and payload.role_code != "admin" + ): + raise HTTPException( + status.HTTP_409_CONFLICT, + "不能降级自身管理员角色", + ) next_role = await session.scalar( select(Roles).where(Roles.role_code == payload.role_code) ) diff --git a/backend/src/backend/platform.py b/backend/src/backend/platform.py index 5bc48cb..6c00d1d 100644 --- a/backend/src/backend/platform.py +++ b/backend/src/backend/platform.py @@ -471,6 +471,12 @@ async def add_member( status.HTTP_409_CONFLICT, "用户已是该 workspace 成员", ) + # 不变量: 给用户授予 workspace admin 时同步设置 platform_role_id, + # 否则前端 role_code === "admin" 与后端 is_system_admin 会给出 + # 不同的结论。降级路径不在此处处理(用户可能在其他 workspace + # 仍是 admin), 升级路径必须在此处理。 + if role.role_code == "admin" and user.platform_role_id is None: + user.platform_role_id = role.role_id membership = WorkspaceMembers( workspace_id=workspace_id, user_id=payload.user_id, @@ -529,6 +535,12 @@ async def update_member( ) next_role = await _load_role_by_code(session, payload.role_code) membership.role_id = next_role.role_id + # 不变量: 升级到 workspace admin 时强制覆盖 platform_role_id。 + # (用户可能之前是 developer, platform_role_id 指向 developer role, + # 现在变成 admin 必须提升到 admin role。) 降级路径不动 (用户 + # 可能在其他 workspace 仍是 admin, 端点看不到全局)。 + if next_role.role_code == "admin": + user.platform_role_id = next_role.role_id if payload.member_status is not None and payload.member_status != membership.member_status: if ( diff --git a/frontend/app/components/admin/UserManagementPage.tsx b/frontend/app/components/admin/UserManagementPage.tsx index fbec786..5c1ddcb 100644 --- a/frontend/app/components/admin/UserManagementPage.tsx +++ b/frontend/app/components/admin/UserManagementPage.tsx @@ -79,6 +79,27 @@ export function UserManagementPage({ onNotify({ tone: "error", message: "请输入密码" }); return; } + // 编辑自身或管理员时,前端二次拦截禁用 status / role_code 的修改 + if (editing) { + const editingSelf = editing.user_id === user?.user_id; + const targetIsAdmin = editing.role_code === "admin"; + if (editingSelf && form.status !== editing.status) { + onNotify({ tone: "error", message: "不能停用当前登录账号" }); + return; + } + if (targetIsAdmin && form.status !== editing.status) { + onNotify({ tone: "error", message: "不能停用或锁定管理员账号" }); + return; + } + if (targetIsAdmin && form.role_code !== editing.role_code) { + onNotify({ tone: "error", message: "不能降级管理员账号" }); + return; + } + if (editingSelf && form.role_code !== editing.role_code) { + onNotify({ tone: "error", message: "不能降级自身管理员角色" }); + return; + } + } // 密码长度校验 8~72 字符 if (form.password && (form.password.length < 8 || form.password.length > 72)) { onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" }); @@ -245,7 +266,7 @@ export function UserManagementPage({