update: admin valid

This commit is contained in:
tao.chen
2026-08-05 17:10:36 +08:00
parent 1ef9961307
commit 8519393895
4 changed files with 65 additions and 3 deletions
+29 -1
View File
@@ -137,6 +137,11 @@ async def create_employee(
email=payload.email.strip() if payload.email else None, email=payload.email.strip() if payload.email else None,
password_hash=hash_password(payload.password), password_hash=hash_password(payload.password),
status="active", 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(user)
session.add( session.add(
@@ -169,9 +174,32 @@ async def update_employee(
user.display_name = payload.display_name.strip() user.display_name = payload.display_name.strip()
if payload.email is not None: if payload.email is not None:
user.email = payload.email.strip() or 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 user.status = payload.status
if payload.role_code is not None and payload.role_code != role.role_code: 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( next_role = await session.scalar(
select(Roles).where(Roles.role_code == payload.role_code) select(Roles).where(Roles.role_code == payload.role_code)
) )
+12
View File
@@ -471,6 +471,12 @@ async def add_member(
status.HTTP_409_CONFLICT, status.HTTP_409_CONFLICT,
"用户已是该 workspace 成员", "用户已是该 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( membership = WorkspaceMembers(
workspace_id=workspace_id, workspace_id=workspace_id,
user_id=payload.user_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) next_role = await _load_role_by_code(session, payload.role_code)
membership.role_id = next_role.role_id 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 payload.member_status is not None and payload.member_status != membership.member_status:
if ( if (
@@ -79,6 +79,27 @@ export function UserManagementPage({
onNotify({ tone: "error", message: "请输入密码" }); onNotify({ tone: "error", message: "请输入密码" });
return; 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 字符 // 密码长度校验 8~72 字符
if (form.password && (form.password.length < 8 || form.password.length > 72)) { if (form.password && (form.password.length < 8 || form.password.length > 72)) {
onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" }); onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" });
@@ -245,7 +266,7 @@ export function UserManagementPage({
</label> </label>
<label className="form-field"> <label className="form-field">
<span></span> <span></span>
<select value={form.role_code} onChange={(event) => setForm({ ...form, role_code: event.target.value as "admin" | "developer" })}> <select value={form.role_code} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setForm({ ...form, role_code: event.target.value as "admin" | "developer" })}>
<option value="developer"></option> <option value="developer"></option>
<option value="admin"></option> <option value="admin"></option>
</select> </select>
@@ -253,7 +274,7 @@ export function UserManagementPage({
{editing && ( {editing && (
<label className="form-field"> <label className="form-field">
<span></span> <span></span>
<select value={form.status} onChange={(event) => setForm({ ...form, status: event.target.value as typeof form.status })}> <select value={form.status} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setForm({ ...form, status: event.target.value as typeof form.status })}>
<option value="active"></option> <option value="active"></option>
<option value="disabled"></option> <option value="disabled"></option>
<option value="locked"></option> <option value="locked"></option>
+1
View File
@@ -19,6 +19,7 @@ export type AuthUser = {
email: string | null; email: string | null;
status: string; status: string;
role_code: string | null; role_code: string | null;
is_system_admin: boolean;
}; };
export type AuthWorkspace = { export type AuthWorkspace = {