首页
/ Rocket.Chat 房间封禁(Ban)与解封(Unban)机制全解

Rocket.Chat 房间封禁(Ban)与解封(Unban)机制全解

2026-09-08 20:45:17作者:殷蕙予

被封禁(Banned)用户无法以任何途径重新进入房间,除非管理员显式解封——这是 Rocket.Chat 房间治理(Room Moderation)中最严格的准入控制手段。本文将围绕 docs/features/ban-user.md 中定义的封禁/解封流程,结合 apps/meteor/server/libserver/lib/rooms 下的真实实现与 server/api/v1/rooms.ts 中的 REST 路由,逐层拆解从权限校验、底层数据变更到联邦(Matrix Federation)回调的完整链路,并给出可直接复用的 REST API 与斜杠命令示例。读完你将能精确回答"封禁到底删了什么、解封后为什么还需要重新邀请"以及"被封禁用户为何所有入口都被拦截"等关键问题。

概述:封禁(Ban)与踢出(Kick)的本质区别

在 Rocket.Chat 中,房间踢人(kick)会直接删除订阅记录,用户可随时重新加入;而封禁(ban)保留订阅记录,但把记录状态置为 BANNED。这两种操作在 ISubscription.ts 的类型定义中可见:

export type SubscriptionStatus = 'INVITED' | 'BANNED';

status: 'BANNED' 形成一道持久化的访问屏障——它既不是一个"已删除"的订阅(可用于被管理员查询到谁被封禁过),又是一条"无效"订阅(无法通过任何准入校验)。此外封禁还能作为联邦网络中的传播信号,这是踢人所不具备的。

权限上,只有持有 ban-user 权限的角色(默认涵盖 admin、房间 ownermoderator)才能发起封禁。封禁的入口共有三个:

  • UI:房间内用户信息面板中的 Ban 操作;
  • REST APIPOST /v1/rooms.banUser
  • 斜杠命令/ban @username

封禁流程(Ban Flow):从权限校验到数据变更

封禁动作在服务端由两条核心函数配合完成:

1. 校验层 banUserFromRoomMethod 的完整检查链

校验函数接收执行者 fromId 与目标 { rid, username },依次执行:

  1. 权限校验hasPermissionAsync(fromId, 'ban-user', data.rid),权限须作用域于目标房间;
  2. 房间类型是否允许:通过 roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.BAN, fromId) 判断该类型房间是否允许 Ban 动作(不同类型的房间可通过房间协调器配置差异化规则);
  3. 执行者房间访问权canAccessRoomAsync(room, fromUser),防止越权操作不在自己房间内的用户;
  4. 目标用户存在性Users.findOneByUsernameIgnoringCase(data.username)
  5. 目标是否在房间中:查询 Subscriptions.findOneByRoomIdAndUserId,若不存在订阅则抛出 'User is not in this room'
  6. 是否已处于封禁态:通过 isBannedSubscription(subscription) 判断,若已封禁则拒绝(避免重复封禁);
  7. 最后一个 owner 保护:若目标是 owner,使用 Roles.countUsersInRole('owner', room._id) 统计房间 owner 数量,若仅剩 1 人则拒绝,抛出 'You are the last owner. Please set new owner before banning the user.'

校验层 server/lib/banUserFromRoom.ts 顶部导出的方法实际调用了执行层 ./rooms/banUserFromRoom,而后者会向联邦发送回调;tests/unit/server/lib/banUserFromRoom.spec.tstests/unit/server/lib/rooms/banUserFromRoom.spec.ts 中提供了对这两层函数的关键路径覆盖。

2. 执行层 performUserBan 的六步数据库操作

校验通过后,performUserBan(在 server/lib/rooms/banUserFromRoom.ts)负责真正的数据变更:

步骤 操作 底层调用 说明
1 订阅状态置为 BANNED Subscriptions.banByRoomIdAndUserId 只改 status保留记录(与 kick 删除记录形成对比)
2 从成员列表剔除 Users.removeRoomByUserId 把该房间从用户的 __rooms 数组移除,成员列表不再显示
3 递减房间人数 Rooms.incUsersCountById(room._id, -1) 保持 usersCount 与成员一致
4 移除房间级角色 removeUserFromRolesAsync(user._id, ['moderator', 'owner', 'leader'], room._id) 仅对频道/私聊群(room.t'c'/'p')执行
5 团队主房间特殊处理 Team.removeMember(room.teamId, user._id) 若房间是团队主房间,同步移除团队成员,保证 roster 一致
6 系统消息 + 客户端通知 Message.saveSystemMessage('user-banned', ...)notifyOnSubscriptionChanged(subscription, 'removed') 服务端落一条 user-banned 消息;向客户端推送 removed 事件,让客户端断开该房间的数据流/订阅

封禁发起人(byUser)的信息会被写入系统消息的 u 字段,保证审计可追溯。

3. 本地动作与联邦传播的回调拆分

执行层区分了两个函数:

  • performUserBan:仅执行必要的数据库操作,不触发回调。它被注释为 "Executes only the necessary database operations, with no callbacks, to prevent propagation loops",专门用于联邦或其他外部事件驱动的封禁,避免外部事件处理时的回调传播死循环。
  • banUserFromRoom:在调用 performUserBan 之后执行 afterBanFromRoomCallback.run({ bannedUser, userWhoBanned }, room),用于本地动作(UI/API),让封禁正常地向联邦与订阅方传播。

这一设计在 Matrix 联邦场景下意义重大:本地封禁通过 afterBanFromRoom 回调向联邦网络广播,而对端回传的事件则走 performUserBan 的"无回调"路径,从源码结构上规避了双向传播的乒乓效应。

解封流程(Unban Flow):删除记录而非恢复成员身份

解封入口同样有三个:UI 的 "Banned Users" 上下文栏、POST /v1/rooms.unbanUser、斜杠命令 /unban @username

核心实现位于 server/lib/rooms/executeUnbanUserFromRoom.ts,其流程为:

  1. 通过 Subscriptions.findOneByRoomIdAndUserId(rid, user._id) 定位订阅;
  2. 分支一(invite 态):若订阅处于 INVITED 态(即"先解封又被邀请、邀请被接受后又收到 leave 事件"这一特殊时序),只补发 user-unbanned 系统消息后直接返回;
  3. 分支二(普通封禁态):若订阅不存在则报 error-invalid-subscription;若存在但不是 BANNED 态则报 error-user-not-banned
  4. 真正执行:Subscriptions.removeById(subscription._id) —— 将封禁订阅整个删除
  5. 保存 user-unbanned 系统消息;发送 removed 通知并触发房间变更通知;
  6. 回调:afterUnbanFromRoomCallback.run(...)(供联邦传播解封)。

需要特别强调的是:解封 ≠ 恢复成员身份。由于封禁时已移除 __rooms 与递减 usersCount,解封只做一件事——删除那条 BANNED 订阅,房间人数与成员列表并不会因此自动恢复。因此解封后用户依然不是房间成员,必须由管理员重新邀请(或用户自行加入公开房间)

禁止再入机制:被封禁用户的所有入口都被拦截

Rocket.Chat 对被封禁用户实行"全方位拦截",无论普通房间还是联邦房间都无法绕过。各入口的拦截点如下:

邀请(API/UI:groups.invitechannels.invite、Add Users)

底层方法 addUsersToRoom 在调用 addUserToRoom 之前先检查是否存在 BANNED 订阅:存在则直接拒绝并返回 error-user-is-banned。该检查位于方法层、处于房间类型分支之前,因此对普通房间与联邦房间一视同仁。UI 侧会弹出警告模态框,提示管理员先解封。

邀请链接(useInviteToken

useInviteToken 在保存 invite token 或调用 addUserToRoom 之前同样执行 BANNED 检查:命中则返回 error-user-is-banned邀请 token 不会被消耗。由于检查发生在 Users.updateInviteToken 之前,经由邀请链接注册后 setUsername 的二次路径同样被封死,没有绕过缝隙。

直接加入(channels.joinjoinRoom

Room.join 在调用 addUserToRoom 之前会先走 canAccessRoom

  • 公开房间 / 团队内公开房间canAccessRoom 校验器显式检查 findOneBannedSubscription,命中即拒绝访问;
  • 私密房间countByRoomIdAndUserId 的查询条件排除了 BANNED 订阅(例如仅匹配 status: { $exists: false } 或特定状态的记录),因此"已是成员"的校验返回 false,访问被拒。

无论公开还是私密,被封禁者都没有"有效订阅",因此准入逻辑全部归于失败。

联邦邀请事件

当 Matrix 联邦 homeserver 向一个本地已被封禁的用户发送邀请时,事件处理位于 apps/ee/packages/federation-matrix/src/events/member.tshandleInvite:它会发现已存在(被封禁的)订阅并直接提前返回,不会新建订阅。用户因此永远不会得到 INVITED 订阅,后续的 handleJoin 永远不会被触达。

推荐的"解封后再入"标准流程

  1. 解封:通过 POST /v1/rooms.unbanUser/unban @username 或 "Banned Users" 上下文栏删除 BANNED 订阅;
  2. 邀请或加入:解封后即可通过 API/UI/邀请链接重新邀请,公开房间也可正常自行加入。

REST 端点与斜杠命令

REST API

文档定义并可由 server/api/v1/rooms.ts 实现佐证的相关端点为:

方法 端点 描述 请求体 / 查询参数
POST /v1/rooms.banUser 封禁用户 body:roomId + userIdusername;需认证
POST /v1/rooms.unbanUser 解封用户 body:roomId + userIdusername;需认证
GET /v1/rooms.bannedUsers 列出被封禁用户(分页) query:roomIdoffsetcount;需认证

请求与响应均通过 ajv 编译的 schema 做运行时校验(如 isRoomsBanUserPropsisRoomsUnbanUserPropsroomsBannedUsersResponseSchema),约束 success/bannedUsers/count/offset/total 等字段。rooms.banUserrooms.unbanUser 均要求调用者已认证(authRequired: true),目标用户会通过 getUserFromParams 从 body 中解析。

以封禁为例的调用形态:

POST /api/v1/rooms.banUser
Content-Type: application/json
X-Auth-Token: <token>
X-User-Id: <userId>

{
  "roomId": "GENERAL_ROOM_ID",
  "username": "offender"
}

成功返回 { "success": true };失败可能返回 error-invalid-roomerror-user-not-bannederror-invalid-subscription 或 401 等。

rooms.bannedUsers 的实现展示了其数据来源:它通过 Subscriptions.findPaginated({ rid: roomId, status: 'BANNED' }, { sort: { ts: 1 }, ... }) 分页查询 BANNED 订阅,再按 u._id 批量反查用户 usernamename 后返回,且列表按封禁时间正序排列。UI 侧正是借助该端点实现 "Banned Users" 列表的无限滚动分页。

斜杠命令

对应实现位于 server/slashcommands/ban/ban.tsserver/slashcommands/ban/unban.ts,客户端注册在 client/startup/slashCommands/ban.ts

/ban @username        # 在房间内封禁指定用户
/unban @username      # 在房间内解封指定用户

UI 交互与客户端实现

UI 侧的封禁/解封与文档中列出的 "Banned Users" 功能一一对应:

系统消息与消息流

整个流程会产生两类系统消息,作为审计与前端展示的依据:

key 触发时机
user-banned 用户被从房间封禁时
user-unbanned 用户被解封时(包括"解封后重新加入/被添加"触发的同步场景)

关键文件速查

层次 文件
API 路由 server/api/v1/rooms.ts
校验与权限 server/lib/banUserFromRoom.ts
核心封禁逻辑 server/lib/rooms/banUserFromRoom.ts
核心解封逻辑 server/lib/rooms/executeUnbanUserFromRoom.ts
解封服务端入口 server/lib/unbanUserFromRoom.ts
斜杠命令 server/slashcommands/ban/ban.tsserver/slashcommands/ban/unban.ts
联邦解封/封禁回调 server/lib/callbacks/afterUnbanFromRoomCallback.ts
客户端封禁 Hook client/views/room/hooks/useBanUser.tsx
客户端解封 Hook client/views/room/hooks/useUnbanUser.tsx
Ban 动作(用户信息) client/views/room/hooks/useUserInfoActions/actions/useBanUserAction.tsx
Banned Users UI client/views/room/contextualBar/BannedUsers/
订阅类型定义 packages/core-typings/src/ISubscription.ts
REST 类型定义 packages/rest-typings/src/v1/rooms.ts
订阅模型类型 packages/model-typings/src/models/ISubscriptionsModel.ts
单元测试 tests/unit/server/lib/banUserFromRoom.spec.tstests/unit/server/lib/rooms/banUserFromRoom.spec.ts

小结:记住五个关键点

  1. 封禁保留订阅并将 status 置为 BANNED;踢人是删除订阅——二者本质不同;
  2. 封禁会同步移除 __rooms 记录、递减 usersCount、清理房间角色,并在团队主房间场景同步移出团队;
  3. 解封只是删除 BANNED 订阅,不会恢复成员身份,必须重新邀请/加入;
  4. 被封禁用户的每一条入室路径(邀请、邀请链接、直接加入、联邦邀请)都在方法层或 canAccessRoom 校验层被拦截,不存在绕过;
  5. 本地动作会触发 afterBanFromRoom / afterUnbanFromRoom 回调向 Matrix 联邦传播,而外部事件驱动的操作走无回调的 performUserBan 路径,从结构上避免传播循环。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393