首页
/ Rocket.Chat TOTP 两步验证的调用方迁移:从五个 2fa:* DDP 方法切换到 REST 端点(Batch 5 变更记录解析)

Rocket.Chat TOTP 两步验证的调用方迁移:从五个 2fa:* DDP 方法切换到 REST 端点(Batch 5 变更记录解析)

2026-09-05 21:32:55作者:丁柯新Fawn

本篇基于 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 TwoFactorTOTP account settings page from the five 2fa:* 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.

拆解成三个要点:

  1. 迁移对象是“调用方”:被迁移的是账户设置中的 TwoFactorTOTP 页面(TOTP 两步验证设置页),而非服务端实现本身。服务端五个 REST 端点在另一份变更集 rest-users-totp.md 中已经先行落地,本条 Batch 5 变更负责把前端页面切换到新端点;
  2. 旧方法不删除:五个 2fa:* DDP 方法仍然注册,因为外部 SDK 与移动端客户端还在调用它们;
  3. 废弃有明确期限:服务端会对每次旧方法调用打废弃日志,日志指向新 REST 路由,方法计划随 9.0.0 移除。

.changeset/ 目录中还能看到同系列的姊妹变更(如 ddp-migrate-get-messages-caller.mdddp-migrate-room-history-callers.mdddp-migrate-batch6-audit-callers.mdddp-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:disable2fa:validateTempToken2fa:regenerateCodes2fa: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.enableTotpusers.ts#L2158-L2187):额外声明 twoFactorRequired: truetwoFactorOptions: { 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.enableTotpusers.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:用当前密钥或备用码校验 codeTOTP.verify),成功后调用 Users.disable2FAByUserId 并广播 services.totp.enabled: false 的用户变更事件。
  • validateTotpTempToken:用临时密钥校验 code,通过后调用 Users.enable2FAAndSetSecretAndCodesByUserId 正式启用并生成一批哈希化的备用码,明文 codes 只在此时返回一次。这里还有本迁移中最有安全含金量的逻辑:如果请求携带 authToken,则对该令牌做哈希后调用 Users.removeNonPATLoginTokensExcept删除除当前令牌(及 PAT)之外的所有登录令牌,即强制其他已登录会话下线,防止攻击者在激活 TOTP 后继续用旧会话操作账户;随后通过 notifyOnUserChangeAsync 把新的 loginTokenstotp.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 迁移模式,可以归纳为四步:

  1. 共享业务函数下沉(lib/2fa/functions/totp.ts),两端行为统一;
  2. 新 REST 端点先行上线并加强安全策略(twoFactorRequired、5 次/60s 限流、ajv 响应校验);
  3. 前端调用方切换到 useEndpoint,切断内部流量对旧方法的依赖;
  4. 旧 DDP 方法保留为带 methodDeprecationLogger 的转发层,依靠废弃日志与指标观测外部流量,直到 9.0.0 统一移除。

对需要跟进迁移的 SDK 或移动端开发者而言,操作路径很直接:按本文映射表把 callMethod('2fa:xxx', ...) 替换为对应 REST 调用(注意 disableTotp/validateTotp/regenerateTotpCodes 的 6 位码通过 JSON body 的 code 字段传递),并在客户端留意服务端日志中给出的替代路由提示即可。

登录后查看全文
热门项目推荐
相关项目推荐