From 52b0bb242b364dc9fcd2ead0a3c0795d56dc6ebe Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Sun, 23 Nov 2025 23:00:28 -0300 Subject: [PATCH 01/13] feat: add improved errors messaging --- .../app/lib/server/functions/createRoom.ts | 23 +++++++ .../ee/server/hooks/federation/index.ts | 35 +++++++++- .../federation-matrix/src/FederationMatrix.ts | 64 ++++++++++++++----- .../middlewares/isFederationDomainAllowed.ts | 40 +++++++++++- ee/packages/federation-matrix/src/index.ts | 4 +- ee/packages/federation-matrix/src/setup.ts | 6 ++ 6 files changed, 152 insertions(+), 20 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index bb99ae7237f19..c3b3b2c42ae6c 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -3,6 +3,7 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti import { FederationMatrix, Message, Room, Team } from '@rocket.chat/core-services'; import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; +import { isFederationDomainAllowedFromUsernames, FederationValidationError } from '@rocket.chat/federation-matrix'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -190,6 +191,28 @@ export const createRoom = async ( }); } + if (shouldBeHandledByFederation && onlyUsernames(members)) { + // check RC allowlist for domain + const isAllowed = await isFederationDomainAllowedFromUsernames(members); + if (!isAllowed) { + throw new Meteor.Error( + 'federation-policy-denied', + "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", + { method: 'createRoom' }, + ); + } + + // validate external users (network + user existence checks) + try { + await FederationMatrix.validateFederatedUsersBeforeRoomCreation(members); + } catch (error) { + if (error instanceof FederationValidationError) { + throw new Meteor.Error(error.error, error.userMessage, { method: 'createRoom' }); + } + throw error; + } + } + if (type === 'd') { return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || owner?._id }); } diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index fa2433a9aa424..f1d0d450c2f4a 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -2,6 +2,7 @@ import { FederationMatrix, Authorization, MeteorError, Room } from '@rocket.chat import { isEditedMessage, isRoomNativeFederated, isUserNativeFederated } from '@rocket.chat/core-typings'; import type { IRoomNativeFederated, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import { validateFederatedUsername } from '@rocket.chat/federation-matrix'; +import { isFederationDomainAllowedFromUsernames, FederationValidationError } from '@rocket.chat/federation-matrix'; import { Rooms } from '@rocket.chat/models'; import { callbacks } from '../../../../server/lib/callbacks'; @@ -108,8 +109,20 @@ beforeAddUserToRoom.add( return; } - if (!FederationActions.shouldPerformFederationAction(room)) { - return; + if (FederationActions.shouldPerformFederationAction(room)) { + if (!(await Authorization.hasPermission(user._id, 'access-federation'))) { + throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation'); + } + + const isAllowed = await isFederationDomainAllowedFromUsernames([user.username]); + if (!isAllowed) { + throw new MeteorError( + 'federation-policy-denied', + "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", + ); + } + + await FederationMatrix.inviteUsersToRoom(room, [user.username], inviter); } // TODO should we really check for "user" here? it is potentially an external user @@ -240,6 +253,24 @@ callbacks.add( 'beforeCreateDirectRoom', async (members, room): Promise => { if (FederationActions.shouldPerformFederationAction(room)) { + const isAllowed = await isFederationDomainAllowedFromUsernames(members); + if (!isAllowed) { + throw new Meteor.Error( + 'federation-policy-denied', + "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", + { method: 'createRoom' }, + ); + } + + try { + await FederationMatrix.validateFederatedUsersBeforeRoomCreation(members); + } catch (error) { + if (error instanceof FederationValidationError) { + throw new Meteor.Error(error.error, error.userMessage); + } + throw error; + } + await FederationMatrix.ensureFederatedUsersExistLocally(members); } }, diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index bb47f5b2d9fab..930ec4067e284 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -14,6 +14,7 @@ import { Logger } from '@rocket.chat/logger'; import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models'; import emojione from 'emojione'; +import { isFederationDomainAllowed } from './api/middlewares/isFederationDomainAllowed'; import { toExternalMessageFormat, toExternalQuoteMessageFormat } from './helpers/message.parsers'; import { MatrixMediaService } from './services/MatrixMediaService'; @@ -206,17 +207,32 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.processEDUPresence = (await Settings.getValueById('Federation_Service_EDU_Process_Presence')) || false; } + static async validateFederatedUsersBeforeRoomCreation(usernames: string[]): Promise { + const federatedUsers = usernames.filter(validateFederatedUsername); + if (federatedUsers.length === 0) { + return; + } + + for await (const username of federatedUsers) { + await federationSDK.validateOutboundUser(userIdSchema.parse(username)); + } + } + async createRoom(room: IRoom, owner: IUser): Promise<{ room_id: string; event_id: string }> { if (room.t !== 'c' && room.t !== 'p') { throw new Error('Room is not a public or private room'); } + let matrixRoomCreated = false; + let matrixRoomResult: { room_id: string; event_id: string } | undefined; + try { const matrixUserId = userIdSchema.parse(`@${owner.username}:${this.serverName}`); const roomName = room.name || room.fname || 'Untitled Room'; // canonical alias computed from name - const matrixRoomResult = await federationSDK.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); + matrixRoomResult = await federationSDK.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); + matrixRoomCreated = true; this.logger.debug('Matrix room created:', matrixRoomResult); @@ -228,7 +244,19 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return matrixRoomResult; } catch (error) { - this.logger.error(error, 'Failed to create room'); + this.logger.error('Failed to create room:', error); + + // if Matrix room was created but invitation failed, we should rollback the room creation + if (matrixRoomCreated && matrixRoomResult) { + this.logger.warn('Matrix room was created but setup failed, leaving room', matrixRoomResult.room_id); + try { + const matrixUserId = userIdSchema.parse(`@${owner.username}:${this.serverName}`); + await federationSDK.leaveRoom(matrixRoomResult.room_id, matrixUserId); + } catch (cleanupError) { + this.logger.error('Failed to cleanup Matrix room after error:', cleanupError); + } + } + throw error; } } @@ -863,24 +891,30 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS if (!homeserverUrl) { return [matrixId, 'UNABLE_TO_VERIFY']; } + try { - const result = await federationSDK.queryProfileRemote< - | { - avatar_url: string; - displayname: string; - } - | { - errcode: string; - error: string; - } - >({ homeserverUrl, userId: matrixId }); - - if ('errcode' in result && result.errcode === 'M_NOT_FOUND') { - return [matrixId, 'UNVERIFIED']; + // check RC domain allowlist + if (!(await isFederationDomainAllowed([homeserverUrl]))) { + return [matrixId, 'POLICY_DENIED']; } + // validate using homeserver (network + user existence) + await federationSDK.validateOutboundUser(userIdSchema.parse(matrixId)); + return [matrixId, 'VERIFIED']; } catch (e) { + if (e && typeof e === 'object' && 'code' in e) { + const error = e as { code: string }; + if (error.code === 'CONNECTION_FAILED') { + return [matrixId, 'UNABLE_TO_VERIFY']; + } + if (error.code === 'USER_NOT_FOUND') { + return [matrixId, 'UNVERIFIED']; + } + if (error.code === 'POLICY_DENIED') { + return [matrixId, 'POLICY_DENIED']; + } + } return [matrixId, 'UNABLE_TO_VERIFY']; } }), diff --git a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts index 78c145b695e84..8171bcdeb9a5a 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts @@ -1,4 +1,5 @@ import { Settings } from '@rocket.chat/core-services'; +import { extractDomainFromId } from '@rocket.chat/federation-sdk'; import { createMiddleware } from 'hono/factory'; import mem from 'mem'; @@ -16,6 +17,42 @@ const getAllowList = mem( { maxAge: 60000 }, ); +export async function isFederationDomainAllowed(domains: string[]): Promise { + const allowList = await getAllowList(); + if (!allowList || allowList.length === 0) { + return true; + } + + const isDomainAllowed = (domain: string) => { + return allowList.some((pattern) => { + if (pattern === domain) { + return true; + } + + if (pattern.startsWith('*.')) { + const baseDomain = pattern.slice(2); // remove '*.' + return domain === baseDomain || domain.endsWith(`.${baseDomain}`); + } + + return domain.endsWith(pattern); + }); + }; + + return domains.every((domain) => isDomainAllowed(domain.toLowerCase())); +} + +export async function isFederationDomainAllowedFromUsernames(usernames: string[]): Promise { + // filter out local users (those without ':' in username) and extract domains from external users + const domains = usernames.filter((username) => username.includes(':')).map((username) => extractDomainFromId(username)); + + // if no federated users, allow (all local users) + if (domains.length === 0) { + return true; + } + + return isFederationDomainAllowed(domains); +} + /** * Parses all key-value pairs from a Matrix authorization header. * Example: X-Matrix origin="matrix.org", key="value", ... @@ -52,8 +89,7 @@ export const isFederationDomainAllowedMiddleware = createMiddleware(async (c, ne return c.json({ errcode: 'M_MISSING_ORIGIN', error: 'Missing origin in authorization header.' }, 401); } - // Check if domain is in allowed list - if (allowList.some((allowed) => domain.endsWith(allowed))) { + if (await isFederationDomainAllowed([domain])) { return next(); } diff --git a/ee/packages/federation-matrix/src/index.ts b/ee/packages/federation-matrix/src/index.ts index b56729b4524ba..2ddf2ca87dcb8 100644 --- a/ee/packages/federation-matrix/src/index.ts +++ b/ee/packages/federation-matrix/src/index.ts @@ -2,8 +2,10 @@ import 'reflect-metadata'; export { FederationMatrix, validateFederatedUsername } from './FederationMatrix'; -export { generateEd25519RandomSecretKey } from '@rocket.chat/federation-sdk'; +export { generateEd25519RandomSecretKey, FederationValidationError } from '@rocket.chat/federation-sdk'; export { getFederationRoutes } from './api/routes'; export { setupFederationMatrix, configureFederationMatrixSettings } from './setup'; + +export { isFederationDomainAllowed, isFederationDomainAllowedFromUsernames } from './api/middlewares/isFederationDomainAllowed'; diff --git a/ee/packages/federation-matrix/src/setup.ts b/ee/packages/federation-matrix/src/setup.ts index f307d9c1e5327..20f6e415fc555 100644 --- a/ee/packages/federation-matrix/src/setup.ts +++ b/ee/packages/federation-matrix/src/setup.ts @@ -95,6 +95,12 @@ export function configureFederationMatrixSettings(settings: { processTyping: processEDUTyping, processPresence: processEDUPresence, }, + federation: { + validation: { + networkCheckTimeoutMs: Number.parseInt(process.env.FEDERATION_NETWORK_CHECK_TIMEOUT_MS || '3000', 10), + userCheckTimeoutMs: Number.parseInt(process.env.FEDERATION_USER_CHECK_TIMEOUT_MS || '3000', 10), + }, + }, }); } From 036ac42c3a30cfbd71270a480f4ea7b669fedb1a Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 25 Nov 2025 18:55:15 -0300 Subject: [PATCH 02/13] chore: rename isFederationDomainAllowedFromUsernames to isFederationDomainAllowedForUsernames --- apps/meteor/app/lib/server/functions/createRoom.ts | 4 ++-- apps/meteor/ee/server/hooks/federation/index.ts | 7 +++---- .../src/api/middlewares/isFederationDomainAllowed.ts | 2 +- ee/packages/federation-matrix/src/index.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index c3b3b2c42ae6c..f323e0fb2a1ac 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -3,7 +3,7 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti import { FederationMatrix, Message, Room, Team } from '@rocket.chat/core-services'; import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; -import { isFederationDomainAllowedFromUsernames, FederationValidationError } from '@rocket.chat/federation-matrix'; +import { isFederationDomainAllowedForUsernames, FederationValidationError } from '@rocket.chat/federation-matrix'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -193,7 +193,7 @@ export const createRoom = async ( if (shouldBeHandledByFederation && onlyUsernames(members)) { // check RC allowlist for domain - const isAllowed = await isFederationDomainAllowedFromUsernames(members); + const isAllowed = await isFederationDomainAllowedForUsernames(members); if (!isAllowed) { throw new Meteor.Error( 'federation-policy-denied', diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index f1d0d450c2f4a..0592b3a3f1a1c 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -1,8 +1,7 @@ import { FederationMatrix, Authorization, MeteorError, Room } from '@rocket.chat/core-services'; import { isEditedMessage, isRoomNativeFederated, isUserNativeFederated } from '@rocket.chat/core-typings'; import type { IRoomNativeFederated, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; -import { validateFederatedUsername } from '@rocket.chat/federation-matrix'; -import { isFederationDomainAllowedFromUsernames, FederationValidationError } from '@rocket.chat/federation-matrix'; +import { validateFederatedUsername, FederationValidationError, isFederationDomainAllowedForUsernames } from '@rocket.chat/federation-matrix'; import { Rooms } from '@rocket.chat/models'; import { callbacks } from '../../../../server/lib/callbacks'; @@ -114,7 +113,7 @@ beforeAddUserToRoom.add( throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation'); } - const isAllowed = await isFederationDomainAllowedFromUsernames([user.username]); + const isAllowed = await isFederationDomainAllowedForUsernames([user.username]); if (!isAllowed) { throw new MeteorError( 'federation-policy-denied', @@ -253,7 +252,7 @@ callbacks.add( 'beforeCreateDirectRoom', async (members, room): Promise => { if (FederationActions.shouldPerformFederationAction(room)) { - const isAllowed = await isFederationDomainAllowedFromUsernames(members); + const isAllowed = await isFederationDomainAllowedForUsernames(members); if (!isAllowed) { throw new Meteor.Error( 'federation-policy-denied', diff --git a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts index 8171bcdeb9a5a..488889d53ddf0 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts @@ -41,7 +41,7 @@ export async function isFederationDomainAllowed(domains: string[]): Promise isDomainAllowed(domain.toLowerCase())); } -export async function isFederationDomainAllowedFromUsernames(usernames: string[]): Promise { +export async function isFederationDomainAllowedForUsernames(usernames: string[]): Promise { // filter out local users (those without ':' in username) and extract domains from external users const domains = usernames.filter((username) => username.includes(':')).map((username) => extractDomainFromId(username)); diff --git a/ee/packages/federation-matrix/src/index.ts b/ee/packages/federation-matrix/src/index.ts index 2ddf2ca87dcb8..f4fc6e0f454d8 100644 --- a/ee/packages/federation-matrix/src/index.ts +++ b/ee/packages/federation-matrix/src/index.ts @@ -8,4 +8,4 @@ export { getFederationRoutes } from './api/routes'; export { setupFederationMatrix, configureFederationMatrixSettings } from './setup'; -export { isFederationDomainAllowed, isFederationDomainAllowedFromUsernames } from './api/middlewares/isFederationDomainAllowed'; +export { isFederationDomainAllowed, isFederationDomainAllowedForUsernames } from './api/middlewares/isFederationDomainAllowed'; From 66999d330bc14fbb97af6bebab611140deb3d44f Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 25 Nov 2025 19:09:43 -0300 Subject: [PATCH 03/13] refactor: add validateFederatedUsername check on validateFederatedUsersBeforeRoomCreation --- ee/packages/federation-matrix/src/FederationMatrix.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index 930ec4067e284..a878b05c05a6e 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -8,7 +8,7 @@ import { UserStatus, } from '@rocket.chat/core-typings'; import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings'; -import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK, FederationRequestError } from '@rocket.chat/federation-sdk'; +import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK, FederationRequestError, FederationValidationError } from '@rocket.chat/federation-sdk'; import type { EventID, UserID, FileMessageType, PresenceState } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models'; @@ -208,6 +208,14 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } static async validateFederatedUsersBeforeRoomCreation(usernames: string[]): Promise { + const hasInvalidFederatedUsername = usernames.some((username) => !validateFederatedUsername(username)); + if (hasInvalidFederatedUsername) { + throw new FederationValidationError( + 'POLICY_DENIED', + `Invalid federated username format: ${usernames.filter((username) => !validateFederatedUsername(username)).join(', ')}. Federated usernames must follow the format @username:domain.com`, + ); + } + const federatedUsers = usernames.filter(validateFederatedUsername); if (federatedUsers.length === 0) { return; From 92acb93104a15c43fb4561e818238f6f5bf113ec Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 25 Nov 2025 19:12:14 -0300 Subject: [PATCH 04/13] chore: remove rollback mechanism from createRoom --- .../federation-matrix/src/FederationMatrix.ts | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index a878b05c05a6e..a38d7659f7119 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -231,18 +231,11 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS throw new Error('Room is not a public or private room'); } - let matrixRoomCreated = false; - let matrixRoomResult: { room_id: string; event_id: string } | undefined; - try { const matrixUserId = userIdSchema.parse(`@${owner.username}:${this.serverName}`); const roomName = room.name || room.fname || 'Untitled Room'; - // canonical alias computed from name - matrixRoomResult = await federationSDK.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); - matrixRoomCreated = true; - - this.logger.debug('Matrix room created:', matrixRoomResult); + const matrixRoomResult = await federationSDK.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); await Rooms.setAsFederated(room._id, { mrid: matrixRoomResult.room_id, origin: this.serverName }); @@ -253,18 +246,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return matrixRoomResult; } catch (error) { this.logger.error('Failed to create room:', error); - - // if Matrix room was created but invitation failed, we should rollback the room creation - if (matrixRoomCreated && matrixRoomResult) { - this.logger.warn('Matrix room was created but setup failed, leaving room', matrixRoomResult.room_id); - try { - const matrixUserId = userIdSchema.parse(`@${owner.username}:${this.serverName}`); - await federationSDK.leaveRoom(matrixRoomResult.room_id, matrixUserId); - } catch (cleanupError) { - this.logger.error('Failed to cleanup Matrix room after error:', cleanupError); - } - } - throw error; } } From 47be39e986d1efdfc1778140e053296d405d7d42 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 25 Nov 2025 19:17:34 -0300 Subject: [PATCH 05/13] fix: make isDomainAllowed to right check subdomains --- .../src/api/middlewares/isFederationDomainAllowed.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts index 488889d53ddf0..aade0a17203f0 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts @@ -25,16 +25,12 @@ export async function isFederationDomainAllowed(domains: string[]): Promise { return allowList.some((pattern) => { - if (pattern === domain) { - return true; - } - if (pattern.startsWith('*.')) { const baseDomain = pattern.slice(2); // remove '*.' - return domain === baseDomain || domain.endsWith(`.${baseDomain}`); + return domain.endsWith(`.${baseDomain}`); } - return domain.endsWith(pattern); + return domain === pattern || domain.endsWith(`.${pattern}`); }); }; From f2673faf3815f091674549b5df4131c2659c850a Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 25 Nov 2025 19:30:08 -0300 Subject: [PATCH 06/13] refactor: add format filtering along with validateFederatedUsers --- apps/meteor/app/lib/server/functions/createRoom.ts | 6 +++++- apps/meteor/ee/server/hooks/federation/index.ts | 3 ++- ee/packages/federation-matrix/src/FederationMatrix.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index f323e0fb2a1ac..88e62f613dfdd 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -204,7 +204,11 @@ export const createRoom = async ( // validate external users (network + user existence checks) try { - await FederationMatrix.validateFederatedUsersBeforeRoomCreation(members); + // TODO: Use common function to extract and validate federated users + const federatedUsers = members + .filter((member: string | IUser) => (typeof member === 'string' ? member.includes(':') : member.username?.includes(':'))) + .map((member: string | IUser) => (typeof member === 'string' ? member : member.username!)); + await FederationMatrix.validateFederatedUsers(federatedUsers); } catch (error) { if (error instanceof FederationValidationError) { throw new Meteor.Error(error.error, error.userMessage, { method: 'createRoom' }); diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index 0592b3a3f1a1c..451ffdb30e167 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -262,7 +262,8 @@ callbacks.add( } try { - await FederationMatrix.validateFederatedUsersBeforeRoomCreation(members); + const federatedUsers = members.filter((username) => username.includes(':')); + await FederationMatrix.validateFederatedUsers(federatedUsers); } catch (error) { if (error instanceof FederationValidationError) { throw new Meteor.Error(error.error, error.userMessage); diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index a38d7659f7119..2d335ed540836 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -207,7 +207,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.processEDUPresence = (await Settings.getValueById('Federation_Service_EDU_Process_Presence')) || false; } - static async validateFederatedUsersBeforeRoomCreation(usernames: string[]): Promise { + static async validateFederatedUsers(usernames: string[]): Promise { const hasInvalidFederatedUsername = usernames.some((username) => !validateFederatedUsername(username)); if (hasInvalidFederatedUsername) { throw new FederationValidationError( From 8318ca6e5709d9aac34a2231a730527e6881553b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 19 Dec 2025 20:03:57 -0300 Subject: [PATCH 07/13] update federation-sdk --- apps/meteor/package.json | 3 ++- ee/packages/federation-matrix/package.json | 2 +- packages/core-services/package.json | 2 +- yarn.lock | 15 ++++++++------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index d5f09f8d4b1a6..3c319fd96cebe 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -99,7 +99,8 @@ "@rocket.chat/emitter": "~0.31.25", "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", - "@rocket.chat/federation-sdk": "0.3.5", + "@rocket.chat/federation-sdk": "0.3.6", + "@rocket.chat/freeswitch": "workspace:^", "@rocket.chat/fuselage": "^0.70.0", "@rocket.chat/fuselage-forms": "~0.1.1", "@rocket.chat/fuselage-hooks": "~0.38.1", diff --git a/ee/packages/federation-matrix/package.json b/ee/packages/federation-matrix/package.json index 72c841daf0654..e6df0b1dbd255 100644 --- a/ee/packages/federation-matrix/package.json +++ b/ee/packages/federation-matrix/package.json @@ -22,7 +22,7 @@ "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/emitter": "^0.31.25", - "@rocket.chat/federation-sdk": "0.3.5", + "@rocket.chat/federation-sdk": "0.3.6", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/license": "workspace:^", "@rocket.chat/models": "workspace:^", diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 6da52c26e7aa7..0812c9ee71f01 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@rocket.chat/core-typings": "workspace:^", - "@rocket.chat/federation-sdk": "0.3.5", + "@rocket.chat/federation-sdk": "0.3.6", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/icons": "~0.46.0", "@rocket.chat/media-signaling": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 87ef47de32823..72ed02bd1d8c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8464,7 +8464,7 @@ __metadata: "@rocket.chat/apps-engine": "workspace:^" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.5" + "@rocket.chat/federation-sdk": "npm:0.3.6" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/icons": "npm:~0.46.0" "@rocket.chat/jest-presets": "workspace:~" @@ -8670,7 +8670,7 @@ __metadata: "@rocket.chat/ddp-client": "workspace:^" "@rocket.chat/emitter": "npm:^0.31.25" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.5" + "@rocket.chat/federation-sdk": "npm:0.3.6" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/license": "workspace:^" "@rocket.chat/models": "workspace:^" @@ -8696,9 +8696,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/federation-sdk@npm:0.3.5": - version: 0.3.5 - resolution: "@rocket.chat/federation-sdk@npm:0.3.5" +"@rocket.chat/federation-sdk@npm:0.3.6": + version: 0.3.6 + resolution: "@rocket.chat/federation-sdk@npm:0.3.6" dependencies: "@datastructures-js/priority-queue": "npm:^6.3.5" "@noble/ed25519": "npm:^3.0.0" @@ -8711,7 +8711,7 @@ __metadata: zod: "npm:^3.24.1" peerDependencies: typescript: ~5.9.2 - checksum: 10/47de2265555649b375620c7a90cf84134b14f3e38d171d84276dee9196b7b303a2d8c6f693223d63ba1c464ce12c128c5f6a795c0bb42c0e5339587b2429d034 + checksum: 10/0fb80c2f62ec8ac53b433571672bf79cab1050c54e5733b463f7592e3fdc059e21535fa5b1c44b0660d9ac78325b66f0cada4f1511ce0d420bb07bf963d8aaad languageName: node linkType: hard @@ -9338,7 +9338,8 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.5" + "@rocket.chat/federation-sdk": "npm:0.3.6" + "@rocket.chat/freeswitch": "workspace:^" "@rocket.chat/fuselage": "npm:^0.70.0" "@rocket.chat/fuselage-forms": "npm:~0.1.1" "@rocket.chat/fuselage-hooks": "npm:~0.38.1" From e82b55388ad4fe8407fbfa7bfbf0340b551cac16 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 19 Dec 2025 20:35:49 -0300 Subject: [PATCH 08/13] fix lint and tsc --- apps/meteor/ee/server/hooks/federation/index.ts | 7 +++++-- ee/packages/federation-matrix/src/FederationMatrix.ts | 11 +++++++++-- .../src/types/IFederationMatrixService.ts | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index 451ffdb30e167..8458b8f641dea 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -1,7 +1,11 @@ import { FederationMatrix, Authorization, MeteorError, Room } from '@rocket.chat/core-services'; import { isEditedMessage, isRoomNativeFederated, isUserNativeFederated } from '@rocket.chat/core-typings'; import type { IRoomNativeFederated, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; -import { validateFederatedUsername, FederationValidationError, isFederationDomainAllowedForUsernames } from '@rocket.chat/federation-matrix'; +import { + validateFederatedUsername, + FederationValidationError, + isFederationDomainAllowedForUsernames, +} from '@rocket.chat/federation-matrix'; import { Rooms } from '@rocket.chat/models'; import { callbacks } from '../../../../server/lib/callbacks'; @@ -257,7 +261,6 @@ callbacks.add( throw new Meteor.Error( 'federation-policy-denied', "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", - { method: 'createRoom' }, ); } diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index 2d335ed540836..997aa89cf0b69 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -8,7 +8,14 @@ import { UserStatus, } from '@rocket.chat/core-typings'; import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings'; -import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK, FederationRequestError, FederationValidationError } from '@rocket.chat/federation-sdk'; +import { + eventIdSchema, + roomIdSchema, + userIdSchema, + federationSDK, + FederationRequestError, + FederationValidationError, +} from '@rocket.chat/federation-sdk'; import type { EventID, UserID, FileMessageType, PresenceState } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models'; @@ -207,7 +214,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.processEDUPresence = (await Settings.getValueById('Federation_Service_EDU_Process_Presence')) || false; } - static async validateFederatedUsers(usernames: string[]): Promise { + async validateFederatedUsers(usernames: string[]): Promise { const hasInvalidFederatedUsername = usernames.some((username) => !validateFederatedUsername(username)); if (hasInvalidFederatedUsername) { throw new FederationValidationError( diff --git a/packages/core-services/src/types/IFederationMatrixService.ts b/packages/core-services/src/types/IFederationMatrixService.ts index ee721b18d5c61..ec566b8a6415c 100644 --- a/packages/core-services/src/types/IFederationMatrixService.ts +++ b/packages/core-services/src/types/IFederationMatrixService.ts @@ -29,4 +29,5 @@ export interface IFederationMatrixService { notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise; verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>; handleInvite(subscriptionId: ISubscription['_id'], userId: IUser['_id'], action: 'accept' | 'reject'): Promise; + validateFederatedUsers(usernames: string[]): Promise; } From 2e34a35c63f0667c64c3f0a960faf8efefff3438 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 23 Dec 2025 14:32:09 -0300 Subject: [PATCH 09/13] fix: resolve rebase conflict that duplicated federation invite logic --- .../ee/server/hooks/federation/index.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index 8458b8f641dea..9f0a761e006ec 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -112,27 +112,22 @@ beforeAddUserToRoom.add( return; } - if (FederationActions.shouldPerformFederationAction(room)) { - if (!(await Authorization.hasPermission(user._id, 'access-federation'))) { - throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation'); - } - - const isAllowed = await isFederationDomainAllowedForUsernames([user.username]); - if (!isAllowed) { - throw new MeteorError( - 'federation-policy-denied', - "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", - ); - } - - await FederationMatrix.inviteUsersToRoom(room, [user.username], inviter); + if (!FederationActions.shouldPerformFederationAction(room)) { + return; } - // TODO should we really check for "user" here? it is potentially an external user if (!(await Authorization.hasPermission(user._id, 'access-federation'))) { throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation'); } + const isAllowed = await isFederationDomainAllowedForUsernames([user.username]); + if (!isAllowed) { + throw new MeteorError( + 'federation-policy-denied', + "Action Blocked. Communication with one of the domains in the list is restricted by your organization's security policy.", + ); + } + // If inviter is federated, the invite came from an external transaction. // Don't propagate back to Matrix (it was already processed at origin server). if (isUserNativeFederated(inviter)) { From 33dadae3908856b1cefcb51f33ceb5c35f5c02eb Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 23 Dec 2025 14:35:54 -0300 Subject: [PATCH 10/13] Update ee/packages/federation-matrix/src/FederationMatrix.ts Co-authored-by: Diego Sampaio --- ee/packages/federation-matrix/src/FederationMatrix.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index 997aa89cf0b69..f1944a804a400 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -252,7 +252,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return matrixRoomResult; } catch (error) { - this.logger.error('Failed to create room:', error); + this.logger.error(error, 'Failed to create room'); throw error; } } From 68c95359cab60603520ac110bb87a2b51434bd67 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 23 Dec 2025 15:47:26 -0300 Subject: [PATCH 11/13] chore: adjusts package and lock to proper federation-sdk versions --- apps/meteor/package.json | 3 +-- yarn.lock | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 3c319fd96cebe..d5f09f8d4b1a6 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -99,8 +99,7 @@ "@rocket.chat/emitter": "~0.31.25", "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", - "@rocket.chat/federation-sdk": "0.3.6", - "@rocket.chat/freeswitch": "workspace:^", + "@rocket.chat/federation-sdk": "0.3.5", "@rocket.chat/fuselage": "^0.70.0", "@rocket.chat/fuselage-forms": "~0.1.1", "@rocket.chat/fuselage-hooks": "~0.38.1", diff --git a/yarn.lock b/yarn.lock index 72ed02bd1d8c4..fe33e2be83f18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8696,6 +8696,25 @@ __metadata: languageName: unknown linkType: soft +"@rocket.chat/federation-sdk@npm:0.3.5": + version: 0.3.5 + resolution: "@rocket.chat/federation-sdk@npm:0.3.5" + dependencies: + "@datastructures-js/priority-queue": "npm:^6.3.5" + "@noble/ed25519": "npm:^3.0.0" + "@rocket.chat/emitter": "npm:^0.31.25" + mongodb: "npm:^6.16.0" + pino: "npm:^8.21.0" + reflect-metadata: "npm:^0.2.2" + tsyringe: "npm:^4.10.0" + tweetnacl: "npm:^1.0.3" + zod: "npm:^3.24.1" + peerDependencies: + typescript: ~5.9.2 + checksum: 10/47de2265555649b375620c7a90cf84134b14f3e38d171d84276dee9196b7b303a2d8c6f693223d63ba1c464ce12c128c5f6a795c0bb42c0e5339587b2429d034 + languageName: node + linkType: hard + "@rocket.chat/federation-sdk@npm:0.3.6": version: 0.3.6 resolution: "@rocket.chat/federation-sdk@npm:0.3.6" @@ -9338,8 +9357,7 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.6" - "@rocket.chat/freeswitch": "workspace:^" + "@rocket.chat/federation-sdk": "npm:0.3.5" "@rocket.chat/fuselage": "npm:^0.70.0" "@rocket.chat/fuselage-forms": "npm:~0.1.1" "@rocket.chat/fuselage-hooks": "npm:~0.38.1" From 7f7928aef196b530ba1ea4d85d112ed712410410 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 23 Dec 2025 16:30:50 -0300 Subject: [PATCH 12/13] chore: add type on errors --- apps/meteor/app/lib/server/functions/createRoom.ts | 2 +- apps/meteor/ee/server/hooks/federation/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 88e62f613dfdd..7284c799747f5 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -209,7 +209,7 @@ export const createRoom = async ( .filter((member: string | IUser) => (typeof member === 'string' ? member.includes(':') : member.username?.includes(':'))) .map((member: string | IUser) => (typeof member === 'string' ? member : member.username!)); await FederationMatrix.validateFederatedUsers(federatedUsers); - } catch (error) { + } catch (error: FederationValidationError | unknown) { if (error instanceof FederationValidationError) { throw new Meteor.Error(error.error, error.userMessage, { method: 'createRoom' }); } diff --git a/apps/meteor/ee/server/hooks/federation/index.ts b/apps/meteor/ee/server/hooks/federation/index.ts index 9f0a761e006ec..c0a3d06a9e86e 100644 --- a/apps/meteor/ee/server/hooks/federation/index.ts +++ b/apps/meteor/ee/server/hooks/federation/index.ts @@ -262,7 +262,7 @@ callbacks.add( try { const federatedUsers = members.filter((username) => username.includes(':')); await FederationMatrix.validateFederatedUsers(federatedUsers); - } catch (error) { + } catch (error: FederationValidationError | unknown) { if (error instanceof FederationValidationError) { throw new Meteor.Error(error.error, error.userMessage); } From 312ec65b8c49656af47527d73c695c6b330340c5 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Fri, 26 Dec 2025 11:33:12 -0300 Subject: [PATCH 13/13] x --- .../federation-matrix/src/FederationMatrix.ts | 10 ++-------- .../api/middlewares/isFederationDomainAllowed.ts | 5 +++-- .../src/errors/FederationValidationError.ts | 13 +++++++++++++ ee/packages/federation-matrix/src/index.ts | 4 +++- 4 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 ee/packages/federation-matrix/src/errors/FederationValidationError.ts diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index f1944a804a400..db07284d760c8 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -8,20 +8,14 @@ import { UserStatus, } from '@rocket.chat/core-typings'; import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings'; -import { - eventIdSchema, - roomIdSchema, - userIdSchema, - federationSDK, - FederationRequestError, - FederationValidationError, -} from '@rocket.chat/federation-sdk'; +import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK, FederationRequestError } from '@rocket.chat/federation-sdk'; import type { EventID, UserID, FileMessageType, PresenceState } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models'; import emojione from 'emojione'; import { isFederationDomainAllowed } from './api/middlewares/isFederationDomainAllowed'; +import { FederationValidationError } from './errors/FederationValidationError'; import { toExternalMessageFormat, toExternalQuoteMessageFormat } from './helpers/message.parsers'; import { MatrixMediaService } from './services/MatrixMediaService'; diff --git a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts index aade0a17203f0..7d65a29868c80 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isFederationDomainAllowed.ts @@ -1,8 +1,9 @@ import { Settings } from '@rocket.chat/core-services'; -import { extractDomainFromId } from '@rocket.chat/federation-sdk'; import { createMiddleware } from 'hono/factory'; import mem from 'mem'; +import { extractDomainFromMatrixUserId } from '../../FederationMatrix'; + // cache for 60 seconds const getAllowList = mem( async () => { @@ -39,7 +40,7 @@ export async function isFederationDomainAllowed(domains: string[]): Promise { // filter out local users (those without ':' in username) and extract domains from external users - const domains = usernames.filter((username) => username.includes(':')).map((username) => extractDomainFromId(username)); + const domains = usernames.filter((username) => username.includes(':')).map((username) => extractDomainFromMatrixUserId(username)); // if no federated users, allow (all local users) if (domains.length === 0) { diff --git a/ee/packages/federation-matrix/src/errors/FederationValidationError.ts b/ee/packages/federation-matrix/src/errors/FederationValidationError.ts new file mode 100644 index 0000000000000..44f046a048fff --- /dev/null +++ b/ee/packages/federation-matrix/src/errors/FederationValidationError.ts @@ -0,0 +1,13 @@ +// Local copy to avoid broken import chain in homeserver's federation-sdk +export class FederationValidationError extends Error { + public error: string; + + constructor( + public code: 'POLICY_DENIED' | 'CONNECTION_FAILED' | 'USER_NOT_FOUND', + public userMessage: string, + ) { + super(userMessage); + this.name = 'FederationValidationError'; + this.error = `federation-${code.toLowerCase().replace(/_/g, '-')}`; + } +} diff --git a/ee/packages/federation-matrix/src/index.ts b/ee/packages/federation-matrix/src/index.ts index f4fc6e0f454d8..b3c7c0e951169 100644 --- a/ee/packages/federation-matrix/src/index.ts +++ b/ee/packages/federation-matrix/src/index.ts @@ -2,7 +2,9 @@ import 'reflect-metadata'; export { FederationMatrix, validateFederatedUsername } from './FederationMatrix'; -export { generateEd25519RandomSecretKey, FederationValidationError } from '@rocket.chat/federation-sdk'; +export { generateEd25519RandomSecretKey } from '@rocket.chat/federation-sdk'; + +export { FederationValidationError } from './errors/FederationValidationError'; export { getFederationRoutes } from './api/routes';