Skip to content

Conversation

@ggazzo
Copy link
Member

@ggazzo ggazzo commented Sep 30, 2025

FDR-193

Co-authored-by: Diego Sampaio chinello@gmail.com

Proposed changes (including videos or screenshots)

Issue(s)

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Auto create/update federated users, username validation, domain extraction, and accept-invite join flow added.
    • Event registration now accepts federation services and federation key generation is exposed.
  • Bug Fixes

    • More consistent user resolution by username across federation events.
    • Tighter federated-only handling for 1:1 DMs and invite flows; improved structured error logging.
  • Other

    • Direct room creation now loads full user profiles; removed bulk federation "add users" callback.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 30, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 30, 2025

⚠️ No Changeset found

Latest commit: fe0e73b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces selective user projections with full-user fetches; adds federated-username validators and helpers (createOrUpdateFederatedUser, getUsernameServername); refactors federation DM/invite/join flows to use username-based lookups and helpers; updates event handlers and typings for native-federation fields; removes multi-invite federation callback.

Changes

Cohort / File(s) Summary
Direct room user fetch
apps/meteor/app/lib/server/functions/createDirectRoom.ts
Removed explicit projection in Users.findUsersByUsernames; now fetches full user documents via .toArray().
Federation core & helpers
ee/packages/federation-matrix/src/FederationMatrix.ts
Added validateFederatedUsername, extractDomainFromMatrixUserId, getUsernameServername, createOrUpdateFederatedUser; re-exported generateEd25519RandomSecretKey; changed createDirectMessageRoom signature to accept IUser[]; refactored federated user creation/invite flows and standardized error logging.
Matrix invite API
ee/packages/federation-matrix/src/api/_matrix/invite.ts
Introduced exported acceptInvite handler; resolve sender via findOneByUsername or createOrUpdateFederatedUser; adjusted join/backoff logic and error handling.
Federation event handlers
ee/packages/federation-matrix/src/events/*
edu.ts, member.ts, message.ts, room.ts, index.ts
Replaced Users.findOne({...}) calls with Users.findOneByUsername(...); added services: HomeserverServices where required; member join flow uses getUsernameServername, createOrUpdateFederatedUser, and subscription checks; registerEvents now forwards services.
Typings for native-federation users
packages/core-typings/src/IUser.ts
Added username: \@${string}:${string}`toIUserNativeFederated; changed federation.muitype to template-literal`@${string}:${string}``.
App federation hooks & callbacks
apps/meteor/app/lib/server/methods/addUsersToRoom.ts, apps/meteor/ee/server/hooks/federation/index.ts, apps/meteor/lib/callbacks.ts
Removed federation.onAddUsersToRoom multi-invite callback and related callback typings; removed Username export from callbacks.
Room moderation methods
apps/meteor/server/methods/addRoomModerator.ts, apps/meteor/server/methods/addRoomOwner.ts, apps/meteor/server/methods/removeRoomModerator.ts, apps/meteor/server/methods/removeRoomOwner.ts
Expanded Rooms.findOneById projection to include federation (in addition to t and federated) so federation data is available; no other control-flow changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant App as App Server
  participant Fed as FederationMatrix
  participant Users as Users DB
  participant Matrix as Matrix HS

  Client->>App: Create DM (members: IUser[])
  App->>Fed: createDirectMessageRoom(room, members, creatorId)
  Fed->>Fed: validateFederatedUsername(member.username)
  alt member is federated
    Fed->>Users: createOrUpdateFederatedUser({ username, origin, name? })
    Users-->>Fed: userId
    Fed->>Matrix: create room / invite federated user
  else local member
    Fed->>Users: find local user by username
  end
  Matrix-->>Fed: room created / invite accepted
  Fed-->>App: DM room ready
  App-->>Client: Success
Loading
sequenceDiagram
  autonumber
  participant Matrix as Matrix HS
  participant InviteAPI as /_matrix/invite
  participant Fed as FederationMatrix
  participant Users as Users DB

  Matrix->>InviteAPI: acceptInvite(inviteEvent, username)
  InviteAPI->>Users: findOneByUsername(sender)
  alt sender not found
    InviteAPI->>Fed: createOrUpdateFederatedUser({ username: sender, origin })
    Fed-->>InviteAPI: senderUserId
  end
  InviteAPI->>InviteAPI: startJoiningRoom (with backoff)
  InviteAPI-->>Matrix: joinRoom request
  Matrix-->>InviteAPI: join result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • sampaiodiego
  • ricardogarim

Poem

I hop where at-signs meet domain and name,
I stitch usernames and kindle the flame.
I validate, create, and gently invite,
Tiny paws tap keys through matrix night.
A rabbit cheers: federated friends take flight! 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “regression(federation): Old federation users” is vague and does not clearly summarize the main changes, which center on implementing bridging support for federated users via new helpers, upsert logic, event handler updates, and projection adjustments. Revise the title to concisely reflect the core implementation, for example “fix(federation): support bridged federated users upsert and invite flows.”
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run `@coderabbitai generate docstrings` to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit's high-level summary is enabled.
Linked Issues Check ✅ Passed The changes directly address FDR-193 by adding validateFederatedUsername, extractDomainFromMatrixUserId, createOrUpdateFederatedUser, updating event handlers to use these helpers, and adjusting projections to properly handle federated users from bridged implementations.
Out of Scope Changes Check ✅ Passed All modifications in federation-matrix, Meteor server methods, callbacks, and typings are focused on enabling or refining federated user support and bridging functionality, with no unrelated code changes.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/old-fed

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ebb2acf and fe0e73b.

📒 Files selected for processing (2)
  • ee/packages/federation-matrix/src/events/index.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/room.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ee/packages/federation-matrix/src/events/room.ts
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/federation-matrix/src/events/index.ts (2)
ee/packages/federation-matrix/src/events/member.ts (1)
  • member (83-99)
ee/packages/federation-matrix/src/events/room.ts (1)
  • room (8-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (4)
ee/packages/federation-matrix/src/events/index.ts (4)

2-2: LGTM! Necessary imports added.

The import statement correctly adds the required types and helper function for dependency injection of federation services.


20-22: LGTM! Services correctly passed to handlers.

The services parameter is properly threaded through to the member and room event handlers, which is consistent with their updated signatures shown in the relevant code snippets.


15-15: Verify getAllServices() has no side effects at module load. The default parameter is fine for DI, but confirm that getAllServices() doesn’t perform heavyweight initialization, network I/O, or depend on uninitialized state in '@rocket.chat/federation-sdk'.


17-19: No services parameter needed in ping, message, reaction, edus handlers — their signatures and implementations don’t accept or reference HomeserverServices, so the current registrations are intentional.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ggazzo ggazzo changed the title Release 7.11.0-rc.0 fix(federation): Old federation users Sep 30, 2025
@codecov
Copy link

codecov bot commented Sep 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.96%. Comparing base (318f272) to head (fe0e73b).
⚠️ Report is 3 commits behind head on release-7.11.0.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           release-7.11.0   #37102      +/-   ##
==================================================
- Coverage           67.43%   65.96%   -1.48%     
==================================================
  Files                3329     3052     -277     
  Lines              113387   109163    -4224     
  Branches            20576    19595     -981     
==================================================
- Hits                76464    72005    -4459     
- Misses              34319    34861     +542     
+ Partials             2604     2297     -307     
Flag Coverage Δ
e2e 50.87% <ø> (-6.40%) ⬇️
unit 71.21% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo ggazzo marked this pull request as ready for review September 30, 2025 21:38
@ggazzo ggazzo requested a review from a team as a code owner September 30, 2025 21:38
Copilot AI review requested due to automatic review settings September 30, 2025 21:38
@ggazzo ggazzo added this to the 7.11.0 milestone Sep 30, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the federation system to handle old federation users by changing how federated users are identified and stored. The changes transition from using the federation.mui field to using the username field directly for user lookups, and introduces better type safety for federated user identifiers.

  • Updates type definitions to enforce Matrix User Identifier format for federated usernames
  • Replaces database queries from federation.mui field to direct username lookups
  • Introduces helper functions for federated user creation and validation

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/core-typings/src/IUser.ts Updates type definitions to enforce Matrix ID format for federated users
ee/packages/federation-matrix/src/events/*.ts Changes user lookups from federation.mui to username-based queries
ee/packages/federation-matrix/src/api/_matrix/invite.ts Refactors user creation to use new helper function and imports
ee/packages/federation-matrix/src/FederationMatrix.ts Adds validation and creation helpers, simplifies DM room creation logic
apps/meteor/app/lib/server/functions/createDirectRoom.ts Removes projection constraint from user query

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ee/packages/federation-matrix/src/events/member.ts (1)

61-77: Replace inline user creation with the new createOrUpdateFederatedUser helper.

The manual user insertion duplicates logic that's now centralized in the createOrUpdateFederatedUser helper introduced in FederationMatrix.ts (lines 62-97). Using the helper ensures consistency across the codebase and handles the upsert pattern for users that might exist with incomplete federation data.

Apply this diff to use the helper:

-	const { insertedId } = await Users.insertOne({
-		username: internalUsername,
-		type: 'user',
-		status: UserStatus.OFFLINE,
-		active: true,
-		roles: ['user'],
-		name: data.content.displayname || internalUsername,
-		requirePasswordChange: false,
-		createdAt: new Date(),
-		_updatedAt: new Date(),
-		federated: true,
-		federation: {
-			version: 1,
-			mui: data.sender,
-			origin: serverName,
-		},
-	});
+	const { createOrUpdateFederatedUser } = await import('../FederationMatrix');
+	const { insertedId } = await createOrUpdateFederatedUser({
+		username: internalUsername,
+		name: data.content.displayname || internalUsername,
+		status: UserStatus.OFFLINE,
+		origin: serverName,
+	});
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)

193-201: Handle the case where createOrUpdateFederatedUser updates an existing user.

The createOrUpdateFederatedUser helper uses Users.updateOne with upsert: true, which returns { upsertedId }. When an existing user is updated (not inserted), upsertedId will be null or undefined, but the code at line 200 assigns it directly to senderUserId.

Apply this diff to handle both insert and update cases:

 	if (!senderUser) {
 		const { createOrUpdateFederatedUser } = await import('../../FederationMatrix');
 		const createdUser = await createOrUpdateFederatedUser({
 			username: inviteEvent.sender,
 			status: UserStatus.ONLINE,
 			origin: matrixRoom.origin,
 		});
 
-		senderUserId = createdUser.insertedId;
+		senderUserId = createdUser.insertedId || (await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } }))?._id;
 	}
🧹 Nitpick comments (2)
apps/meteor/app/lib/server/functions/createDirectRoom.ts (1)

74-76: Reinstate targeted projection for user lookup
Re-add a projection to Users.findUsersByUsernames(...) to fetch only the fields actually used in this function (_id, name, username). No federation-specific fields are accessed here, so fetching full documents is unnecessary.

ee/packages/federation-matrix/src/FederationMatrix.ts (1)

314-316: Skip check might be too broad.

The condition if (existingUser && isUserNativeFederated(existingUser)) skips users that exist and are natively federated. However, this doesn't verify that the user's federation data (origin, mui) matches the current username. A user might exist with outdated federation metadata.

Consider removing the skip or verifying federation metadata matches:

 			const existingUser = await Users.findOneByUsername(username);
 			if (existingUser && isUserNativeFederated(existingUser)) {
+				// Verify federation metadata is correct
+				const expectedOrigin = username.split(':')[1];
+				if (existingUser.federation.origin === expectedOrigin && existingUser.federation.mui === username) {
-				continue;
+					continue;
+				}
 			}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f22549 and ff14893.

📒 Files selected for processing (8)
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts (1 hunks)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (3 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/edu.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/member.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/message.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/room.ts (3 hunks)
  • packages/core-typings/src/IUser.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
🧬 Code graph analysis (2)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
  • createOrUpdateFederatedUser (62-97)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (13)
ee/packages/federation-matrix/src/events/room.ts (2)

31-34: Verify upstream username validation.

Same concern as the room.name handler: ensure userId is validated as a federated username before this handler processes it.


47-55: Verify upstream username validation for both userId and senderId.

Both userId and senderId are now treated as usernames. Ensure upstream validation confirms these are valid federated usernames.

ee/packages/federation-matrix/src/events/message.ts (2)

126-130: LGTM: Defensive error handling maintained.

The comment indicates the user should exist, but the code appropriately throws an error if the lookup fails. This defensive approach is good practice for federation event handling.


289-294: LGTM: Consistent username-based lookup in redaction handler.

The redaction handler correctly uses the same username-based lookup pattern as the main message handler.

ee/packages/federation-matrix/src/events/edu.ts (2)

23-27: LGTM: Appropriate defensive checks.

The handler correctly validates both the user existence and username field after the lookup, with clear debug logging.


45-54: LGTM: Federated flag validation ensures correct user type.

The presence handler appropriately checks the federated flag (line 51-53) to ensure only federated users receive presence updates from Matrix, preventing incorrect status updates for local users.

packages/core-typings/src/IUser.ts (1)

268-271: Approve code changes The createOrUpdateFederatedUser helper in packages/federation-matrix/src/FederationMatrix.ts upserts existing federated users with the newly required username field, covering the migration path.

ee/packages/federation-matrix/src/events/member.ts (1)

31-31: Verify the username format for sender lookups.

Similar to the state_key lookup, data.sender is now used directly with Users.findOneByUsername. Confirm that data.sender always contains a valid federated username format.

ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)

187-187: Verify sender username format for federation lookups.

The change to Users.findOneByUsername(inviteEvent.sender) assumes inviteEvent.sender is a valid username. Ensure that the sender field from the invite event is consistently formatted as a federated username (@username:domain).

ee/packages/federation-matrix/src/FederationMatrix.ts (4)

311-322: Good use of the new helpers!

The refactored ensureFederatedUsersExistLocally now leverages validateFederatedUsername and createOrUpdateFederatedUser, which improves consistency and maintainability.


363-377: Simplified member handling for group DMs.

The refactored loop now skips non-federated members instead of throwing errors, which is a more graceful approach for group DMs with mixed membership.


348-354: Verify local-to-local DM behavior

We found no tests covering non-federated (local-to-local) DM creation, and the new throw at FederationMatrix.ts:352–354 will cause those flows to fail. Confirm whether local users should still be able to open 1-on-1 DMs; if so, swap the error for an early return:

-   if (!isUserNativeFederated(otherMember)) {
-       throw new Error('Other member is not federated');
-   }
+   if (!isUserNativeFederated(otherMember)) {
+       this.logger.debug('Other member is not federated, skipping Matrix DM creation');
+       return;
+   }

330-387: No changes needed: all callers updated to pass IUser[]
Verified that createDirectMessageRoom is only invoked with params.members, which is typed as IUser[] in the afterCreateDirectRoom hook—matching the updated signature.

@ggazzo ggazzo requested a review from Copilot September 30, 2025 21:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

315-336: Fix origin extraction to handle domains with ports.
Replace the use of username.split(':')[1] (line 329) with logic that captures everything after the first colon—e.g.

const origin = username.slice(username.indexOf(':') + 1);

so that domains containing ports (e.g. example.com:8448) are preserved.

♻️ Duplicate comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

51-54: Regex still doesn't match Matrix spec for user IDs.

The current regex allows uppercase letters (A-Z), but the Matrix specification only permits lowercase letters in the localpart. Additionally, the regex is missing / and + characters which are valid in Matrix user IDs according to the spec.

Apply this diff to enforce the Matrix spec correctly:

 export function validateFederatedUsername(username: string): username is `@${string}:${string}` {
-	return /^@[a-zA-Z0-9._=-]+:[a-zA-Z0-9.:_-]+$/.test(username);
+	return /^@[a-z0-9._=\-/+]+:[a-z0-9.:_-]+$/.test(username);
 }

Note: This enforces lowercase-only localpart and includes all valid punctuation characters (., _, =, -, /, +).

Based on past review comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff14893 and a17ed42.

📒 Files selected for processing (1)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: Builds matrix rust bindings against alpine
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

360-362: Federation-only DM path is guarded; local DMs remain unaffected
The createDirectMessageRoom call in FederationMatrix is only invoked inside the shouldPerformFederationAction guard (and after handling params.mrid), so local 1-on-1 DMs with non-federated users continue using the standard code path. Disregard the concern about blocking non-federated DMs here.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

74-95: Do not overwrite createdAt on every upsert

createdAt is still inside $set, so every legacy user “update” will reset their creation timestamp. This was flagged earlier and remains unresolved—please move createdAt under $setOnInsert so historical metadata survives the migration.

 		{
 			$set: {
 				username,
 				name,
 				type: 'user' as const,
 				status: UserStatus.OFFLINE,
 				active: true,
 				roles: ['user'],
 				requirePasswordChange: false,
 				federated: true,
 				federation: {
 					version: 1,
 					mui: username,
 					origin,
 				},
-				createdAt: new Date(),
 				_updatedAt: new Date(),
 			},
+			$setOnInsert: {
+				createdAt: new Date(),
+			},
 		},
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a17ed42 and ca8f169.

📒 Files selected for processing (2)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (3 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
  • createOrUpdateFederatedUser (62-105)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

@ggazzo ggazzo force-pushed the fix/old-fed branch 2 times, most recently from c179fcd to 3d3e7fa Compare September 30, 2025 22:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

51-82: Critical: Localpart regex still rejects valid Matrix user IDs.

The current regex at line 61 allows [a-z0-9._\-] and hex-encoded characters (=HH), but the Matrix specification permits additional characters in the localpart: / and +. This means valid Matrix IDs like @user/test:example.org or @user+tag:example.org will be rejected, breaking federation with compliant homeservers.

Apply this diff to include the missing characters:

-	const localpartRegex = /^(?:[a-z0-9._\-]|=[0-9a-fA-F]{2}){1,255}$/;
+	const localpartRegex = /^(?:[a-z0-9._\-/+]|=[0-9a-fA-F]{2}){1,255}$/;

Based on previous review feedback and Matrix specification requirements.

🧹 Nitpick comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

408-422: Consider logging skipped non-federated members.

Lines 413-415 silently skip non-federated members in group DMs. While functionally correct, adding a debug log would improve troubleshooting when users expect all members to be invited but some are excluded.

Apply this diff to add logging:

 		if (!isUserNativeFederated(member)) {
+			this.logger.debug('Skipping non-federated member in group DM', { username: member.username, roomId: room._id });
 			continue;
 		}

Based on previous review feedback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca8f169 and 3d3e7fa.

📒 Files selected for processing (2)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (3 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)
  • createOrUpdateFederatedUser (97-142)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🚀 Notify external services - draft
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (7)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (2)

2-2: LGTM on imports.

The new imports for IUser and createOrUpdateFederatedUser are properly used in the refactored user creation flow below.

Also applies to: 15-15


188-195: LGTM on refactored sender user resolution.

The change from federation.mui-based lookup to username-based lookup with centralized user creation via createOrUpdateFederatedUser is a solid improvement. The fallback pattern correctly handles both existing and new federated users, and error handling at line 197 ensures senderUserId is present before proceeding.

ee/packages/federation-matrix/src/FederationMatrix.ts (5)

83-89: LGTM on domain extraction logic.

This correctly preserves ports and IPv6 literals by using indexOf and substring instead of split, addressing the past review concern. For example, @user:example.org:8448 will correctly extract example.org:8448 as the origin.


97-142: LGTM on federated user upsert logic.

The implementation correctly uses $setOnInsert for createdAt (line 126), addressing the past review concern. The fallback pattern for userId retrieval (line 134) properly handles both insert and update cases. The UserStatus.OFFLINE default is appropriate for newly discovered federated users, as their actual presence will be updated through separate EDU events.


352-372: Verify federated user filtering doesn't skip valid usernames.

The logic correctly uses validateFederatedUsername to filter federated usernames and extractDomainFromMatrixUserId to preserve ports. However, if validateFederatedUsername rejects valid Matrix IDs (as flagged above), some federated users won't be created here.

Ensure the validateFederatedUsername fix is applied so this function doesn't skip valid federated usernames containing / or +.


375-375: LGTM on simplified function signature.

Requiring IUser[] instead of (IUser | string)[] is a cleaner API that ensures all members are resolved before room creation, aligning with the federated user validation pattern.


392-401: LGTM on 1-on-1 DM federated user validation.

The logic correctly validates that the other member is natively federated before creating a direct message room, preventing invalid federation scenarios. The error at line 398 provides clear feedback when attempting to create a federated DM with a non-federated user.

@ggazzo ggazzo requested a review from a team as a code owner October 1, 2025 21:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
ee/packages/federation-matrix/src/events/room.ts (1)

15-18: Normalize federated usernames before calling findOneByUsername.
Line 15: the handler now feeds the raw Matrix identifier (e.g., @alice:remote.example) straight into Users.findOneByUsername. Since createOrUpdateFederatedUser persists our local federated users with the Rocket.Chat username shape (alice@remote.example), this query will always miss remote members and throw “mapped user not found” for any rename/topic/role event emitted by an external homeserver. Please run the incoming ID through validateFederatedUsername and getUsernameServername (and do the same for senderId) before the lookup, or fall back to the former federation.mui matcher.

Also applies to: 31-34, 47-55

ee/packages/federation-matrix/src/events/member.ts (2)

18-23: Validate data.state_key format before user lookup.

The code uses data.state_key directly in findOneByUsername without validating its format. This could fail if the state_key is not in the expected federated username format.

Consider using validateFederatedUsername from the imported helpers:

+	if (!validateFederatedUsername(data.state_key)) {
+		logger.error(`Invalid federated username in state_key: ${data.state_key}`);
+		return;
+	}
 	const affectedUser = await Users.findOneByUsername(data.state_key);

Based on learnings from past reviews.


68-72: Validate data.state_key before using as federated username.

Line 69 casts data.state_key to a federated username type without validation. If the state_key is malformed, this could create invalid user records.

Apply this diff:

+	if (!validateFederatedUsername(data.state_key)) {
+		logger.error(`Invalid federated username in state_key: ${data.state_key}`);
+		return;
+	}
+
 	const insertedId = await createOrUpdateFederatedUser({
 		username: data.state_key as `@${string}:${string}`,
 		origin: serverName,
 		name: data.content.displayname || (data.state_key as `@${string}:${string}`),
 	});
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

51-82: Regex still rejects spec-compliant Matrix localparts containing / or +.

The localpart regex on line 61 excludes / and + characters, which are valid per the Matrix specification. This will reject legitimate federated user IDs like @watch/for/slashes:example.org or @user+tag:domain.com.

Apply this diff to include the missing characters:

-	const localpartRegex = /^(?:[a-z0-9._\-]|=[0-9a-fA-F]{2}){1,255}$/;
+	const localpartRegex = /^(?:[a-z0-9._\-/+]|=[0-9a-fA-F]{2}){1,255}$/;

Based on learnings from past reviews and Matrix specification requirements.

🧹 Nitpick comments (1)
apps/meteor/app/lib/server/functions/createDirectRoom.ts (1)

74-74: Inconsistent user fetching patterns.

Line 74 fetches full user documents without projection, while lines 153-156 fetch users with an explicit projection ({ 'username': 1, 'settings.preferences': 1 }). Both queries serve similar purposes within the same function—preparing member data for subscription creation.

Apply consistent projection patterns. If full documents are required for federation (verify with the script above), update line 153-156:

-		const membersWithPreferences: IUser[] = await Users.find(
-			{ _id: { $in: memberIds } },
-			{ projection: { 'username': 1, 'settings.preferences': 1 } },
-		).toArray();
+		const membersWithPreferences: IUser[] = await Users.find(
+			{ _id: { $in: memberIds } }
+		).toArray();

Otherwise, restore the projection at line 74 to avoid fetching unnecessary fields:

-	const roomMembers = await Users.findUsersByUsernames(membersUsernames).toArray();
+	const roomMembers = await Users.findUsersByUsernames(membersUsernames, {
+		projection: { _id: 1, name: 1, username: 1, customFields: 1 }
+	}).toArray();

Also applies to: 153-156

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d3e7fa and 0db433e.

📒 Files selected for processing (16)
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts (1 hunks)
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts (0 hunks)
  • apps/meteor/ee/server/hooks/federation/index.ts (0 hunks)
  • apps/meteor/lib/callbacks.ts (0 hunks)
  • apps/meteor/server/methods/addRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/addRoomOwner.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomOwner.ts (1 hunks)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (6 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (5 hunks)
  • ee/packages/federation-matrix/src/events/edu.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/index.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/member.ts (4 hunks)
  • ee/packages/federation-matrix/src/events/message.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/room.ts (3 hunks)
  • packages/core-typings/src/IUser.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • apps/meteor/ee/server/hooks/federation/index.ts
  • apps/meteor/lib/callbacks.ts
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • ee/packages/federation-matrix/src/events/edu.ts
  • packages/core-typings/src/IUser.ts
  • ee/packages/federation-matrix/src/events/message.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
🧬 Code graph analysis (4)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (2)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • createOrUpdateFederatedUser (115-160)
  • getUsernameServername (96-108)
packages/core-typings/src/IUser.ts (2)
  • IUser (186-255)
  • isUserNativeFederated (276-277)
ee/packages/federation-matrix/src/FederationMatrix.ts (4)
ee/packages/federation-matrix/src/events/index.ts (1)
  • registerEvents (11-23)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
packages/core-typings/src/IRoom.ts (2)
  • IRoom (21-95)
  • IRoomNativeFederated (114-120)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
  • acceptInvite (287-326)
ee/packages/federation-matrix/src/events/member.ts (2)
ee/packages/federation-matrix/src/events/room.ts (1)
  • room (6-59)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • getUsernameServername (96-108)
  • createOrUpdateFederatedUser (115-160)
ee/packages/federation-matrix/src/events/index.ts (1)
ee/packages/federation-matrix/src/events/member.ts (1)
  • member (83-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: Builds matrix rust bindings against alpine
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (16)
ee/packages/federation-matrix/src/events/index.ts (1)

11-23: LGTM! Services parameter threaded correctly.

The addition of the services parameter to registerEvents and its forwarding to the member event handler is clean and consistent with the broader refactoring to support federated user management.

ee/packages/federation-matrix/src/api/_matrix/invite.ts (4)

2-2: LGTM! Imports aligned with new functionality.

The added imports support the username-based lookup, federated user creation, and native federation checks introduced in this refactor.

Also applies to: 12-12, 15-15


169-282: LGTM! Username-based user resolution correctly implemented.

The refactored joinRoom function properly uses findOneByUsername and falls back to createOrUpdateFederatedUser when needed. The DM and room creation flows handle federated users appropriately.


286-326: LGTM! Local invite validation correctly enforced.

The acceptInvite function properly validates that both inviter and invitee are local (non-native-federated) users before processing the join operation, preventing external federation users from using the internal invite flow.


284-284: Export binding correctly wraps joinRoom with backoff.

The startJoiningRoom export provides a cancellable, backoff-wrapped version of joinRoom for use in the invite processing flow.

ee/packages/federation-matrix/src/events/member.ts (3)

3-3: LGTM! Imports support federated user management.

The new imports provide the necessary utilities and services for username-based user resolution and federated user creation.

Also applies to: 5-5, 7-7


50-62: LGTM! Local user handling correctly checks for existing subscriptions.

The use of getUsernameServername to identify local users and the subscription check (lines 56-59) prevents duplicate room memberships.


83-99: LGTM! Services parameter correctly forwarded.

The updated member function signature accepts and forwards the services parameter to membershipJoinAction, enabling federated user management.

ee/packages/federation-matrix/src/FederationMatrix.ts (8)

83-89: LGTM! Domain extraction correctly preserves ports.

The function correctly extracts the domain portion including any port numbers by slicing from the first colon, addressing previous concerns about port preservation.


96-108: LGTM! Username/server resolution handles local vs. remote users correctly.

The function appropriately strips the domain for local users while preserving the full Matrix ID for remote federated users.


115-160: LGTM! Upsert correctly uses $setOnInsert for createdAt.

The function properly separates mutable fields ($set) from insert-only fields ($setOnInsert), preserving the original creation timestamp on updates. The fallback lookup for the user ID correctly handles both insert and update cases.


313-320: LGTM! Services correctly provided to event registration.

The created method properly passes homeserverServices to registerEvents, enabling federated user management in event handlers.


375-396: LGTM! Federated user provisioning correctly uses validation and domain extraction.

The function properly validates federated usernames, skips existing native federated users, and uses extractDomainFromMatrixUserId to preserve port information when creating users.


398-455: LGTM! Direct message creation correctly validates federation requirements.

The refactored signature (members: IUser[]) and the validation that 1-on-1 DMs require a native federated user (lines 420-422) properly enforce federation constraints.

Note: A past review comment flagged lines 436-438 for silently skipping non-federated members without logging. Consider adding debug logging if this becomes confusing during troubleshooting.


695-717: LGTM! Invite flow correctly handles both federated and local users.

The function appropriately validates federated usernames and routes local users through the acceptInvite flow, aligning with the new internal invite handling.


29-29: LGTM! Export of acceptInvite enables internal invite handling.

Adding acceptInvite to the exports allows the inviteUsersToRoom method to process local user invites correctly.

const roomMembers: IUser[] = await Users.findUsersByUsernames(membersUsernames, {
projection: { _id: 1, name: 1, username: 1, settings: 1, customFields: 1 },
}).toArray();
const roomMembers = await Users.findUsersByUsernames(membersUsernames).toArray();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify necessity of fetching full user documents.

The selective projection was removed, causing all user fields to be fetched instead of only _id, name, username, settings, and customFields. While the PR context mentions federation fixes, the downstream code (lines 76-79, 135-174) only uses _id, name, username, and customFields.

Run the following script to check if federation-related fields (e.g., federated, isRemote, federatedId) are accessed elsewhere in the call chain:

Additionally, please search the web to confirm whether this aligns with Matrix Federation specification requirements for user data:


🏁 Script executed:

#!/bin/bash
# Description: Search for federation field usage in callbacks and related files

# Check afterCreateDirectRoom callback implementations for federation field usage
rg -n -C3 'afterCreateDirectRoom' --type=ts

# Check for federation-related field access patterns in room/subscription creation
rg -nP --type=ts '\b(federated|isRemote|federatedId|federation)\b' -g '*createDirectRoom*' -g '*callbacks*'

Length of output: 2124


🌐 Web query:

Matrix Federation user data requirements for direct messages

💡 Result:

Short answer — what gets shared over federation for direct/1:1 Matrix messages:

  • If it's a room-based 1:1 DM: room PDUs are sent to participant homeservers. That includes the event envelope (room_id, event type, sender user_id, event_id, origin/origin_server_ts, prev_events/auth_events, content). If E2EE is used the content is encrypted, but metadata (room membership, sender ID, room_id, timestamps, and any unencrypted state) is still shared. [1][2]

  • If it's a true send-to-device message (e.g., Olm/Device key/one-to-one keying): servers exchange m.direct_to_device EDUs containing sender user_id, event type, message_id and the encrypted payload — delivered to the target device(s) on the remote homeserver. These are not PDUs in a room graph but are federated EDUs. [1][2]

  • Other federated data related to DMs: profile/profile queries (displayname, avatar_url) are available via federation queries; presence or presence-invite EDUs may be exchanged; media referenced by mxc:// URIs requires homeserver-to-homeserver fetches. Note that redactions remove content but usernames/IDs and replicated copies on remote homeservers can remain. [1][2][3]

Notes: end-to-end encryption protects message content from remote servers only if the client used E2EE; servers still see routing/room/participant metadata. [1][2][3]

Sources: Matrix federation spec (send-to-device / PDUs) and Matrix privacy notice. [1][2][3]


Restore selective projection on Users.findUsersByUsernames
Reinstate { projection: { _id: 1, name: 1, username: 1, settings: 1, customFields: 1 } } on line 74 to avoid fetching full user documents when only those fields are used (callbacks and subscription creation here don’t require any additional properties). This also aligns with the explicit projection applied later at lines 153–156.

🤖 Prompt for AI Agents
In apps/meteor/app/lib/server/functions/createDirectRoom.ts around line 74, the
Users.findUsersByUsernames call currently fetches full user documents; restore
the selective projection by passing { projection: { _id: 1, name: 1, username:
1, settings: 1, customFields: 1 } } as the second argument so only those fields
are returned (matching the explicit projection used later at lines 153–156) to
avoid fetching unnecessary data.

Comment on lines 142 to 167
function runWithBackoff<T extends (...args: any[]) => Promise<void>>(fn: T, delaySec = 5): T & { stop: () => void } {
let timeoutId: NodeJS.Timeout | null = null;
let currentDelay = delaySec;

const execute = async (...args: Parameters<T>) => {
try {
await fn(...args);
currentDelay = delaySec; // Reset delay on success
} catch (e) {
const delay = currentDelay === 625 ? 625 : currentDelay ** 2;
console.log(`error occurred, retrying in ${delay}ms`, e);
currentDelay = delay;
timeoutId = setTimeout(() => execute(...args), delay);
}
};

return Object.assign(
(...args: Parameters<T>) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
execute(...args);
},
{ stop: () => clearTimeout(timeoutId ?? 0) },
) as T & { stop: () => void };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace console.log with proper logger.

Line 152 uses console.log for error logging. This should use the existing logger instance for consistency with the rest of the codebase and proper log level management.

Apply this diff:

 function runWithBackoff<T extends (...args: any[]) => Promise<void>>(fn: T, delaySec = 5): T & { stop: () => void } {
 	let timeoutId: NodeJS.Timeout | null = null;
 	let currentDelay = delaySec;

 	const execute = async (...args: Parameters<T>) => {
 		try {
 			await fn(...args);
 			currentDelay = delaySec; // Reset delay on success
 		} catch (e) {
 			const delay = currentDelay === 625 ? 625 : currentDelay ** 2;
-			console.log(`error occurred, retrying in ${delay}ms`, e);
+			logger.error(`Error occurred, retrying in ${delay}s`, e);
 			currentDelay = delay;
 			timeoutId = setTimeout(() => execute(...args), delay);
 		}
 	};

Note: You'll need to import or have access to a logger instance in this file's scope.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/invite.ts around lines 142 to
167, replace the console.log in the catch block with the project's logger (e.g.
logger.error) so errors follow the codebase logging conventions; ensure a logger
instance is imported or available in this module's scope, call logger.error with
a clear message and the error object (and/or error.stack) to preserve details,
and keep the existing retry/backoff logic unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
ee/packages/federation-matrix/src/events/room.ts (1)

15-15: Validate federated usernames before DB lookup.

All four findOneByUsername calls on lines 15, 31, 47, and 52 use userId or senderId without validating the format. Import and call validateFederatedUsername before each lookup to prevent malformed usernames from reaching the database.

Apply this diff:

+import { validateFederatedUsername } from '../FederationMatrix';
+
 export function room(emitter: Emitter<HomeserverEventSignatures>) {
 	emitter.on('homeserver.matrix.room.name', async (data) => {
 		const { room_id: roomId, name, user_id: userId } = data;
 
 		const localRoomId = await Rooms.findOne({ 'federation.mrid': roomId }, { projection: { _id: 1 } });
 		if (!localRoomId) {
 			throw new Error('mapped room not found');
 		}
 
+		if (!validateFederatedUsername(userId)) {
+			throw new Error(`Invalid federated username format: ${userId}`);
+		}
 		const localUserId = await Users.findOneByUsername(userId, { projection: { _id: 1 } });

Repeat this pattern for userId on line 31 and for both userId (line 47) and senderId (line 52) in the role handler.

Also applies to: 31-31, 47-47, 52-52

ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)

152-152: Replace console.log with logger.

Use the existing logger instance for consistency with the rest of the codebase.

Apply this diff:

 		} catch (e) {
 			const delay = currentDelay === 625 ? 625 : currentDelay ** 2;
-			console.log(`error occurred, retrying in ${delay}ms`, e);
+			logger.error(`Error occurred, retrying in ${delay}s`, { error: e, delay });
 			currentDelay = delay;

Note: You'll need to ensure a logger instance is available in this module's scope.

ee/packages/federation-matrix/src/events/member.ts (2)

19-19: Validate federated usernames before lookup in leave handler.

Lines 19 and 32 call findOneByUsername with data.state_key and data.sender without validating the format. Import and call validateFederatedUsername before each lookup to prevent malformed usernames from reaching the database.

Apply this diff:

+import { createOrUpdateFederatedUser, getUsernameServername, validateFederatedUsername } from '../FederationMatrix';

 async function membershipLeaveAction(data: HomeserverEventSignatures['homeserver.matrix.membership']) {
 	const room = await Rooms.findOne({ 'federation.mrid': data.room_id }, { projection: { _id: 1 } });
 	if (!room) {
 		logger.warn(`No bridged room found for Matrix room_id: ${data.room_id}`);
 		return;
 	}
 
+	if (!validateFederatedUsername(data.state_key)) {
+		logger.error(`Invalid federated username: ${data.state_key}`);
+		return;
+	}
 	const affectedUser = await Users.findOneByUsername(data.state_key);

Repeat for data.sender on line 32.

Also applies to: 32-32


68-72: Validate data.state_key before calling createOrUpdateFederatedUser.

Line 69 casts data.state_key without validating the format. Import and call validateFederatedUsername to ensure the username is well-formed before creating/updating the user.

Apply this diff:

+	if (!validateFederatedUsername(data.state_key)) {
+		logger.error(`Invalid federated username: ${data.state_key}`);
+		return;
+	}
 	const insertedId = await createOrUpdateFederatedUser({
 		username: data.state_key as `@${string}:${string}`,
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

28-59: Broaden localpartRegex to include Matrix-compliant characters.

Line 38 rejects spec-compliant Matrix IDs containing / or + (e.g., @user/name:example.org). Per the Matrix spec, localparts may include lowercase a-z, digits 0-9, and punctuation . _ = - / +. Update the regex to allow these characters.

Apply this diff:

-	const localpartRegex = /^(?:[a-z0-9._\-]|=[0-9a-fA-F]{2}){1,255}$/;
+	const localpartRegex = /^(?:[a-z0-9._\-/+]|=[0-9a-fA-F]{2}){1,255}$/;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0db433e and d444d88.

📒 Files selected for processing (16)
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts (1 hunks)
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts (0 hunks)
  • apps/meteor/ee/server/hooks/federation/index.ts (0 hunks)
  • apps/meteor/lib/callbacks.ts (0 hunks)
  • apps/meteor/server/methods/addRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/addRoomOwner.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomOwner.ts (1 hunks)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (5 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (5 hunks)
  • ee/packages/federation-matrix/src/events/edu.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/index.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/member.ts (4 hunks)
  • ee/packages/federation-matrix/src/events/message.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/room.ts (3 hunks)
  • packages/core-typings/src/IUser.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • apps/meteor/lib/callbacks.ts
  • apps/meteor/ee/server/hooks/federation/index.ts
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
  • apps/meteor/server/methods/addRoomModerator.ts
  • packages/core-typings/src/IUser.ts
  • ee/packages/federation-matrix/src/events/edu.ts
  • apps/meteor/server/methods/removeRoomModerator.ts
  • ee/packages/federation-matrix/src/events/message.ts
  • apps/meteor/server/methods/removeRoomOwner.ts
  • ee/packages/federation-matrix/src/events/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (2)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • createOrUpdateFederatedUser (92-137)
  • getUsernameServername (73-85)
packages/core-typings/src/IUser.ts (2)
  • IUser (186-255)
  • isUserNativeFederated (276-277)
ee/packages/federation-matrix/src/FederationMatrix.ts (3)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
packages/core-typings/src/IRoom.ts (2)
  • IRoom (21-95)
  • IRoomNativeFederated (114-120)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
  • acceptInvite (287-326)
ee/packages/federation-matrix/src/events/member.ts (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • getUsernameServername (73-85)
  • createOrUpdateFederatedUser (92-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (5)
apps/meteor/server/methods/addRoomOwner.ts (1)

27-27: Projection change is correct and required

The federation field is consumed by EE federation hooks (e.g. FederationMatrix in apps/meteor/ee/server/hooks/federation/index.ts) to access room.federation.mrid; including it ensures beforeChangeRoomRole callbacks work correctly.

ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)

286-326: acceptInvite logic looks correct.

The function correctly validates that both inviter and invitee are local (non-native-federated) users and uses getUsernameServername to extract the appropriate username for lookup. The guard clauses ensure only valid local-to-local invites are processed.

ee/packages/federation-matrix/src/FederationMatrix.ts (3)

269-285: ensureFederatedUsersExistLocally implementation looks correct.

The function filters usernames with validateFederatedUsername and calls createOrUpdateFederatedUser only for valid federated usernames. Error handling and logging are appropriate.


288-345: createDirectMessageRoom correctly enforces federation requirements.

The function validates that DM participants are native federated users and throws errors for non-federated members in 1-on-1 DMs. For group DMs, it silently skips non-federated members (line 326-328), which is acceptable given the context.


585-607: inviteUsersToRoom correctly uses validation and acceptInvite.

The function validates federated usernames before inviting and calls acceptInvite for local users. The logic correctly handles both federated and local invite flows.

Comment on lines 203 to 210
const senderUser = await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } });

senderUserId = createdUser.insertedId;
}
const senderUserId =
senderUser?._id ||
(await createOrUpdateFederatedUser({
username: inviteEvent.sender as `@${string}:${string}`,
origin: matrixRoom.origin,
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate inviteEvent.sender before calling createOrUpdateFederatedUser.

The sender username is cast and passed to createOrUpdateFederatedUser without validation. Import and call validateFederatedUsername to ensure the format is correct before creating/updating the user.

Apply this diff:

+	if (!validateFederatedUsername(inviteEvent.sender)) {
+		throw new Error(`Invalid federated username format: ${inviteEvent.sender}`);
+	}
 	const senderUserId =
 		senderUser?._id ||
 		(await createOrUpdateFederatedUser({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const senderUser = await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } });
senderUserId = createdUser.insertedId;
}
const senderUserId =
senderUser?._id ||
(await createOrUpdateFederatedUser({
username: inviteEvent.sender as `@${string}:${string}`,
origin: matrixRoom.origin,
}));
const senderUser = await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } });
if (!validateFederatedUsername(inviteEvent.sender)) {
throw new Error(`Invalid federated username format: ${inviteEvent.sender}`);
}
const senderUserId =
senderUser?._id ||
(await createOrUpdateFederatedUser({
username: inviteEvent.sender as `@${string}:${string}`,
origin: matrixRoom.origin,
}));
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/invite.ts around lines 203 to
210, validate inviteEvent.sender before calling createOrUpdateFederatedUser:
import validateFederatedUsername at the top of the file, check that
inviteEvent.sender is a string and passes validateFederatedUsername (handle
failure by logging/throwing or returning early), and only then call
createOrUpdateFederatedUser with the casted username; otherwise avoid calling
createOrUpdateFederatedUser and ensure senderUserId remains undefined/null or
handle the error path consistently.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (2)

152-153: Use the module logger instead of console.log.

This was already raised earlier and is still unresolved—please wire this through the existing logger so federation retries are logged consistently with the rest of the service. Suggest importing Logger and logging via logger.error.


203-210: Validate inviteEvent.sender before upserting.

We’re still calling createOrUpdateFederatedUser with unchecked input. Bring in validateFederatedUsername and fail fast when the sender MXID is malformed to avoid persisting garbage usernames.

-import { createOrUpdateFederatedUser, getUsernameServername } from '../../FederationMatrix';
+import { createOrUpdateFederatedUser, getUsernameServername, validateFederatedUsername } from '../../FederationMatrix';-	const senderUser = await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } });
+	if (!validateFederatedUsername(inviteEvent.sender)) {
+		throw new Error(`Invalid federated username format: ${inviteEvent.sender}`);
+	}
+	const senderUser = await Users.findOneByUsername(inviteEvent.sender, { projection: { _id: 1 } });
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

38-40: Allow / and + in Matrix localparts.

The checker still rejects spec-compliant MXIDs like @bot/relay:example.org and @room+service:example.org. Please widen the character class accordingly as previously requested.

-	const localpartRegex = /^(?:[a-z0-9._\-]|=[0-9a-fA-F]{2}){1,255}$/;
+	const localpartRegex = /^(?:[a-z0-9._\/+\-]|=[0-9a-fA-F]{2}){1,255}$/;
ee/packages/federation-matrix/src/events/member.ts (1)

43-71: Guard membership usernames with validateFederatedUsername.

We continue to trust data.sender/data.state_key without validation, even though earlier feedback asked to gate them. Import validateFederatedUsername and bail out (or log) when either MXID is malformed before we hit getUsernameServername, Users.findOneByUsername, or createOrUpdateFederatedUser.

-import { createOrUpdateFederatedUser, getUsernameServername } from '../FederationMatrix';
+import { createOrUpdateFederatedUser, getUsernameServername, validateFederatedUsername } from '../FederationMatrix';-	const [username, serverName, isLocal] = getUsernameServername(data.sender, services.config.serverName);
+	if (!validateFederatedUsername(data.sender) || !validateFederatedUsername(data.state_key)) {
+		logger.error(`Invalid federated username in membership event`, { sender: data.sender, stateKey: data.state_key });
+		return;
+	}
+	const [username, serverName, isLocal] = getUsernameServername(data.sender, services.config.serverName);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d444d88 and 8e51515.

📒 Files selected for processing (16)
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts (1 hunks)
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts (0 hunks)
  • apps/meteor/ee/server/hooks/federation/index.ts (0 hunks)
  • apps/meteor/lib/callbacks.ts (0 hunks)
  • apps/meteor/server/methods/addRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/addRoomOwner.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomModerator.ts (1 hunks)
  • apps/meteor/server/methods/removeRoomOwner.ts (1 hunks)
  • ee/packages/federation-matrix/src/FederationMatrix.ts (5 hunks)
  • ee/packages/federation-matrix/src/api/_matrix/invite.ts (5 hunks)
  • ee/packages/federation-matrix/src/events/edu.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/index.ts (2 hunks)
  • ee/packages/federation-matrix/src/events/member.ts (4 hunks)
  • ee/packages/federation-matrix/src/events/message.ts (1 hunks)
  • ee/packages/federation-matrix/src/events/room.ts (3 hunks)
  • packages/core-typings/src/IUser.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • apps/meteor/lib/callbacks.ts
  • apps/meteor/ee/server/hooks/federation/index.ts
  • apps/meteor/app/lib/server/methods/addUsersToRoom.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/core-typings/src/IUser.ts
  • apps/meteor/server/methods/addRoomModerator.ts
  • apps/meteor/app/lib/server/functions/createDirectRoom.ts
  • ee/packages/federation-matrix/src/events/index.ts
  • ee/packages/federation-matrix/src/events/room.ts
  • ee/packages/federation-matrix/src/events/edu.ts
  • ee/packages/federation-matrix/src/events/message.ts
🧰 Additional context used
🧬 Code graph analysis (3)
ee/packages/federation-matrix/src/FederationMatrix.ts (3)
packages/core-typings/src/IUser.ts (2)
  • isUserNativeFederated (276-277)
  • IUser (186-255)
packages/core-typings/src/IRoom.ts (2)
  • IRoom (21-95)
  • IRoomNativeFederated (114-120)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (1)
  • acceptInvite (287-326)
ee/packages/federation-matrix/src/api/_matrix/invite.ts (2)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • createOrUpdateFederatedUser (92-137)
  • getUsernameServername (73-85)
packages/core-typings/src/IUser.ts (2)
  • IUser (186-255)
  • isUserNativeFederated (276-277)
ee/packages/federation-matrix/src/events/member.ts (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (2)
  • getUsernameServername (73-85)
  • createOrUpdateFederatedUser (92-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
apps/meteor/server/methods/addRoomOwner.ts (1)

27-27: No action needed—federation is projected consistently across all add/remove room owner/moderator methods.

apps/meteor/server/methods/removeRoomOwner.ts (1)

25-25: Retain federation in the projection.

The added federation: 1 is required by downstream federation logic—EE’s beforeChangeRoomRole hooks (apps/meteor/ee/server/hooks/federation/index.ts) and the isRoomNativeFederated type guard both access room.federation. No changes needed.

Comment on lines 301 to 325
const inviter = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(
getUsernameServername(inviteEvent.sender, services.config.serverName)[0],
{
projection: { _id: 1, username: 1 },
},
);

if (!inviter) {
throw new Error('Sender user ID not found');
}
if (isUserNativeFederated(inviter)) {
throw new Error('Sender user is native federated');
}

const user = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(username, { projection: { _id: 1 } });

// we cannot accept invites from users that are external
if (!user) {
throw new Error('User not found');
}
if (isUserNativeFederated(user)) {
throw new Error('User is native federated');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Include federation fields when checking for native federated users.

Both inviter and user queries strip federated/federation from the projection, so isUserNativeFederated(...) always returns false and the guard never trips. Either pull those fields or drop the restrictive generic so the full document comes back.

-	const inviter = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(
+	const inviter = await Users.findOneByUsername(
 		getUsernameServername(inviteEvent.sender, services.config.serverName)[0],
 		{
-			projection: { _id: 1, username: 1 },
+			projection: { _id: 1, username: 1, federated: 1, federation: 1 },
 		},
 	);
…
-	const user = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(username, { projection: { _id: 1 } });
+	const user = await Users.findOneByUsername(username, {
+		projection: { _id: 1, username: 1, federated: 1, federation: 1 },
+	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const inviter = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(
getUsernameServername(inviteEvent.sender, services.config.serverName)[0],
{
projection: { _id: 1, username: 1 },
},
);
if (!inviter) {
throw new Error('Sender user ID not found');
}
if (isUserNativeFederated(inviter)) {
throw new Error('Sender user is native federated');
}
const user = await Users.findOneByUsername<Pick<IUser, '_id' | 'username'>>(username, { projection: { _id: 1 } });
// we cannot accept invites from users that are external
if (!user) {
throw new Error('User not found');
}
if (isUserNativeFederated(user)) {
throw new Error('User is native federated');
}
const inviter = await Users.findOneByUsername(
getUsernameServername(inviteEvent.sender, services.config.serverName)[0],
{
projection: { _id: 1, username: 1, federated: 1, federation: 1 },
},
);
if (!inviter) {
throw new Error('Sender user ID not found');
}
if (isUserNativeFederated(inviter)) {
throw new Error('Sender user is native federated');
}
const user = await Users.findOneByUsername(username, {
projection: { _id: 1, username: 1, federated: 1, federation: 1 },
});
// we cannot accept invites from users that are external
if (!user) {
throw new Error('User not found');
}
if (isUserNativeFederated(user)) {
throw new Error('User is native federated');
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/invite.ts around lines 301 to
323, the inviter and user queries explicitly project only _id and username so
the federated/federation fields are missing and isUserNativeFederated(...) can
never detect native federated users; update the queries to include the
federation-related fields (e.g., federated and/or federation) in the projection
or remove the restrictive generic/explicit projection so the full user document
is returned, then re-run the isUserNativeFederated checks.

Comment on lines +41 to +56
const [domain, port] = domainAndPort.split(':');

const hostnameRegex = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)*$/i;
const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/;
const ipv6Regex = /^\[([0-9a-f:.]+)\]$/i;

if (!(hostnameRegex.test(domain) || ipv4Regex.test(domain) || ipv6Regex.test(domain))) {
return false;
}

if (port !== undefined) {
const portNum = Number(port);
if (!/^[0-9]+$/.test(port) || portNum < 1 || portNum > 65535) {
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle IPv6 homeservers correctly.

domainAndPort.split(':') shatters IPv6 literals like @user:[2001:db8::1], so domain becomes '[2001' and the regex test fails even though the MXID is valid. Parse the host/port without blindly splitting on every colon—for example, detect the bracketed form first, peel off the optional :port, then validate the host.

-	const [domain, port] = domainAndPort.split(':');
+	let domain = domainAndPort;
+	let port: string | undefined;
+
+	if (domainAndPort.startsWith('[')) {
+		const closingBracket = domainAndPort.indexOf(']');
+		if (closingBracket === -1) {
+			return false;
+		}
+		domain = domainAndPort.slice(0, closingBracket + 1);
+		const remainder = domainAndPort.slice(closingBracket + 1);
+		if (remainder) {
+			if (!remainder.startsWith(':')) {
+				return false;
+			}
+			port = remainder.slice(1);
+		}
+	} else {
+		const colonIndex = domainAndPort.indexOf(':');
+		if (colonIndex !== -1) {
+			domain = domainAndPort.slice(0, colonIndex);
+			port = domainAndPort.slice(colonIndex + 1);
+		}
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [domain, port] = domainAndPort.split(':');
const hostnameRegex = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)*$/i;
const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/;
const ipv6Regex = /^\[([0-9a-f:.]+)\]$/i;
if (!(hostnameRegex.test(domain) || ipv4Regex.test(domain) || ipv6Regex.test(domain))) {
return false;
}
if (port !== undefined) {
const portNum = Number(port);
if (!/^[0-9]+$/.test(port) || portNum < 1 || portNum > 65535) {
return false;
}
}
let domain = domainAndPort;
let port: string | undefined;
if (domainAndPort.startsWith('[')) {
const closingBracket = domainAndPort.indexOf(']');
if (closingBracket === -1) {
return false;
}
domain = domainAndPort.slice(0, closingBracket + 1);
const remainder = domainAndPort.slice(closingBracket + 1);
if (remainder) {
if (!remainder.startsWith(':')) {
return false;
}
port = remainder.slice(1);
}
} else {
const colonIndex = domainAndPort.indexOf(':');
if (colonIndex !== -1) {
domain = domainAndPort.slice(0, colonIndex);
port = domainAndPort.slice(colonIndex + 1);
}
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/FederationMatrix.ts around lines 41 to 56,
the current domain/port parsing uses domainAndPort.split(':') which breaks IPv6
bracketed literals; update parsing to first check for a leading '[' (bracketed
IPv6) and if present find the matching ']' to extract the full IPv6 host and
then optionally strip a single leading ':' plus port after the closing ']',
otherwise for non-bracketed input split only on the last ':' to separate an
optional port from the host; after extracting host and optional port, run the
existing hostnameRegex / ipv4Regex / ipv6Regex validation on the host and
validate the port is all digits and between 1 and 65535, returning false on any
validation failure.

@sampaiodiego sampaiodiego merged commit ccfe416 into release-7.11.0 Oct 2, 2025
18 of 19 checks passed
@sampaiodiego sampaiodiego deleted the fix/old-fed branch October 2, 2025 20:43
ricardogarim pushed a commit that referenced this pull request Oct 7, 2025
Co-authored-by: Diego Sampaio <chinello@gmail.com>
ggazzo added a commit that referenced this pull request Oct 8, 2025
Co-authored-by: Diego Sampaio <chinello@gmail.com>
ricardogarim pushed a commit that referenced this pull request Oct 8, 2025
Co-authored-by: Diego Sampaio <chinello@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants