From 23ba66ae81269fe336a95989e8d3ffb60fc3d6ac Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 26 Sep 2025 18:30:01 -0300 Subject: [PATCH] feat(federation): add verifyMatrixIds method and API (#37080) --- apps/meteor/ee/server/api/federation.ts | 40 +++++++++++++++- .../federation-matrix/src/FederationMatrix.ts | 47 +++++++++++++++++++ .../src/types/IFederationMatrixService.ts | 1 + 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/apps/meteor/ee/server/api/federation.ts b/apps/meteor/ee/server/api/federation.ts index 278282ed0ec..e0b99cd8185 100644 --- a/apps/meteor/ee/server/api/federation.ts +++ b/apps/meteor/ee/server/api/federation.ts @@ -1,13 +1,51 @@ import type { IFederationMatrixService } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; +import { ajv } from '@rocket.chat/rest-typings'; import type express from 'express'; import { WebApp } from 'meteor/webapp'; +import { API } from '../../../app/api/server'; import { isRunningMs } from '../../../server/lib/isRunningMs'; const logger = new Logger('FederationRoutes'); -export async function registerFederationRoutes(federationService: IFederationMatrixService): Promise { +let federationService: IFederationMatrixService | undefined; +API.v1.get( + '/federation/matrixIds.verify', + { + authRequired: true, + query: ajv.compile<{ + matrixIds: string[]; + }>({ + type: 'object', + properties: { + matrixIds: { type: 'array', items: { type: 'string' } }, + }, + }), + response: { + 200: ajv.compile<{ + results: { [key: string]: string }; + }>({ + type: 'object', + properties: { + results: { type: 'object', additionalProperties: { type: 'string' } }, + }, + }), + }, + }, + async function () { + const { matrixIds } = this.queryParams; + if (!federationService) { + throw new Error('Federation service not registered'); + } + return API.v1.success({ + results: await federationService.verifyMatrixIds(matrixIds), + }); + }, +); + +export async function registerFederationRoutes(f: IFederationMatrixService): Promise { + federationService = f; if (isRunningMs()) { return; } diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index ea0f33eb5df..b185cc89376 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -944,4 +944,51 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS void this.homeserverServices.edu.sendTypingNotification(room.federation.mrid, userMui, isTyping); } + + async verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }> { + const results = Object.fromEntries( + await Promise.all( + matrixIds.map(async (matrixId) => { + // Split only on the first ':' (after the leading '@') so we keep any port in the homeserver + const separatorIndex = matrixId.indexOf(':', 1); + if (separatorIndex === -1) { + return [matrixId, 'UNABLE_TO_VERIFY']; + } + const userId = matrixId.slice(0, separatorIndex); + const homeserverUrl = matrixId.slice(separatorIndex + 1); + + if (homeserverUrl === this.serverName) { + const user = await Users.findOneByUsername(userId.slice(1)); + return [matrixId, user ? 'VERIFIED' : 'UNVERIFIED']; + } + + if (!homeserverUrl) { + return [matrixId, 'UNABLE_TO_VERIFY']; + } + try { + const result = await this.homeserverServices.request.get< + | { + avatar_url: string; + displayname: string; + } + | { + errcode: string; + error: string; + } + >(homeserverUrl, `/_matrix/federation/v1/query/profile`, { user_id: matrixId }); + + if ('errcode' in result && result.errcode === 'M_NOT_FOUND') { + return [matrixId, 'UNVERIFIED']; + } + + return [matrixId, 'VERIFIED']; + } catch (e) { + return [matrixId, 'UNABLE_TO_VERIFY']; + } + }), + ), + ); + + return results; + } } diff --git a/packages/core-services/src/types/IFederationMatrixService.ts b/packages/core-services/src/types/IFederationMatrixService.ts index 893bca7be5f..c44cb226dd6 100644 --- a/packages/core-services/src/types/IFederationMatrixService.ts +++ b/packages/core-services/src/types/IFederationMatrixService.ts @@ -27,4 +27,5 @@ export interface IFederationMatrixService { ): Promise; inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise; notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise; + verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>; }