Rocket.Chat TOTP 两步验证的调用方迁移:从五个 2fa:* DDP 方法切换到 REST 端点(Batch 5 变更记录解析)
本篇基于 Rocket.Chat 仓库中的变更记录 ddp-migrate-batch5-totp-caller.md 展开,讲解前端账户安全页 TwoFactorTOTP 如何从五个 2fa:* DDP 方法迁移到新的 TOTP REST 端点、旧 DDP 方法如何通过统一废弃机制保留至 9.0.0,以及两端共享的 totp.ts 安全实现细节。读完后你将掌握:新旧接口的完整映射关系、REST 端点的鉴权/限流/响应校验配置、登录令牌轮换等安全设计,以及如何通过废弃日志与测试用例验证迁移的正确性。
变更记录本身:一次“patch 级”的前端调用方迁移
该变更集(Changeset)的 frontmatter 声明这是一次对 @rocket.chat/meteor 包的 patch 级修改,正文一句话概括了变更内容:
Migrates the
TwoFactorTOTPaccount settings page from the five2fa:*DDP methods to the new TOTP REST endpoints. DDP methods stay registered for external SDK/mobile clients with deprecation logs pointing at the new routes until 9.0.0.
拆解成三个要点:
- 迁移对象是“调用方”:被迁移的是账户设置中的
TwoFactorTOTP页面(TOTP 两步验证设置页),而非服务端实现本身。服务端五个 REST 端点在另一份变更集 rest-users-totp.md 中已经先行落地,本条 Batch 5 变更负责把前端页面切换到新端点; - 旧方法不删除:五个
2fa:*DDP 方法仍然注册,因为外部 SDK 与移动端客户端还在调用它们; - 废弃有明确期限:服务端会对每次旧方法调用打废弃日志,日志指向新 REST 路由,方法计划随 9.0.0 移除。
从 .changeset/ 目录中还能看到同系列的姊妹变更(如 ddp-migrate-get-messages-caller.md、ddp-migrate-room-history-callers.md、ddp-migrate-batch6-audit-callers.md、ddp-migrate-batch7-oauth-caller.md),可以推断 Rocket.Chat 正在分批次把前端对 DDP 方法的调用系统性切换到 REST API,本条记录是其中处理 TOTP 两步验证的一批。
新旧接口映射:五个 2fa:* DDP 方法与五个 TOTP REST 端点
五个接口一一对应,映射关系由 rest-users-totp.md 明确列出,并在源码中可逐条印证:
| 功能 | 旧 DDP 方法 | 新 REST 端点 | 旧方法定义文件 |
|---|---|---|---|
| 生成密钥 | 2fa:enable |
POST /v1/users.enableTotp |
enable.ts |
| 关闭两步验证 | 2fa:disable |
POST /v1/users.disableTotp(body 传 code) |
disable.ts |
| 用临时密钥激活并生成备用码 | 2fa:validateTempToken |
POST /v1/users.validateTotp(body 传 code) |
validateTempToken.ts |
| 重新生成备用码 | 2fa:regenerateCodes |
POST /v1/users.regenerateTotpCodes(body 传 code) |
regenerateCodes.ts |
| 查询备用码剩余数量 | 2fa:checkCodesRemaining |
GET /v1/users.totpCodesRemaining |
checkCodesRemaining.ts |
以 2fa:enable 为例,其完整实现只有十几行(enable.ts):
Meteor.methods<ServerMethods>({
async '2fa:enable'() {
methodDeprecationLogger.method('2fa:enable', '9.0.0', '/v1/users.enableTotp');
return enableTotp(Meteor.userId());
},
});
可以看出旧 DDP 方法已经退化成一个“带废弃日志的转发层”:第一行调用 methodDeprecationLogger.method 记录废弃并给出替代路由,第二行直接委托给与 REST 端点共享的 enableTotp 函数(来自 totp.ts)。其余四个方法(2fa:disable、2fa:validateTempToken、2fa:regenerateCodes、2fa:checkCodesRemaining)是完全同构的写法,每个文件都在第一行调用 methodDeprecationLogger.method('<方法名>', '9.0.0', '<对应 REST 路由>')。
值得注意的是 2fa:validateTempToken 的一个细节:DDP 侧通过 this.connection?.httpHeaders 提取 x-auth-token 请求头并传给共享函数,因为 DDP 调用同样可能携带认证头,而 REST 侧则由框架注入(见下文)。
REST 端点侧:鉴权、限流与响应校验
五个端点注册在 users.ts 尾部(约 L2158–L2317),全部要求登录(authRequired: true),全部配置了限流(numRequestsAllowed: 5, intervalTimeInMS: 60000,即 60 秒内最多 5 次请求)。关键配置差异如下:
POST users.enableTotp(users.ts#L2158-L2187):额外声明twoFactorRequired: true与twoFactorOptions: { disableRememberMe: true },即调用方本身必须先通过一次两步验证才能开启 TOTP。响应体用 ajv 校验为{ secret: string, url: string, success: true },其中url是可供扫码器导入的otpauth://链接。POST users.disableTotp:body 为{ code: string }(minLength 1),响应为{ disabled: boolean, success: true }。这里的code是当前的 6 位 TOTP 码或已消费的备用码,用于确认本人操作。POST users.validateTotp:同样带twoFactorRequired: true;body{ code };响应{ codes: string[], success: true },返回新生成的备用码明文列表。实现中显式读取x-auth-token请求头(this.request.headers.get('x-auth-token'))传入共享函数。POST users.regenerateTotpCodes:body{ code },响应{ codes: string[] };共享函数校验失败返回undefined时,端点转换为API.v1.failure('invalid-totp')。GET users.totpCodesRemaining:无 body,响应{ remaining: number }。
twoFactorRequired 的安全意义在 rest-users-totp.md 中有明确解释:users.enableTotp 和 users.validateTotp 要求调用者先完成一次两步验证,是为了封堵“会话被劫持后攻击者注册一个自己控制的 TOTP 设备”的注册旁路——开启/激活新 TOTP 设备前必须先证明账户所有人身份。这也是本批迁移中 REST 端点相对旧 DDP 方法的一个安全性增强点。
共享实现 totp.ts:两端行为一致性的根基
REST 端点与 DDP 方法最终都汇聚到 apps/meteor/server/lib/2fa/functions/totp.ts 中的五个导出函数,这保证了无论走哪条通道,安全语义完全一致:
enableTotp:生成 base32 临时密钥(TOTP.generateSecret()),通过Users.disable2FAAndSetTempSecretByUserId把旧 2FA 关闭并写入services.totp.tempSecret,返回{ secret, url }。若当前已启用 TOTP 会抛error-2fa-already-enabled。disableTotp:用当前密钥或备用码校验code(TOTP.verify),成功后调用Users.disable2FAByUserId并广播services.totp.enabled: false的用户变更事件。validateTotpTempToken:用临时密钥校验code,通过后调用Users.enable2FAAndSetSecretAndCodesByUserId正式启用并生成一批哈希化的备用码,明文codes只在此时返回一次。这里还有本迁移中最有安全含金量的逻辑:如果请求携带authToken,则对该令牌做哈希后调用Users.removeNonPATLoginTokensExcept,删除除当前令牌(及 PAT)之外的所有登录令牌,即强制其他已登录会话下线,防止攻击者在激活 TOTP 后继续用旧会话操作账户;随后通过notifyOnUserChangeAsync把新的loginTokens与totp.enabled状态同步到客户端。regenerateTotpCodes/codesRemainingTotp:前者校验通过后重新生成备用码(update2FABackupCodesByUserId),后者返回services.totp.hashedBackup?.length ?? 0。
废弃机制:methodDeprecationLogger 如何工作
变更集承诺的“deprecation logs pointing at the new routes until 9.0.0”由 deprecationWarningLogger.ts 统一实现:
- 类型
DeprecationLoggerNextPlannedVersion被硬编码为'9.0.0',即当前这一轮所有废弃项的计划移除版本; methodDeprecationLogger.method(method, version, info)会生成形如The method "2fa:enable" is deprecated and will be removed on version 9.0.0 (Use the "/v1/users.enableTotp" endpoint instead)的警告日志,并把替代路由拼接进消息体,方便外部 SDK/移动端开发者定位迁移目标;- 每次触发都会递增 Prometheus 指标
metrics.deprecations/metrics.deprecationsTotal(label 含kind: 'method'与方法名),运维侧可以据此观测各旧方法的实际调用量,为 9.0.0 的删除决策提供数据; - 测试与灰度行为有两处开关:
TEST_MODE=true时废弃调用直接抛错,防止新代码在测试中误触废弃路径;API 端到端套件设置TEST_MODE=api,此时废弃调用只记日志不抛错——因为该套件有意通过/v1/method.call/:method与 streamer 覆盖废弃 DDP 方法;另外环境变量ROCKET_CHAT_DEPRECATION_THROW_ERRORS_FOR_VERSIONS_UNDER可让低于指定版本的废弃项直接抛错。
对外部客户端的实际含义是:在 9.0.0 之前,旧的 2fa:* DDP 方法功能不受影响(返回结构与 DDP 类型声明一致,如 '2fa:enable': () => Promise<{ secret: string; url: string }>),但服务端日志会持续输出指向 REST 路由的迁移指引,服务端可以在发布前据此判断是否仍有存量外部调用。
前端调用方:TwoFactorTOTP 页面已切换到 useEndpoint
本条变更集真正落地的代码在前端 TwoFactorTOTP.tsx:
const enableTotpFn = useEndpoint('POST', '/v1/users.enableTotp');
const checkCodesRemainingFn = useEndpoint('GET', '/v1/users.totpCodesRemaining');
页面通过 useEndpoint 钩子直接调用 REST 端点,而不是 callMethod('2fa:enable') 这类 DDP 调用。这正是变更集标题中 “caller”(调用方)一词的所指:服务端两个通道都已就绪,本批次完成的是把 Rocket.Chat 自家 Web 前端这个最重要的“内部调用方”切走,从而在指标与废弃日志中把剩余流量凸显为真正的外部 SDK/移动端调用。
测试与验证路径
仓库中已存在针对旧 DDP 通道的端到端用例 2fa-enable.ts,位于 API 方法测试目录下;配合 TEST_MODE=api 下“废弃方法只记日志不抛错”的行为(见上文 deprecationWarningLogger.ts 注释说明),API 端到端套件会继续覆盖 2fa:* 旧路径,确保迁移期间两条通道行为不漂移。若需要核对新端点的参数契约,可直接阅读 users.ts 中各端点的 ajv body/response 校验 schema——它们同时也是响应字段的事实定义。
小结
这份 Batch 5 变更记录虽然只有一句话,但背后是一条完整的 DDP→REST 迁移模式,可以归纳为四步:
- 共享业务函数下沉(
lib/2fa/functions/totp.ts),两端行为统一; - 新 REST 端点先行上线并加强安全策略(
twoFactorRequired、5 次/60s 限流、ajv 响应校验); - 前端调用方切换到
useEndpoint,切断内部流量对旧方法的依赖; - 旧 DDP 方法保留为带
methodDeprecationLogger的转发层,依靠废弃日志与指标观测外部流量,直到 9.0.0 统一移除。
对需要跟进迁移的 SDK 或移动端开发者而言,操作路径很直接:按本文映射表把 callMethod('2fa:xxx', ...) 替换为对应 REST 调用(注意 disableTotp/validateTotp/regenerateTotpCodes 的 6 位码通过 JSON body 的 code 字段传递),并在客户端留意服务端日志中给出的替代路由提示即可。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00