☰
Kener 用户管理与 RBAC 权限体系:角色、权限与邀请流程实战指南
2026/10/13 3:12:35 网站建设 项目流程
  • 可观测性
  • 后端
  • 前端
  • 运维

【免费下载链接】kener

Stunning status pages, batteries included!

项目地址:https://gitcode.com/gh_mirrors/ke/kener
点击查看免费下载

本篇基于 Kener 官方文档 user-management.md/docs/content/v4/user-management.md) 展开,系统讲解 Kener 后台(Manage → Users / Manage → Roles)的用户管理全貌:基于 RBAC 的角色与权限模型、内置与自定义角色的差异、domain.action格式的权限域划分、两层权限执行机制(路由级 + 动作级),以及完整的邀请、激活与验证邮件流程。读完后你将能够:为团队成员正确分配admin/editor/member或自定义角色、创建符合命名规范的自定义角色并精细授权、理解邀请 token 的生命周期与激活链路,并结合源码定位权限被拒(403)的具体执行点。

一、RBAC 数据模型:四张表构成权限骨架

Kener 采用基于角色的访问控制(RBAC)系统:每个用户可被分配一个或多个角色,每个角色拥有一组权限,权限决定用户能执行哪些操作。从迁移文件 20260330120000_add_rbac_tables.ts 可以看到,整个体系由四张表构成:

表名作用关键字段
roles角色定义id(角色 ID)、role_name、readonly(内置角色标记)、status(默认ACTIVE)
permissions权限定义id(即domain.action字符串)、permission_name(展示名)
roles_permissions角色 ↔ 权限多对多roles_id、permissions_id、status,复合主键
users_roles用户 ↔ 角色多对多roles_id、users_id,复合主键,并对users_id建了idx_users_roles_users_id索引

两个多对多连接表都使用onDelete("CASCADE"),这意味着删除角色时其权限映射与用户分配会被级联清理——后文删除自定义角色的“迁移/移除”逻辑正建立在这一级联行为之上。

权限与角色的初始数据由种子脚本写入:

  • seeds/permissions.ts:从 src/lib/allPerms.ts 的permissions数组同步权限表,采用“插入缺失项 + 删除不在清单中的项”的双向对齐策略,保证数据库权限集合与代码定义始终一致;
  • seeds/roles.ts:注入三个只读角色(admin、editor、member),按角色分配权限,并把旧版users.role单值字段迁移到users_roles多对多关系。

readonly字段是“内置角色不可编辑/删除”这一规则的底层来源,后文的控制器代码会反复校验该标志。

二、内置角色(Built-in roles)

三个只读角色由种子自动创建(见 seeds/roles.ts 中的readonlyRoles与rolePermissions映射):

RolePermissionsNotes
admin全部权限完全访问,包含api_keys.delete
editor除api_keys.delete外的全部权限日常运营角色
member仅全部.read权限只读访问

从源码结构看,editor的计算方式是allPermissionIds.filter((id) => id !== "api_keys.delete"),member是id.endsWith(".read")过滤——即“API key 的删除权”被刻意收敛为仅admin可用,源码注释也标注了 “AdminCan-only”。内置角色readonly = 1,不能编辑、停用或删除:UpdateRole/DeleteRole/UpdateRolePermissions在 userController.ts 中都会先检查existing.readonly === 1并抛出对应错误(如 "Readonly roles cannot be deleted")。

三、自定义角色(Custom roles)

拥有roles.write权限的用户可以从Manage → Roles创建自定义角色,操作步骤:

  1. 点击Create Role;
  2. 输入角色 ID(仅允许小写字母、数字、下划线、连字符)和显示名;
  3. 可选:从已有角色克隆权限;
  4. 创建后在Permissions面板中逐项分配权限。

角色 ID 的合法性在CreateRole(userController.ts)中做了三层校验:

  • 归一化:id.trim().toLowerCase().replace(/\s+/g, "_"),空格会被替换为下划线;
  • 正则约束:ROLE_ID_REGEX = /^[a-z0-9_-]+$/,不满足则报 "Role ID can only contain lowercase letters, numbers, underscores, and hyphens";
  • 保留字保护:RESTRICTED_ROLE_IDS = ["admin", "editor", "member"],自定义角色禁止使用这三个 ID,防止与内置角色冲突。

此外还会做重名检查("Role with ID ... already exists")。

自定义角色支持编辑、停用(status只能取ACTIVE/INACTIVE)和删除。删除时(DeleteRole,userController.ts)二选一:

  • migrate:把该角色下的用户迁移到另一个目标角色;目标角色必须存在且为ACTIVE("Cannot migrate users to an inactive role"),且不能与被删角色相同;
  • remove:直接解除用户分配。

删除完成后,users_roles与roles_permissions由外键 CASCADE 自动清理。

四、权限域(Permission domains)

所有权限遵循domain.action格式,完整清单定义在 src/lib/allPerms.ts 的permissions数组中,共 13 个域、29 项权限:

DomainActions
monitorsread,write
incidentsread,write
maintenancesread,write
pagesread,write
triggersread,write
alertsread,write
api_keysread,write,delete
usersread,write
settingsread,write
subscribersread,write
email_templatesread,write
imageswrite
rolesread,write,assign_permissions,assign_users

几个值得注意的细节:

  • api_keys域比其他域多出delete动作,形成三级粒度(读 / 写 / 删),这是整个权限体系中最细的一处拆分;
  • images域只有write(上传与删除图片),没有单独的read;
  • roles域额外拆出assign_permissions与assign_users,使“管权限”与“管人的角色分配”可以分离授权。

allPerms.ts文件头的注释还给出了每个权限对应的具体 API action 映射(ACTION_PERMISSION_MAP),例如monitors.write覆盖storeMonitorData、updateMonitoringData、deleteMonitor、cloneMonitor、testMonitor、listDockerContainers;users.write覆盖manualUpdate、createNewUser、resendInvitation、sendVerificationEmail。而updateUser、updatePassword映射为null,表示这些“自操作”只需登录即可,无需额外权限。

五、双层权限执行:路由级 + 动作级

文档明确写道:权限在路由级(页面访问)和动作级(API 操作)两处强制执行。源码中这两处执行点非常清晰:

5.1 路由级(页面访问)

管理区布局加载函数 +layout.server.ts/+layout.server.ts) 在每次进入 manage 路由时:

  1. 校验 setup 是否完成、登录会话是否有效(未登录则重定向到/account/signin);
  2. 通过GetUserPermissions(loggedInUser.id)取出用户全部权限;
  3. 查ROUTE_PERMISSION_MAP[route.id]:若路由不在映射表中直接抛 403;若要求某权限而用户没有,同样抛error(403, "Forbidden")。

ROUTE_PERMISSION_MAP(allPerms.ts)覆盖了 monitors、incidents、maintenances、pages、triggers、alerts、api-keys、users、settings 各配置页、subscriptions、email templates、roles、oidc 等全部后台页面,统一映射到对应的*.read权限——即“进得了页面”的前提是拥有该域的读权限。布局函数同时把userPermissions数组下发给前端组件,供按钮/菜单按权限显隐。

5.2 动作级(API 操作)

后台所有变更操作都汇聚到单一入口 manage/api/+server.ts/manage/api/+server.ts) 的POSThandler,其权限检查流程(第 144–169 行附近):

let userDB = await GetLoggedInSession(cookies); if (!userDB) { return json({ error: "User not logged in" }, { status: 401 }); } // Fetch user permissions once for the entire request const userPermissions = await GetUserPermissions(userDB.id); // Check permission for the action const requiredPermission = ACTION_PERMISSION_MAP[action]; if (requiredPermission === undefined) { return json({ error: "Unknown action" }, { status: 400 }); } if (requiredPermission !== null) { try { RequirePermission(userPermissions, requiredPermission); } catch { return json({ error: "You do not have permission to perform this action" }, { status: 403 }); } }

要点:每个请求只查一次用户权限(GetUserPermissions返回Set<string>);ACTION_PERMISSION_MAP查不到的 action 直接 400(防止未知 action 被误放行);null表示免权限的自操作;校验失败统一 403。RequirePermission本身(userController.ts)只是一次Set.has判断,抛错即被上层捕获转换为 403 响应。

会话层面还有一道前置闸:GetLoggedInSession会校验 cookie token、并检查userDB.is_active——被停用的用户即便 token 未过期也无法通过会话校验,这与文档中“停用用户会使既有会话失效”的描述一致。

六、管理角色权限与角色用户

  • Permissions 面板:在角色表中点击任意角色的Permissions即可查看或编辑其权限;权限按域分组、可逐项开关。操作走updateRolePermissionsaction,由UpdateRolePermissions实现(userController.ts):先做只读角色检查,再对“期望集合”与“当前集合”做差集,分别调用addRolePermission/removeRolePermission增量落库。只读(内置)角色在此面板中呈只读模式。
  • Users 面板:点击任意角色的Users查看已分配用户;拥有roles.assign_users权限的用户可增删成员。AddUserToRole会拒绝向INACTIVE角色添加用户,并防止重复分配;RemoveUserFromRole有一条例外保护:owner 不能被移出admin角色("The owner cannot be removed from the admin role")。

七、用户管理(User management)

拥有users.write权限的用户可以:

  • 邀请新用户(invite new users)
  • 重发邀请邮件(resend invitation emails)
  • 更新用户角色(update user roles)
  • 激活/停用用户(activate/deactivate users)
  • 发送验证邮件(send verification emails)

Owner 专属限制(由 userController.ts 的ManualUpdateUserData与首个用户创建逻辑保证):

  • owner 必须始终保留admin角色:updateType == "role"时若目标用户is_owner === "YES"且role_ids不含admin,直接抛错 "Owner must retain the admin role";
  • owner 账号不能被停用:updateType == "is_active"且is_active === 0时,对 owner 抛错 "Owner account cannot be deactivated"。

owner 标志在首个用户创建时写入:CreateFirstUser固定赋予role_ids: ["admin"]与is_owner: "YES"(userController.ts)。另外,修改角色时对每个role_id都会做存在性与ACTIVE状态双重校验——这是“所有被选角色必须处于激活状态”这一约束的代码来源。

八、邀请流程(Invite flow)

重要前提:必须先在后台配置好 Email,否则邀请流程不可用(见文末依赖章节)。

操作路径为Manage → Users:

  1. 点击Add User;
  2. 输入姓名、邮箱,勾选一个或多个角色;
  3. 系统创建用户并发送携带安全 token 链接的邀请邮件。

SendInvitationEmail(userController.ts)的实际行为与文档描述逐条对应:

  • 被邀请者以未激活账号 + 空密码创建:db.insertUser({ ..., password_hash: "", is_active: 0 });
  • 邀请 token 7 天过期:GenerateToken({ email, validTill: Date.now() + 7 * 24 * 60 * 60 * 1000 }),链接格式为{site_url}account/invitation?view=confirm_token&token={token};
  • 所有选定角色必须存在且 ACTIVE,否则报错(如Role "xxx" is not active);
  • 邮箱重复会抛 "A user with email ... already exists",数据库层的 UNIQUE 约束冲突也被捕获并转换为同一友好错误。

邮件正文使用invite_user通用模板(可在后台Email Templates中自定义),模板缺失时不发邮件(静默跳过)。

九、用户如何接受邀请(Accept invitation)

被邀请者打开邀请链接后,前端页面提交到 accept-invitation API/account/invitation/api/accept-invitation/+server.ts),服务端按顺序执行四道检查:

  1. Token 校验:VerifyToken(receivedToken)失败即返回 "Invalid or expired invitation link";
  2. 过期检查:Date.now() > tokenData.validTill返回 "This invitation link has expired";
  3. 防重复激活:查询用户密码哈希,若非空说明密码已设置,返回 "This invitation has already been accepted"(这正是“链接已使用则无法再从该链接激活”的实现);
  4. 密码强度校验:至少 8 位,且包含大写、小写字母和数字。

全部通过后:写入密码哈希 →updateUserIsActive(user.id, 1)激活账号 →updateIsVerified(user.id, 1)标记邮箱已验证。被邀请者随后即可用正常方式登录。

十、验证邮件(Verification emails)

  • 拥有users.write权限的用户可以给其他用户发送验证邮件;
  • 任何用户都可以为自己的未验证账号触发验证(无需额外权限)。

这一“自己 vs 他人”的差异化逻辑在 manage/api/+server.ts/manage/api/+server.ts) 的sendVerificationEmail分支中实现:当toId !== userDB.id时才额外检查users.write。SendVerificationEmail(userController.ts)会先确认目标用户未验证("User email is already verified"),生成的验证 token 有效期为24 小时(区别于邀请的 7 天),链接指向{site_url}account/verify?view=confirm_token&token={token},使用verify_email模板——该模板缺失时会直接抛错。

十一、常见任务与 UI 行为

常见任务

  • 修改用户角色:打开用户设置 sheet,勾选/取消角色,点击Update Roles;用户可以同时拥有多个角色。
  • 停用用户:在用户设置 sheet 中把账号切换为 inactive。由于会话校验依赖is_active(见GetLoggedInSession),停用后该用户的既有登录会话立即失效。
  • 重新邀请用户:对尚未设置密码的用户重发邀请。ResendInvitationEmail会先检查用户是否存在、密码哈希是否为空(已设密码则报 "User has already set their password"),然后生成新的 7 天 token 并走invite_user模板发信。

UI 行为细节(源自 users/+page.svelte/manage/app/users/+page.svelte))

  • 当前登录用户在用户表格中会被高亮显示(组件以currentUser与行数据比对);
  • 用户表格支持按 active/inactive 状态过滤,请求参数is_active会传给分页查询getUsers;
  • 角色徽章(Badge)展示用户已分配的角色 ID 列表;
  • 页面级权限按钮(邀请、重发邀请、发验证邮件等)依据userPermissions数组做前端显隐,后端动作级检查仍是最终防线。

十二、前提与依赖

以下功能依赖 Email 配置,未配置前邀请与验证流程不可用:

  • 邀请用户(inviting users)
  • 重发邀请邮件(resending invitation emails)
  • 验证邮件(verification emails)

Email 相关配置参见 email-setup.md/docs/content/v4/setup/email-setup.md)。

相关页面

  • 站点配置(Site Configuration)/docs/content/v4/setup/site-configuration.md)
  • 用户订阅(User Subscriptions)/docs/content/v4/subscriptions.md)

关键源码索引

文件说明
migrations/20260330120000_add_rbac_tables.tsRBAC 四表建表迁移
src/lib/allPerms.ts权限清单、action→权限映射、路由→权限映射
seeds/roles.ts / seeds/permissions.ts内置角色与权限种子
src/lib/server/controllers/userController.ts角色 CRUD、邀请/验证邮件、owner 保护逻辑
src/routes/(manage)/manage/api/+server.ts/manage/api/+server.ts)动作级权限执行点
src/routes/(manage)/+layout.server.ts/+layout.server.ts)路由级权限执行点
src/routes/(account)/account/invitation/api/accept-invitation/+server.ts/account/invitation/api/accept-invitation/+server.ts)邀请接受与激活链路
  • 可观测性
  • 后端
  • 前端
  • 运维

【免费下载链接】kener

Stunning status pages, batteries included!

项目地址:https://gitcode.com/gh_mirrors/ke/kener
点击查看免费下载

相关推荐

上一篇:HyperAgents双智能体架构全解:MetaAgent与TaskAgent如何协同实现AI自我进化
下一篇:Paper Search MCP安装完整指南:7种方式覆盖Smithery、uvx、Docker与macOS本地

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询