Skip to content

Conversation

@ricardogarim
Copy link
Contributor

@ricardogarim ricardogarim commented Nov 17, 2025

Task: FB-44
Related PR: RocketChat/Rocket.Chat#37532

Summary by CodeRabbit

  • New Features

    • Leave flow: create, prepare and broadcast leave events; new endpoints to make and send leaves.
    • Invite actions: accept and reject invite operations exposed.
  • Improvements

    • Improved invite processing across resident and remote servers with better state handling and persistence.
    • Enhanced event persistence for outlier/remote invites and clearer join/leave workflows.
  • Tests

    • Added tests covering accept/reject invite scenarios and updated join/leave test flows.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Walkthrough

Refactors invite and leave flows: adds leave-related methods (makeLeave, sendLeave), adds accept/reject invite paths, injects EventRepository into InviteService and adds EventRepository.insertOutlierEvent, updates RoomService join/leave signatures and controller call sites to derive server info from events.

Changes

Cohort / File(s) Summary
Event Repository
packages/federation-sdk/src/repositories/event.repository.ts
Added insertOutlierEvent(eventId: EventID, event: Pdu, origin: string): Promise<InsertOneResult<EventStore>>; imported InsertOneResult from mongodb.
SDK public surface
packages/federation-sdk/src/sdk.ts
Added delegating methods makeLeave, sendLeave, acceptInvite, rejectInvite; marked joinUser as deprecated.
Federation service
packages/federation-sdk/src/services/federation.service.ts
Added makeLeave(domain, roomId, userId) to fetch leave template and sendLeave(leaveEvent) to send leave events to resident servers.
Invite service
packages/federation-sdk/src/services/invite.service.ts
Injected EventRepository via DI; changed processInvite signature to accept event/eventId/roomVersion/strippedStateEvents; derives resident server from event; for non-hosts signs event, stores unsigned.invite_room_state, checks for m.room.create and either handles PDU or calls insertOutlierEvent, then emits membership.
Room service
packages/federation-sdk/src/services/room.service.ts
Added makeLeave, sendLeave, acceptInvite, rejectInvite; updated joinUser(roomId, sender, userId) signature; refactored leave flow to use makeLeave and unified local/remote leave handling.
Invite controller
packages/homeserver/src/controllers/federation/invite.controller.ts
Stopped passing roomId and authenticatedServer to processInvite; now forwards eventId, event, room_version and invite_room_state only.
Room service tests
packages/federation-sdk/src/services/room.service.spec.ts
Test DI adjusted to register ConfigService and resolve RoomService; replaced some federationSDK calls with roomService and added tests for acceptInvite/rejectInvite.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RoomService
    participant InviteService
    participant EventRepository
    participant StateService
    participant FederationService

    rect rgb(220,240,255)
    Note over Client,InviteService: Invite processing (non-host)
    Client->>InviteService: processInvite(event, eventId, room_version, strippedState)
    InviteService->>InviteService: derive residentServer from event
    InviteService->>StateService: signEvent(inviteEvent)
    StateService-->>InviteService: signed event
    InviteService->>EventRepository: find existing m.room.create for room
    alt m.room.create exists
      EventRepository-->>InviteService: found create
      InviteService->>StateService: handlePdu(inviteEvent)
      StateService-->>InviteService: processed
    else no create (outlier)
      InviteService->>EventRepository: insertOutlierEvent(eventId, inviteEvent, origin)
      EventRepository-->>InviteService: insert result
    end
    InviteService->>Client: emit membership event
    end

    rect rgb(240,255,240)
    Note over Client,FederationService: Reject invite for remote room (high-level)
    Client->>RoomService: rejectInvite(roomId, userId)
    RoomService->>FederationService: makeLeave(domain, roomId, userId)
    FederationService-->>RoomService: { event: leaveEvent, room_version }
    RoomService->>StateService: signEvent(leaveEvent)
    StateService-->>RoomService: signed
    RoomService->>FederationService: sendLeave(leaveEvent)
    FederationService-->>RoomService: response
    RoomService->>Client: reject result
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to InviteService changes (resident server derivation, unsigned.invite_room_state handling, when to call insertOutlierEvent vs. handlePdu).
  • Verify EventRepository.insertOutlierEvent fields (stateId/nextEventId empty, outlier: true, partial: false).
  • Check RoomService new methods for correct signing, delegation to federation, and updated joinUser call sites.
  • Confirm tests reflect behavioral changes and DI registrations are correct.

Possibly related PRs

Suggested reviewers

  • ggazzo
  • rodrigok

"I hopped through code with one bright spring,
Inserted outliers, made invites sing.
Leaves now travel, signed and neat,
Rooms accept, or take their seat —
A carrot cheer for every merged thing!" 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
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.
Linked Issues check ❓ Inconclusive The linked issue FB-44 lacks specific coding requirements or acceptance criteria to validate whether all implementation details have been addressed. Review the issue FB-44 directly to confirm all backend implementation requirements for invitation handling have been met in the code changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add room invites support' clearly and concisely summarizes the main change, which is adding support for room invitations across multiple services.
Out of Scope Changes check ✅ Passed All changes are cohesively related to invitation handling: new invite endpoints, leave functionality, accept/reject invites, and supporting repository methods.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/invites

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.

@ricardogarim ricardogarim marked this pull request as ready for review November 17, 2025 11:09
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

🧹 Nitpick comments (3)
packages/federation-sdk/src/services/federation.service.ts (1)

118-143: LGTM! Consider extracting resident server logic.

The sendLeave implementation is correct and follows the same patterns as sendJoin. The error handling, validation, and request structure are all appropriate.

The resident server extraction logic (leaveEvent.roomId.split(':').pop()) is duplicated in sendJoin (line 80) and here (line 125). Consider extracting this into a helper method to reduce duplication:

private extractResidentServer(roomId: string): string {
  const residentServer = roomId.split(':').pop();
  if (!residentServer) {
    throw new Error(`invalid room_id ${roomId}, no server_name part`);
  }
  return residentServer;
}

Then use it in both methods:

-    const residentServer = leaveEvent.roomId.split(':').pop();
-
-    if (!residentServer) {
-      this.logger.debug({ msg: 'invalid room_id', event: leaveEvent.event });
-      throw new Error(
-        `invalid room_id ${leaveEvent.roomId}, no server_name part`,
-      );
-    }
+    const residentServer = this.extractResidentServer(leaveEvent.roomId);
packages/federation-sdk/src/services/invite.service.ts (2)

236-243: Make “no state yet” for partial invites explicit instead of '' as StateID

Using '' as StateID as the stateId when calling forceInsertOrUpdateEventWithStateId relies on a type cast to bypass the StateID schema and may store an invalid value in persistence or mislead downstream code that assumes stateId is always a real identifier.

If partial invites are expected to have “no state yet”, consider modeling that explicitly instead of abusing an empty string, for example by:

  • Allowing stateId to be nullable/optional in forceInsertOrUpdateEventWithStateId and passing null/undefined, or
  • Introducing a well‑named sentinel (e.g., NO_STATE_ID) that is documented and shared across the codebase.

That would improve type safety and make the intent of partial invite persistence clearer.


245-254: Membership emission payload and placement look correct

Emitting homeserver.matrix.membership after the invite is persisted (either via stateService.handlePdu for host rooms or EventRepository for remote rooms) ensures consumers see a consistent view. The payload fields (event_id, event, room_id, sender, state_key, origin_server_ts, content, stripped_state) line up with the described membership signature and carry both the invite content (including is_direct when present) and the stripped state used for authorization/UI.

Only minor note: state_key on a membership event should always be defined; the ?? '' fallback may hide upstream issues. If you want to fail fast on malformed events, you could assert/log instead of silently substituting an empty string, but this is optional.

📜 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 5cc3127 and aa1a23c.

📒 Files selected for processing (10)
  • packages/federation-sdk/src/index.ts (1 hunks)
  • packages/federation-sdk/src/repositories/event-staging.repository.ts (1 hunks)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/state.service.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/rooms.controller.ts (5 hunks)
  • packages/room/src/manager/room-state.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/homeserver/src/controllers/federation/rooms.controller.ts
  • packages/room/src/manager/room-state.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/state.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (6)
packages/federation-sdk/src/services/federation.service.ts (3)
packages/room/src/manager/event-wrapper.ts (1)
  • roomId (101-103)
packages/room/src/types/v3-11.ts (1)
  • Pdu (828-828)
packages/federation-sdk/src/specs/federation-api.ts (1)
  • FederationEndpoints (24-71)
packages/federation-sdk/src/services/room.service.ts (3)
packages/room/src/types/_common.ts (3)
  • EventID (8-8)
  • UserID (20-20)
  • StateID (12-12)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
packages/room/src/manager/event-wrapper.ts (2)
  • roomId (101-103)
  • extractDomainFromId (23-29)
packages/federation-sdk/src/repositories/event.repository.ts (2)
packages/room/src/types/_common.ts (2)
  • EventID (8-8)
  • StateID (12-12)
packages/room/src/types/v3-11.ts (1)
  • Pdu (828-828)
packages/federation-sdk/src/services/state.service.ts (2)
packages/room/src/types/_common.ts (1)
  • RoomID (16-16)
packages/room/src/manager/event-wrapper.ts (1)
  • roomId (101-103)
packages/federation-sdk/src/index.ts (1)
packages/room/src/types/_common.ts (1)
  • PduForType (26-26)
packages/federation-sdk/src/services/invite.service.ts (1)
packages/room/src/types/_common.ts (1)
  • StateID (12-12)
🔇 Additional comments (13)
packages/federation-sdk/src/services/federation.service.ts (1)

101-116: The review comment is based on a misunderstanding of the Matrix Federation API spec.

The GET /_matrix/federation/v1/make_leave/{roomId}/{userId} endpoint does not take a room-version query parameter; it simply returns an event template and a room_version field in the response. This is a different design pattern than makeJoin, which supports version negotiation via a ver query parameter. The current implementation of makeLeave correctly omits the version parameter and instead receives the room_version from the response—this is the intended behavior per the spec.

Likely an incorrect or invalid review comment.

packages/federation-sdk/src/sdk.ts (1)

148-154: LGTM!

The new acceptInvite and rejectInvite methods follow the established delegation pattern consistently with other SDK methods. The type-safe parameter forwarding is appropriate.

packages/federation-sdk/src/services/state.service.ts (1)

1131-1148: LGTM! Safe handling of optional room names.

The changes correctly handle the case where room names may be undefined (e.g., for DM rooms). The optional type annotation and ?? '' fallback ensure type safety and provide a sensible default.

Based on learnings

packages/federation-sdk/src/repositories/event.repository.ts (1)

408-429: LGTM! Clear separation of concerns.

The new forceInsertOrUpdateEventWithStateId method provides a variant that allows overwriting the event on updates (by placing event in $set instead of $setOnInsert). The method name clearly conveys this behavior, and it's appropriately used for remote processing flows where event data may need to be updated.

packages/homeserver/src/controllers/federation/rooms.controller.ts (2)

8-30: LGTM! Safe handling of optional room names.

The changes ensure that the name field is always present in public room objects by providing an empty string fallback when the name is undefined. This maintains API contract consistency.


54-89: LGTM! Safe null handling in filter logic.

The optional chaining on Line 74 (?.toLowerCase()) prevents runtime errors when name is undefined during filtering. Combined with the fallback on Line 87, this ensures robust handling of missing room names.

packages/room/src/manager/room-state.ts (1)

65-76: LGTM! Correct handling of DM rooms without name events.

The change to return string | undefined is appropriate for DM rooms, which don't have name events. The clarifying comment is helpful, and downstream consumers correctly handle the undefined case with fallback values.

packages/federation-sdk/src/index.ts (1)

213-238: LGTM! Appropriate extension of membership event type.

The addition of is_direct for DM identification and stripped_state for invite context follows standard Matrix patterns. Both fields are optional, maintaining backward compatibility while enabling richer membership state representation.

packages/federation-sdk/src/services/room.service.ts (2)

1054-1068: Verify room version handling in acceptInvite.

Line 1061 uses PersistentEventFactory.defaultRoomVersion to reconstruct the invite event. However, the invite event should already have room version information in the stored event. Using a hardcoded default version could cause issues if the room uses a different version.

Consider extracting the room version from the stored event or fetching it from the room:

-		const roomVersion = PersistentEventFactory.defaultRoomVersion;
+		const createEvent = await this.eventRepository.findByRoomIdAndType(
+			inviteEventStore.event.room_id,
+			'm.room.create',
+		);
+		const roomVersion = createEvent?.event.content?.room_version ?? PersistentEventFactory.defaultRoomVersion;
		const inviteEvent = PersistentEventFactory.createFromRawEvent(
			inviteEventStore.event,
			roomVersion,
		);

1070-1115: LGTM! Correct invite rejection flow.

The rejectInvite method correctly implements the leave flow for rejected invites:

  1. Validates the invite exists
  2. Gets a leave template from the inviting server via makeLeave
  3. Signs and sends the leave event
  4. Stores locally with empty StateID and partial=true (appropriate since we don't have full room state)
  5. Emits a membership event for local tracking

The use of forceInsertOrUpdateEventWithStateId with an empty StateID is appropriate for this remote rejection scenario where full state resolution isn't needed.

packages/federation-sdk/src/repositories/event-staging.repository.ts (1)

29-48: Based on my investigation into the codebase and Matrix federation protocol, the original review comment's concern is valid and requires clarification from the developer.

Verify the origin field overwrite behavior.

In Matrix federation, it is the responsibility of the originating server of a PDU to deliver that event to its recipient servers. Moving origin from the filter to the $set block means that if the same eventId is received from different federation sources, the origin will be overwritten rather than preserved.

Based on the two call sites found:

  • event.service.ts:213 passes an explicit origin value
  • room.service.ts:991 passes residentServer as origin

These varying origin values across calls suggest events with the same ID could arrive from different sources. The current implementation will overwrite the origin field on subsequent upserts, which may be unintended if your use case requires tracking the original source.

Consider clarifying the intent:

  • If you want to preserve the first origin: use $setOnInsert for the origin field instead of $set
  • If you intentionally want to track the latest origin: add a comment explaining this behavior for maintainability
packages/federation-sdk/src/services/invite.service.ts (2)

9-19: Constructor DI wiring for EventEmitterService and EventRepository looks sound

Adding EventEmitterService as a plain constructor dependency and EventRepository via @inject(delay(() => EventRepository)) is consistent with tsyringe patterns for avoiding circular deps. The new imports and constructor parameters are coherent and keep InviteService responsibilities clear.

Also applies to: 38-46


152-163: Including m.room.member in strippedStateEvents types is appropriate

Extending the PduForType<...> union with 'm.room.member' for strippedStateEvents in both shouldProcessInvite and processInvite matches the new usage where stripped membership data is forwarded to listeners. The existing logic still only inspects join_rules and encryption, so behavior is unchanged while the type becomes more accurate.

Also applies to: 193-203

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: 5

♻️ Duplicate comments (1)
packages/federation-sdk/src/services/invite.service.ts (1)

51-74: Code duplication: Avatar URL construction is duplicated across multiple services.

This getAvatarUrlForUser method duplicates the same logic found in room.service.ts and the avatar URL construction in profiles.service.ts. Please see the refactoring suggestion in the review of profiles.service.ts to extract this into a shared method.

🧹 Nitpick comments (4)
packages/federation-sdk/src/services/profiles.service.ts (1)

28-57: Input validation is handled at DTO layer; the edge case is already mitigated.

The UsernameDto pattern (^@[A-Za-z0-9_=\\/.+-]+:(.+)$) enforces that userId must contain : when present, so the HTTP endpoint prevents malformed input before queryProfile is called. The current code already handles the edge case defensively—if serverName becomes undefined, the check on line 36 (serverName !== this.configService.serverName) returns null.

The suggested refactoring improves explicitness by validating the split result, which would be a code clarity improvement but is not required to prevent the edge case.

packages/federation-sdk/src/services/room.service.ts (3)

69-89: Improve username extraction robustness and clarify avatarETag fallback logic.

Several concerns with this implementation:

  1. Line 79: The username extraction userId.split(':')[0]?.slice(1) assumes userId format @username:domain but doesn't validate. If the format is unexpected or the result is empty, this could lead to incorrect avatar URLs.

  2. Line 86: The fallback user?.avatarETag || username will use username when avatarETag is an empty string (which is falsy). If an empty avatarETag has a specific meaning (e.g., no avatar set), this fallback behavior might be unintended.

  3. No error handling: If userRepository.findByUsername throws, the entire flow will fail without graceful degradation.

Consider these improvements:

 private async getAvatarUrlForUser(
   userId: UserID,
 ): Promise<string | undefined> {
   const userDomain = extractDomainFromId(userId);
   const localDomain = this.configService.serverName;

   if (userDomain !== localDomain) {
     return undefined;
   }

-  const username = userId.split(':')[0]?.slice(1);
+  // Extract username from userId format: @username:domain
+  const match = userId.match(/^@([^:]+):/);
+  const username = match?.[1];
   if (!username) {
     return undefined;
   }

-  // Fetch user to get avatarETag
-  const user = await this.userRepository.findByUsername(username);
-  const avatarIdentifier = user?.avatarETag || username;
+  try {
+    const user = await this.userRepository.findByUsername(username);
+    // Use avatarETag if explicitly set, otherwise fallback to username
+    const avatarIdentifier = user?.avatarETag ?? username;
-
-  return `mxc://${localDomain}/avatar${avatarIdentifier}`;
+    return `mxc://${localDomain}/avatar${avatarIdentifier}`;
+  } catch (error) {
+    this.logger.warn(`Failed to fetch user ${username} for avatar URL`, error);
+    return undefined;
+  }
 }

281-292: Extract username parsing to reduce duplication.

The username extraction logic on line 282 (username.split(':')[0]?.slice(1)) is duplicated in multiple places throughout this file (lines 282, 902, 1570, 1689). This creates maintenance burden and inconsistency risk.

Consider extracting this to a helper method:

private extractUsername(userId: UserID): string | undefined {
  const match = userId.match(/^@([^:]+):/);
  return match?.[1];
}

Then use it consistently:

- const displayname = username.split(':')[0]?.slice(1);
+ const displayname = this.extractUsername(username);

901-934: Reduce duplication in event content construction.

The membership content is constructed twice - once for the event (lines 907-911) and again for the emitter (lines 930-934). This duplication is error-prone and makes maintenance harder.

Consider reusing the event content:

  const membershipEvent = await stateService.buildEvent<'m.room.member'>(
    {
      type: 'm.room.member',
      content: {
        membership: 'join',
        ...(displayname && { displayname }),
        ...(avatarUrl && { avatar_url: avatarUrl }),
      },
      // ... rest of config
    },
    PersistentEventFactory.defaultRoomVersion,
  );

  await stateService.handlePdu(membershipEvent);

  this.eventEmitterService.emit('homeserver.matrix.membership', {
    event_id: membershipEvent.eventId,
    event: membershipEvent.event,
    room_id: roomId,
    state_key: userId,
-   content: {
-     membership: 'join',
-     ...(displayname && { displayname }),
-     ...(avatarUrl && { avatar_url: avatarUrl }),
-   },
+   content: membershipEvent.event.content,
    sender: userId,
    origin_server_ts: Date.now(),
  });
📜 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 aa1a23c and 57c7fd6.

📒 Files selected for processing (16)
  • packages/federation-sdk/src/index.ts (3 hunks)
  • packages/federation-sdk/src/repositories/event-staging.repository.ts (1 hunks)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/repositories/user.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (5 hunks)
  • packages/federation-sdk/src/services/event-authorization.service.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (7 hunks)
  • packages/federation-sdk/src/services/profiles.service.ts (3 hunks)
  • packages/federation-sdk/src/services/room.service.ts (11 hunks)
  • packages/federation-sdk/src/services/state.service.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/profiles.controller.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/rooms.controller.ts (5 hunks)
  • packages/homeserver/src/dtos/federation/error.dto.ts (1 hunks)
  • packages/homeserver/src/dtos/federation/profiles.dto.ts (1 hunks)
  • packages/room/src/manager/room-state.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/federation-sdk/src/services/state.service.ts
  • packages/federation-sdk/src/index.ts
  • packages/homeserver/src/controllers/federation/rooms.controller.ts
  • packages/room/src/manager/room-state.ts
  • packages/federation-sdk/src/repositories/event-staging.repository.ts
  • packages/federation-sdk/src/services/federation.service.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/profiles.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (5)
packages/federation-sdk/src/services/room.service.ts (3)
packages/room/src/types/_common.ts (4)
  • UserID (20-20)
  • EventID (8-8)
  • StateID (12-12)
  • RoomID (16-16)
packages/room/src/manager/event-wrapper.ts (2)
  • extractDomainFromId (23-29)
  • roomId (101-103)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
packages/homeserver/src/controllers/federation/profiles.controller.ts (3)
packages/federation-sdk/src/index.ts (1)
  • federationSDK (348-348)
packages/homeserver/src/dtos/federation/profiles.dto.ts (2)
  • QueryProfileQueryDto (9-14)
  • QueryProfileResponseDto (16-19)
packages/homeserver/src/dtos/federation/error.dto.ts (2)
  • FederationErrorResponseDto (3-12)
  • FederationErrorResponseDto (14-16)
packages/federation-sdk/src/services/profiles.service.ts (1)
packages/federation-sdk/src/services/config.service.ts (1)
  • serverName (108-110)
packages/federation-sdk/src/services/invite.service.ts (2)
packages/room/src/types/_common.ts (2)
  • UserID (20-20)
  • StateID (12-12)
packages/room/src/manager/event-wrapper.ts (1)
  • extractDomainFromId (23-29)
packages/federation-sdk/src/repositories/event.repository.ts (2)
packages/room/src/types/_common.ts (2)
  • EventID (8-8)
  • StateID (12-12)
packages/room/src/types/v3-11.ts (1)
  • Pdu (828-828)
🔇 Additional comments (13)
packages/homeserver/src/dtos/federation/profiles.dto.ts (1)

17-17: Breaking change verified: displayname no longer accepts null.

The DTO change is confirmed at line 17 (profiles.dto.ts), removing the nullable union from the response schema. However, the implementation already handles this correctly: the profiles service (line 124) uses conditional spreading to omit the displayname field when it's falsy, preventing null values from being returned. This aligns the runtime behavior with the new schema.

While this is a breaking change for API consumers expecting { displayname: null }, no code modifications are needed—the implementation already conforms to the new contract.

packages/homeserver/src/dtos/federation/error.dto.ts (1)

9-9: LGTM!

The addition of M_NOT_FOUND error code properly supports 404 responses in the federation API, aligning with Matrix specification error handling.

packages/homeserver/src/controllers/federation/profiles.controller.ts (1)

31-51: LGTM!

The updated profile query handler properly handles the not-found case by returning a 404 response with M_NOT_FOUND, and correctly filters the response to include only the relevant fields (displayname and avatar_url). This aligns with Matrix federation specification for profile queries.

packages/federation-sdk/src/repositories/user.repository.ts (2)

27-47: LGTM! The query correctly filters for local users only.

The findByUsername method appropriately filters for non-federated users using the $or condition, ensuring only local users are returned. The explicit projection is also good practice for performance.


4-19: Field naming inconsistency is intentional and correct—no changes needed.

The User type uses avatarUrl (camelCase) as part of the internal TypeScript domain model. When federation services communicate with the Matrix protocol, they explicitly convert to avatar_url (snake_case)—which is mandated by the Matrix specification. This boundary mapping is visible throughout the codebase (e.g., ...(avatarUrl && { avatar_url: avatarUrl })). The design aligns internal model naming with the external protocol requirements, which is the correct architectural approach.

packages/federation-sdk/src/sdk.ts (1)

41-41: LGTM! The new delegation methods follow the established pattern.

The additions properly expose the new room service methods (updateMemberProfile, acceptInvite, rejectInvite, updateUserProfile) and event emitter functionality (emit) through the SDK's public API, following the existing delegation pattern consistently.

Also applies to: 104-108, 156-168, 319-321

packages/federation-sdk/src/services/invite.service.ts (2)

276-285: LGTM! Event emission provides good extensibility.

The event emission after invite processing enables event-driven reactions to membership changes, which is a good architectural pattern for extensibility.


267-274: Confirm: Non-host invite persistence pattern is correct.

The empty StateID and partial=true pattern is intentional and properly handles federation scenarios:

  1. Empty StateID is correct — it's a sentinel value indicating events without full state context, used consistently across invite, leave, and room creation flows.

  2. partial=true implications — marks events for state completion; the _needsProcessing method propagates this flag through the state graph, and events remain tracked via findPartialsByRoomId.

  3. Events are resolved later — confirmed by the room join workflow: isRoomStatePartial() detects incomplete state, getPartialEvents() retrieves them, _fetchFullBranch() fetches missing branches, and updateStateId() transitions events from empty to actual StateID during state resolution (state.service.ts:979).

This is a well-designed pattern for non-host servers receiving invites with incomplete state.

packages/federation-sdk/src/repositories/event.repository.ts (1)

408-429: No actionable issues found. Current usages are appropriate for federation event handling.

The verification shows both usages of forceInsertOrUpdateEventWithStateId are in federation contexts where events lack local state context (stateId='', partial=true). In invite.service.ts (line 268) and room.service.ts (line 1146), the method is called only when handling remote server events—a scenario where the overwrite behavior is intentional. Events marked as partial=true are explicitly designed to be re-processed once state becomes available, as evidenced by the state service logic and findPartialsByRoomId method. The method naming with "force" prefix and consistent parameter patterns across both usages demonstrate this distinction is well-understood at call sites.

packages/federation-sdk/src/services/room.service.ts (4)

30-30: LGTM: New dependencies properly integrated.

The StateID import and UserRepository injection are appropriately added to support the new avatar and profile functionality.

Also applies to: 37-37, 62-63


1167-1215: LGTM: Profile update relies on authorization checks in handlePdu.

The method doesn't pre-validate that the user is a member of the room (unlike updateMemberProfile at lines 757-762), but the handlePdu call at line 1195 will perform proper authorization checks. If the user lacks permission or isn't a member, the event will be rejected (checked at lines 1210-1212).


1571-1571: LGTM: Avatar URL integration is consistent.

The avatar URL fetching and conditional inclusion in membership events follows the same pattern established in createRoom and joinUser.

Also applies to: 1581-1581, 1690-1690, 1700-1700


1146-1151: The empty StateID usage is correct and follows established codebase patterns.

Verification confirms that using '' as StateID for leave events during invite rejection is intentional and consistent throughout the codebase. The StateID type is a branded string, and empty StateID serves as a sentinel value for events that don't participate in state resolution. This is explicitly documented in the codebase with the comment: "for a state that doesn't NEED to have state. latest events filter out rejected events anyway, therefore no need to be afraid of having no state id associated with an event here" (state.service.ts:633-634). Database queries reinforce this pattern by explicitly filtering events with stateId: { $ne: '' as StateID }. For rejected invites where the user was never actually in the room, bypassing state resolution is the correct behavior.

@codecov-commenter
Copy link

codecov-commenter commented Nov 17, 2025

Codecov Report

❌ Patch coverage is 34.37500% with 168 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.22%. Comparing base (2d00b4c) to head (59ae9ac).

Files with missing lines Patch % Lines
...ckages/federation-sdk/src/services/room.service.ts 54.74% 62 Missing ⚠️
.../federation-sdk/src/services/federation.service.ts 0.00% 44 Missing ⚠️
...ages/federation-sdk/src/services/invite.service.ts 12.00% 44 Missing ⚠️
...ederation-sdk/src/repositories/event.repository.ts 5.88% 16 Missing ⚠️
packages/federation-sdk/src/sdk.ts 75.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #300      +/-   ##
==========================================
+ Coverage   52.81%   53.22%   +0.40%     
==========================================
  Files          96       96              
  Lines       12597    12794     +197     
==========================================
+ Hits         6653     6809     +156     
- Misses       5944     5985      +41     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

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

Caution

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

⚠️ Outside diff range comments (1)
packages/federation-sdk/src/services/invite.service.ts (1)

34-259: Add comprehensive test coverage for InviteService security-critical authorization and invite flows.

The InviteService has zero test coverage. This is problematic because:

  1. Authorization logic is untested: The service injects and uses eventAuthorizationService.checkAclForInvite() (line 226) to enforce room access controls—critical for preventing unauthorized invites.
  2. No validation tests: The shouldProcessInvite() method (line 152) validates room encryption and privacy settings based on configuration, but this logic is unverified.
  3. Multiple untested code paths: The service branches on whether the invited user is local or remote (lines 105–118), whether the server is the room host (line 224–232), and handles several error conditions—all without test coverage.
  4. Event signing and federation operations: Cryptographic signing and federation messages have no verification.

Please add unit and integration tests covering:

  • Local and remote user invite flows with different room configurations
  • ACL rejection scenarios (encrypted rooms, public rooms based on config)
  • Invalid input validation (malformed roomId/userId)
  • Authorization failure paths
  • Event signing and persistence
  • Event emission for membership changes
♻️ Duplicate comments (1)
packages/federation-sdk/src/services/room.service.ts (1)

1054-1068: Fetch and use the actual room version instead of the default.

This issue was flagged in a previous review and remains unresolved. The method uses PersistentEventFactory.defaultRoomVersion (line 1061) when reconstructing the invite event, but the actual room version should be fetched from the invite event itself or via stateService.getRoomVersion(roomId).

The entire codebase follows a consistent pattern of fetching the actual room version before calling createFromRawEvent (see event.service.ts:193, state.service.ts:203, room.service.ts:405, 434, and rejectInvite at line 1092 which correctly uses the version from makeLeave). Room versions have different schemas, canonicalization rules, and validation requirements—using the wrong version can cause event validation or signature verification failures.

Apply this diff:

 async acceptInvite(inviteEventId: EventID, userId: UserID) {
   const inviteEventStore =
     await this.eventService.getEventById(inviteEventId);
   if (!inviteEventStore) {
     throw new Error(`Invite event not found: ${inviteEventId}`);
   }

-  const roomVersion = PersistentEventFactory.defaultRoomVersion;
+  const roomId = inviteEventStore.event.room_id;
+  const roomVersion = await this.stateService.getRoomVersion(roomId);
+  
   const inviteEvent = PersistentEventFactory.createFromRawEvent(
     inviteEventStore.event,
     roomVersion,
   );

   return this.joinUser(inviteEvent, userId);
 }
📜 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 57c7fd6 and ab1a5e6.

📒 Files selected for processing (12)
  • packages/federation-sdk/src/index.ts (1 hunks)
  • packages/federation-sdk/src/repositories/event-staging.repository.ts (1 hunks)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (3 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (1 hunks)
  • packages/federation-sdk/src/services/staging-area.service.ts (1 hunks)
  • packages/federation-sdk/src/services/state.service.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/rooms.controller.ts (5 hunks)
  • packages/room/src/manager/room-state.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/homeserver/src/controllers/federation/rooms.controller.ts
  • packages/federation-sdk/src/services/federation.service.ts
  • packages/federation-sdk/src/repositories/event-staging.repository.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/state.service.ts
  • packages/room/src/manager/room-state.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/staging-area.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (7)
packages/federation-sdk/src/services/state.service.ts (2)
packages/room/src/types/_common.ts (1)
  • RoomID (16-16)
packages/room/src/manager/event-wrapper.ts (1)
  • roomId (101-103)
packages/federation-sdk/src/services/send-join.service.ts (1)
packages/room/src/manager/event-wrapper.ts (1)
  • roomId (101-103)
packages/federation-sdk/src/services/room.service.ts (3)
packages/room/src/manager/event-wrapper.ts (2)
  • roomId (101-103)
  • extractDomainFromId (23-29)
packages/room/src/types/_common.ts (3)
  • EventID (8-8)
  • UserID (20-20)
  • StateID (12-12)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
packages/federation-sdk/src/services/staging-area.service.ts (1)
packages/room/src/manager/event-wrapper.ts (2)
  • roomId (101-103)
  • event (129-138)
packages/federation-sdk/src/index.ts (2)
packages/core/src/events/isRoomMemberEvent.ts (1)
  • Membership (11-11)
packages/room/src/types/_common.ts (1)
  • PduForType (26-26)
packages/federation-sdk/src/services/invite.service.ts (1)
packages/room/src/types/_common.ts (1)
  • StateID (12-12)
packages/federation-sdk/src/repositories/event.repository.ts (3)
packages/room/src/types/_common.ts (2)
  • EventID (8-8)
  • StateID (12-12)
packages/room/src/manager/event-wrapper.ts (1)
  • event (129-138)
packages/room/src/types/v3-11.ts (1)
  • Pdu (828-828)
🔇 Additional comments (12)
packages/federation-sdk/src/services/state.service.ts (1)

1134-1144: LGTM! Properly handles optional room names.

The changes correctly align with RoomState.name now returning string | undefined. The nullish coalescing operator provides a sensible fallback to empty string for public room listings.

packages/federation-sdk/src/services/room.service.ts (2)

883-891: LGTM! Extended membership event payload.

The emission now includes additional contextual fields (room_id, sender, state_key, origin_server_ts, content) that align with the updated HomeserverEventSignatures type definition.


1070-1115: I need to verify a few more implementation details before finalizing the assessment.

Based on my verification of the codebase through script execution and web searches, I can now provide an assessment:

The pattern of using empty StateID for leave events rejecting invites is correct and consistent with the codebase design.

Evidence from verification:

  • Empty StateID is used consistently across the codebase in 6+ locations for events that don't modify room state: room creation events (state.service.ts line 431), initial state creation (state.service.ts line 582), rejected events (state.service.ts line 636), invite events (invite.service.ts line 240), and leave events from rejectInvite (room.service.ts line 1102)
  • The codebase explicitly documents this pattern in state.service.ts (line 633-634): "latest events filter out rejected events anyway, therefore no need to be afraid of having no state id associated with an event here"
  • Downstream queries explicitly filter OUT events with empty StateID using { $ne: '' as StateID } in two locations (event.repository.ts lines 146 and 483), ensuring events with empty StateID don't get included in state resolution or timeline queries
  • The Matrix Federation API specification permits this pattern: when rejecting invites, the server makes a /make_leave request, signs the event, and sends it via /send_leave

The design correctly handles the concern raised:

  1. Empty StateID serves as a marker for non-state-modifying events
  2. Downstream consumers are aware and explicitly filter these out where appropriate
  3. The event emission still works correctly for logging/tracking purposes
  4. This matches the Matrix specification for handling invite rejection
packages/federation-sdk/src/services/staging-area.service.ts (1)

309-320: LGTM! Membership event emission enriched with contextual data.

The emission now includes all required fields from the updated HomeserverEventSignatures type, including stripped_state sourced from the invite's unsigned.invite_room_state, which provides invited users with minimal room context.

packages/federation-sdk/src/services/send-join.service.ts (1)

73-81: LGTM! Join event emission properly enriched.

The emission correctly extracts and includes all required contextual fields from the signed join event, with a sensible fallback for state_key.

packages/federation-sdk/src/sdk.ts (1)

148-154: LGTM! Invite handling methods properly exposed.

The new acceptInvite and rejectInvite methods follow the established delegation pattern and correctly expose the room service functionality through the SDK's public API.

packages/federation-sdk/src/index.ts (1)

106-130: LGTM! Type signature correctly reflects enriched membership events.

The extended homeserver.matrix.membership signature properly captures all the contextual fields now emitted across the codebase (staging-area.service.ts, send-join.service.ts, room.service.ts). The stripped_state array includes appropriate room state event types that provide invited users with necessary room context.

packages/federation-sdk/src/repositories/event.repository.ts (1)

408-429: Behavior confirmed as intentional; force overwrite is by design, not a bug.

The technical observation in the review is accurate: forceInsertOrUpdateEventWithStateId places event in $set (applies on both insert and update), while insertOrUpdateEventWithStateId places it in $setOnInsert (applies only on insert). However, this difference is intentional.

Both call sites—invite.service.ts:237 and room.service.ts:1099—pass partial = true, indicating these are partial/incomplete event states that may be updated. The "force" prefix and $set placement signal that subsequent calls should overwrite the event data. This pattern is appropriate for handling federated events where partial state is initially stored and later updated.

No issues found. The code functions as designed.

packages/room/src/manager/room-state.ts (1)

65-76: LGTM! Breaking change properly handles rooms without names.

The getter now correctly returns undefined for rooms without name events (e.g., DMs), which is more accurate than potentially throwing or returning a default value. The related changes in state.service.ts (line 1144) properly handle this by defaulting to an empty string for public room listings.

Verification confirms all consumers of RoomState.name handle the undefined case:

  • Production code at state.service.ts:1144 uses state.name ?? ''
  • No other production code chains .name directly after getLatestRoomState2()
  • Test expectations align with actual behavior
packages/federation-sdk/src/services/invite.service.ts (3)

245-254: I need to search for downstream consumers of this event to complete the verification:

Let me search for event listeners in the codebase directly:


The review comment's concerns about stripped_state are based on incorrect assumptions. The type definition at packages/federation-sdk/src/index.ts explicitly includes stripped_state?: PduForType<...> as an optional field within the 'homeserver.matrix.membership' event schema, confirming that inclusion of this field is intentional design, not an oversight. This is consistent with how other services like staging-area.service.ts handle the same event type.

Likely an incorrect or invalid review comment.


236-243: Revert review comment—schema validation concern is incorrect.

The original review's primary concern about type safety is unfounded. StateID is defined as z.string().brand('StateID'), which validates that a value is a valid string; empty string '' passes this validation. The use of '' as StateID does not bypass validation.

Additionally, the empty StateID pattern with partial = true is intentional throughout the codebase (used in 8 locations across repositories and services) to represent events that haven't been fully processed yet. The MongoDB updateOne operation doesn't throw on empty string values—it successfully upserts the document.

While systematic error handling is reasonable as defensive programming, the reviewed code is consistent with patterns in this file (e.g., the host path at line 231 also lacks error handling). No changes are required.


44-45: Based on my verification, I cannot find an actual import-time circular dependency between InviteService and EventRepository. Here's what I discovered:

Dependency analysis:

  • EventRepository only depends on MongoDB (Collection<EventStore>) with no service imports
  • StateService imports EventRepository directly (one-way)
  • InviteService imports StateService and EventRepository directly (one-way)
  • No import-level cycle detected

Key findings:

  • StateService also uses @inject(delay(() => EventRepository)), suggesting the pattern is broader than just this one service
  • EventRepository does not import InviteService or StateService, so there's no back-reference creating a cycle
  • Both services import EventRepository directly without cycles

The delay() pattern from tsyringe is designed to break circular dependencies, but my analysis shows no actual circular imports. The usage might be:

  • Defensive/anticipatory coding
  • Addressing a runtime initialization order issue
  • Unnecessary and safe to reconsider

Please verify:

  1. Whether the delay() pattern is actually needed or if it's redundant defensive coding
  2. If removing @inject(delay(() => EventRepository)) from both InviteService and StateService would cause any runtime issues
  3. The original architectural reason this pattern was introduced

@ricardogarim ricardogarim marked this pull request as draft November 17, 2025 20:25
@ricardogarim ricardogarim force-pushed the feat/invites branch 5 times, most recently from 4cdd35c to b420e79 Compare November 19, 2025 02:37
@ricardogarim ricardogarim marked this pull request as ready for review November 25, 2025 00:01
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 (1)
packages/federation-sdk/src/services/room.service.ts (1)

1049-1067: Fetch the actual room version instead of using the default.

Line 1060 uses PersistentEventFactory.defaultRoomVersion when reconstructing the invite event. This pattern is inconsistent with the rest of the codebase, where stateService.getRoomVersion(roomId) is called before createFromRawEvent (see lines 370, 399, 434, and 621 in this file). Using an incorrect room version can cause event validation, signing, or hashing failures since different versions have different schemas and canonicalization rules.

Based on learnings, stateService.getRoomVersion() throws an error if the room is not found, so you should handle that case appropriately.

Apply this diff to fetch the actual room version:

 	async acceptInvite(roomId: RoomID, userId: UserID) {
 		const inviteEventStore = await this.eventService.findInviteEvent(
 			roomId,
 			userId,
 		);
 		if (!inviteEventStore) {
 			throw new Error(
 				`Invite event not found for user ${userId} in room ${roomId}`,
 			);
 		}
 
-		const roomVersion = PersistentEventFactory.defaultRoomVersion;
+		const roomVersion = await this.stateService.getRoomVersion(roomId);
 		const inviteEvent = PersistentEventFactory.createFromRawEvent(
 			inviteEventStore.event,
 			roomVersion,
 		);
 
 		return this.joinUser(inviteEvent, userId);
 	}
🧹 Nitpick comments (1)
packages/federation-sdk/src/services/invite.service.ts (1)

198-208: Consider documenting why an empty StateID is used for invite persistence.

The code persists the invite event with an empty StateID and partial=true, then emits a membership event. This deviates from the typical persistence flow (using stateService.handlePdu). While this aligns with the PR's design (mentioned in the AI summary), adding a comment would clarify why invites bypass normal state resolution.

+		// Invites are persisted with empty state as they don't affect room state until accepted/rejected
 		await this.eventRepository.forceInsertOrUpdateEventWithStateId(
 			inviteEvent.eventId,
 			inviteEvent.event,
 			'' as StateID,
 			true, // partial = true
 		);
📜 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 ab1a5e6 and a591844.

📒 Files selected for processing (7)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (4 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/state.service.ts (1 hunks)
  • packages/homeserver/src/controllers/federation/invite.controller.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/homeserver/src/controllers/federation/invite.controller.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/federation-sdk/src/services/federation.service.ts
  • packages/federation-sdk/src/sdk.ts
  • packages/federation-sdk/src/repositories/event.repository.ts
  • packages/federation-sdk/src/services/state.service.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (2)
packages/federation-sdk/src/services/room.service.ts (4)
packages/room/src/manager/event-wrapper.ts (2)
  • roomId (101-103)
  • extractDomainFromId (23-29)
packages/federation-sdk/src/index.ts (2)
  • RoomID (37-37)
  • UserID (36-36)
packages/room/src/types/_common.ts (3)
  • RoomID (16-16)
  • UserID (20-20)
  • StateID (12-12)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
packages/federation-sdk/src/services/invite.service.ts (3)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (33-33)
  • RoomVersion (34-34)
packages/room/src/manager/event-wrapper.ts (1)
  • event (129-138)
packages/room/src/types/_common.ts (1)
  • StateID (12-12)
🔇 Additional comments (4)
packages/federation-sdk/src/services/invite.service.ts (1)

37-40: LGTM! Dependency injection properly configured.

The constructor correctly injects EventEmitterService and uses delayed injection for EventRepository to handle circular dependencies.

packages/federation-sdk/src/services/room.service.ts (3)

1069-1086: LGTM! Error handling is thorough.

The method correctly validates that the invite exists and extracts the inviting server from the sender. The error messages are clear and actionable.


1088-1099: LGTM! Leave event construction follows the correct pattern.

The code obtains a leave template from the inviting server (which includes the correct room_version), constructs the event with that version, and signs it locally before sending. This is the proper federation flow for rejecting an invite.


1101-1111: Persistence and emission align with invite processing.

The leave event is persisted with an empty StateID and partial=true, then emitted via the membership event—consistent with how invite.service.ts handles invites (lines 198-208). This maintains consistency across invite-related flows.

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

🧹 Nitpick comments (2)
packages/federation-sdk/src/services/invite.service.ts (2)

145-161: Stricter invite_room_state validation is correct; consider using a domain error type

The new strippedStateEvents parameter type and the guard:

  • Ensure shouldProcessInvite is only run when invite_room_state is actually present and non-empty.
  • Match the new call site event.unsigned?.invite_room_state in processInvite, closing the nullability hole highlighted in earlier reviews.

The behavior (throwing when stripped state is missing) is reasonable given that policy decisions depend on it. If you want upstream handling to treat “missing invite_room_state” similarly to other policy rejections (e.g. mapping to a 4xx instead of a generic 5xx), consider throwing NotAllowedError (or another explicit domain error) here as well rather than a bare Error. That’s optional but would keep error classification more uniform.


186-221: Empty string sentinel for stateId is intentional but could be cleaner

Verification confirms the '' as StateID pattern is deliberate throughout the codebase. Empty string serves as a documented sentinel indicating events without state graph associations yet. Queries that depend on real stateIds explicitly filter empty strings: findLatestEventByRoomIdBeforeTimestampWithAssociatedState and findStateIdsByEventIds both use stateId: { $ne: '' as StateID }. No invariants are broken—the pattern is consistent in both invite.service.ts and room.service.ts.

However, the type escape hatch remains suboptimal. Consider:

  • Making stateId parameter StateID | null with null as the sentinel, or
  • Introducing a named constant (e.g., UNASSOCIATED_STATE_ID = '' as StateID) with clear documentation

This improves clarity without changing behavior.

📜 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 a591844 and 4f5e732.

📒 Files selected for processing (1)
  • packages/federation-sdk/src/services/invite.service.ts (5 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (1)
packages/federation-sdk/src/services/invite.service.ts (2)
packages/federation-sdk/src/index.ts (3)
  • PduForType (30-30)
  • PersistentEventBase (33-33)
  • RoomVersion (34-34)
packages/room/src/types/_common.ts (2)
  • PduForType (26-26)
  • StateID (12-12)
🔇 Additional comments (3)
packages/federation-sdk/src/services/invite.service.ts (3)

32-40: Constructor wiring for new dependencies looks coherent

Injecting EventEmitterService and EventRepository directly into InviteService keeps invite processing cohesive and avoids additional service indirections. The constructor surface still looks manageable and consistent with the rest of the federation SDK.


169-181: Invite policy logic for encrypted/public rooms is clear and minimal

The computation of isRoomNonPrivate and isRoomEncrypted, combined with allowedEncryptedRooms / allowedNonPrivateRooms, keeps the policy check straightforward and easy to reason about. Even for rooms that are both encrypted and public, the behavior is deterministic, and the single NotAllowedError path is appropriate for a first iteration. This aligns with the preference for simple, functional implementations over premature complexity.


213-216: Membership event emission on invite persistence is a good hook

Emitting homeserver.matrix.membership after persisting the signed invite gives downstream consumers a clear, centralized signal for membership-related changes initiated via invites. The payload shape { event_id, event } is simple and future-proof if more metadata is needed later.

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)
packages/federation-sdk/src/services/room.service.ts (1)

1061-1079: Fetch and use the actual room version instead of the default.

The acceptInvite method uses PersistentEventFactory.defaultRoomVersion (line 1072) when reconstructing the invite event, but should use the room's actual version. The codebase consistently fetches the actual room version via stateService.getRoomVersion(roomId) before calling createFromRawEvent (as seen in other parts of the codebase).

Room version is critical for event validation, signing, and hashing—different versions have different schemas and canonicalization rules. Using the default version could lead to event validation or signature verification failures.

Extract the room_id from inviteEventStore.event.room_id and call stateService.getRoomVersion(roomId) to get the actual room version before passing it to createFromRawEvent.

🧹 Nitpick comments (1)
packages/federation-sdk/src/services/state.service.ts (1)

788-818: Error handling in getServerSetInRoom now masks state/room errors as “empty server set”

Wrapping getLatestRoomState in a broad try/catch and returning the (possibly empty) servers set means callers can no longer distinguish between:

  • room with no joined members, and
  • failures like “no state found for room” or other internal errors (including UnknownRoomError).

Given getLatestRoomState(roomId) is guaranteed to throw when there is no state instead of returning undefined, this is a behavioral change for getServerSetInRoom/getServersInRoom that might hide genuine problems. Based on learnings, this may be surprising to existing callers.

Consider tightening the error handling, for example:

  • Only swallow specific, expected errors (e.g., missing membership events) and rethrow or propagate room/state-not-found conditions, or
  • Return a richer result (or throw) when room/state is missing, so the caller can tell “room doesn’t exist / state not ready” from “room has no servers”.

The per-event try/catch around extractDomainFromId is a nice robustness improvement; you might optionally downgrade the log level or include roomId for better correlation.

📜 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 4f5e732 and dd6ace8.

📒 Files selected for processing (12)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (7 hunks)
  • packages/federation-sdk/src/services/profiles.service.ts (2 hunks)
  • packages/federation-sdk/src/services/room.service.ts (12 hunks)
  • packages/federation-sdk/src/services/state.service.ts (1 hunks)
  • packages/homeserver/src/controllers/federation/invite.controller.ts (0 hunks)
  • packages/homeserver/src/controllers/internal/invite.controller.ts (2 hunks)
  • packages/homeserver/src/controllers/internal/room.controller.ts (2 hunks)
  • packages/room/src/types/_common.ts (1 hunks)
  • packages/room/src/types/v3-11.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/homeserver/src/controllers/federation/invite.controller.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/federation-sdk/src/sdk.ts
  • packages/federation-sdk/src/services/federation.service.ts
  • packages/federation-sdk/src/repositories/event.repository.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/homeserver/src/controllers/internal/room.controller.ts
  • packages/room/src/types/v3-11.ts
  • packages/federation-sdk/src/services/profiles.service.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/homeserver/src/controllers/internal/invite.controller.ts
  • packages/room/src/types/_common.ts
  • packages/federation-sdk/src/services/state.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (6)
packages/homeserver/src/controllers/internal/room.controller.ts (1)
packages/room/src/types/_common.ts (1)
  • stateIdSchema (18-18)
packages/room/src/types/v3-11.ts (1)
packages/room/src/types/_common.ts (1)
  • stateIdSchema (18-18)
packages/federation-sdk/src/services/profiles.service.ts (1)
packages/room/src/types/_common.ts (1)
  • stateIdSchema (18-18)
packages/federation-sdk/src/services/room.service.ts (2)
packages/room/src/types/_common.ts (2)
  • stateIdSchema (18-18)
  • UserID (16-16)
packages/room/src/manager/event-wrapper.ts (1)
  • extractDomainFromId (23-29)
packages/homeserver/src/controllers/internal/invite.controller.ts (1)
packages/room/src/types/_common.ts (1)
  • stateIdSchema (18-18)
packages/federation-sdk/src/services/state.service.ts (1)
packages/room/src/manager/event-wrapper.ts (2)
  • event (129-138)
  • extractDomainFromId (23-29)
🔇 Additional comments (10)
packages/room/src/types/v3-11.ts (1)

6-6: LGTM! Improved type accuracy for state_key.

The change from userIdSchema to stateIdSchema for the state_key descriptor is correct. The state_key field can hold various identifiers (empty string for many state events, user IDs for membership events, etc.), so using a dedicated StateID type provides more accurate typing than constraining it to UserID.

Also applies to: 644-644

packages/room/src/types/_common.ts (1)

18-20: LGTM! Proper export of StateID types.

Exporting stateIdSchema and the StateID type enables consistent state_key handling across the codebase. The branded type provides compile-time type safety while accepting string values at runtime.

packages/federation-sdk/src/services/profiles.service.ts (1)

10-10: LGTM! Consistent state_key normalization.

The use of stateIdSchema.parse(userId) aligns with the broader pattern of normalizing state_key values across the codebase. Since userId is typed as UserID, the parse should succeed. This change maintains consistency with other membership event constructions in the PR.

Also applies to: 94-94

packages/federation-sdk/src/services/room.service.ts (2)

266-266: LGTM! Consistent state_key normalization across membership events.

The use of stateIdSchema.parse() for all state_key assignments in membership events (join, leave, kick, ban, DM creation) provides consistent validation and type safety throughout the room service.

Also applies to: 491-491, 621-621, 702-702, 745-745, 828-828, 878-878, 1492-1492, 1609-1609, 1667-1668


1081-1124: LGTM! rejectInvite correctly handles room version and federation flow.

The rejectInvite method properly:

  • Validates the invite exists
  • Extracts and validates the inviting server domain
  • Obtains the actual room version from the federation makeLeave response (line 1100)
  • Signs and sends the leave event through federation
  • Persists the event with proper state_key handling
  • Emits the membership event for downstream processing

The implementation follows the correct pattern for federated leave handling.

packages/homeserver/src/controllers/internal/invite.controller.ts (1)

5-5: LGTM! Consistent state_key handling in invite controller.

The use of stateIdSchema.parse(username) for the invite event's state_key aligns with the standardized state_key normalization pattern across the federation and homeserver modules.

Also applies to: 44-44

packages/homeserver/src/controllers/internal/room.controller.ts (1)

7-7: LGTM! Consistent state_key handling in ban endpoint.

The use of stateIdSchema.parse(userIdToBan) for the ban membership event's state_key maintains consistency with the standardized state_key normalization applied across the federation-related codebase.

Also applies to: 358-358

packages/federation-sdk/src/services/invite.service.ts (3)

31-38: LGTM! Proper dependency injection for event persistence.

The constructor correctly wires in EventEmitterService and EventRepository using delayed injection for the repository to handle circular dependencies. This enables the new invite event persistence flow.


76-76: LGTM! Consistent state_key normalization in invites.

Using stateIdSchema.parse(userId) for the invite event's state_key maintains consistency with the standardized identifier handling across the federation SDK.


188-227: LGTM! Improved invite processing with proper validation and persistence.

The processInvite method now:

  • Validates invite_room_state is present before processing (lines 193-197), addressing the previous review feedback
  • Uses repository-based persistence via insertInviteEvent (lines 212-217) with proper state_key handling
  • Emits membership events through eventEmitterService (lines 219-222)

The validation ensures policy checks can proceed safely, and the repository-based persistence provides proper event storage with stateId support.

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

🧹 Nitpick comments (1)
packages/federation-sdk/src/services/invite.service.ts (1)

153-185: Invite policy validation and error handling are sound; consider richer context

The shouldProcessInvite logic correctly derives isRoomNonPrivate and isRoomEncrypted from stripped state and enforces allowedEncryptedRooms / allowedNonPrivateRooms, using NotAllowedError for policy rejections. The new guard in processInvite that throws when event.unsigned?.invite_room_state is missing ensures policy is always evaluated on well-formed data, which is important.

If you want easier debugging later, consider including eventId and/or event.room_id in the 'Missing invite_room_state required for policy validation' error message (or logging it beforehand), but this is optional.

Also applies to: 192-197

📜 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 dd6ace8 and 814a7e2.

📒 Files selected for processing (1)
  • packages/federation-sdk/src/services/invite.service.ts (6 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (1)
packages/federation-sdk/src/services/invite.service.ts (2)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (29-29)
  • RoomVersion (30-30)
packages/room/src/types/_common.ts (1)
  • StateID (12-12)
🔇 Additional comments (3)
packages/federation-sdk/src/services/invite.service.ts (3)

1-14: Constructor wiring and DI look consistent

The additional imports and constructor parameters for StateService, EventEmitterService, and the EventRepository with @inject(delay(...)) are consistent with the rest of the file and look correct for avoiding circular dependencies. No changes needed here.

Also applies to: 18-18, 30-37


103-106: Consistent membership event emission across invite flows

Emitting 'homeserver.matrix.membership' for:

  • local invites (lines 103–106),
  • remote invites you send (lines 138–141), and
  • inbound invites processed via processInvite (lines 218–221),

gives a unified signal for downstream consumers regardless of the invite source. This looks coherent and should simplify listeners. No action required.

Also applies to: 138-141, 218-221


211-216: Pattern is well-established in the codebase and intentionally designed for partial events.

The use of '' as StateID is not a type safety issue but an established pattern throughout the federation SDK for handling partial events (invites, rejected events, etc.).

Evidence:

  • Type definition: StateID is a Zod branded type (z.string().brand('StateID')), which explicitly supports empty strings
  • Consistent usage: The same pattern appears in state.service.ts (lines 440, 591, 645), state-graph.repository.ts (line 137), and invite.service.ts (line 214)
  • Repository layer support: EventRepository explicitly handles this pattern:
    • Query filters: stateId: { $ne: '' as StateID } distinguish partial from state-bearing events
    • findPartialsByRoomId() method retrieves partial events
  • Intentional design: The partial parameter documents when StateID is empty

This is not a type contract violation—it's intentional use of branded types for event categorization. No cleaner API exists, and the current implementation aligns with established codebase conventions.

@ricardogarim ricardogarim force-pushed the feat/invites branch 2 times, most recently from e120b16 to 865e1d1 Compare November 25, 2025 20:22
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 (1)
packages/federation-sdk/src/services/room.service.ts (1)

1060-1078: Prefer using the room’s actual version instead of defaultRoomVersion when reconstructing the invite.

acceptInvite currently reconstructs the stored invite with PersistentEventFactory.defaultRoomVersion before passing it to joinUser. Elsewhere in this file and in the SDK, events are consistently reconstructed with the real room version (via stateService.getRoomVersion(roomId)), which avoids subtle mismatches if non‑default versions are used.

Even though joinUser today mostly needs roomId and sender, using the correct roomVersion here would:

  • Keep this path consistent with the rest of the codebase.
  • Reduce the risk of future breakage if joinUser or downstream logic starts using event.version.

Consider replacing the hard‑coded default with:

const roomVersion = await this.stateService.getRoomVersion(roomId);
const inviteEvent = PersistentEventFactory.createFromRawEvent(
	inviteEventStore.event,
	roomVersion,
);
📜 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 814a7e2 and f9c60e4.

📒 Files selected for processing (7)
  • packages/federation-sdk/src/repositories/event.repository.ts (1 hunks)
  • packages/federation-sdk/src/sdk.ts (1 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (6 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/state.service.ts (1 hunks)
  • packages/homeserver/src/controllers/federation/invite.controller.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/homeserver/src/controllers/federation/invite.controller.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/federation-sdk/src/repositories/event.repository.ts
  • packages/federation-sdk/src/sdk.ts
  • packages/federation-sdk/src/services/federation.service.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/state.service.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (2)
packages/federation-sdk/src/services/state.service.ts (1)
packages/room/src/manager/event-wrapper.ts (3)
  • roomId (101-103)
  • event (129-138)
  • extractDomainFromId (23-29)
packages/federation-sdk/src/services/room.service.ts (2)
packages/room/src/manager/event-wrapper.ts (2)
  • roomId (101-103)
  • extractDomainFromId (23-29)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
🔇 Additional comments (4)
packages/federation-sdk/src/services/room.service.ts (1)

1080-1122: Reject‑invite flow looks consistent and side‑effect complete.

The rejectInvite implementation validates the stored invite, derives the inviting server, calls makeLeave/sendLeave, persists the resulting leave event via insertInviteEvent, and emits homeserver.matrix.membership. This aligns well with Matrix invite rejection semantics for the invited homeserver and keeps invite/leave persistence consolidated in the repository.

packages/federation-sdk/src/services/invite.service.ts (3)

30-37: DI wiring and singleton scope look good.

Injecting StateService, EventEmitterService, and EventRepository with @singleton() and @inject(delay(...)) is consistent with the rest of the SDK and keeps invite handling focused on this service without adding extra global state.


88-142: Consistent membership event emission via eventEmitterService.

Using this.eventEmitterService.emit('homeserver.matrix.membership', …) for both local and federated invite paths centralizes membership notifications and matches how other services publish these events, which should simplify consumers.


153-225: Invite policy check + repository‑backed persistence form a clean inbound‑invite pipeline.

processInvite now:

  • Validates that event.unsigned.invite_room_state is present and runs it through shouldProcessInvite (encryption/publicity policy).
  • Rebuilds the invite as a PersistentEventBase, checks the eventId, signs it, persists via eventRepository.insertInviteEvent, and emits a membership event.

This keeps policy enforcement, persistence, and notification concerns cleanly separated on the invited server side and avoids incorrectly mutating the main room state graph for invites.

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

📜 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 f9c60e4 and 865e1d1.

📒 Files selected for processing (2)
  • packages/federation-sdk/src/services/invite.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/federation-sdk/src/services/room.service.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (1)
packages/federation-sdk/src/services/invite.service.ts (2)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (29-29)
  • RoomVersion (30-30)
packages/room/src/manager/event-wrapper.ts (1)
  • event (129-138)
🔇 Additional comments (1)
packages/federation-sdk/src/services/invite.service.ts (1)

210-214: Based on my investigation, I can now provide a rewritten review comment with the verification results:


Verify authorization checks for received invite events are appropriate for the use case.

The change from stateService.handlePdu() to eventRepository.insertInviteEvent() bypasses the authorization validation that handlePdu provides. Investigation confirms:

  1. handlePdu performs comprehensive authorization checks via checkEventAuthWithState(), validating the event against its auth events and room state (lines 629, 704 in state.service.ts)
  2. insertInviteEvent() is a simple database upsert with no validation (lines 408-427 in event.repository.ts)
  3. processInvite() only validates:
    • The invite_room_state field exists
    • Policy checks (encrypted/public room restrictions via shouldProcessInvite())
    • Event ID matches the request parameter

However, processInvite() is specifically for receiving invitation events from remote Matrix servers. The design appears intentional:

  • The code comment states "let them handle the transactions" (line 222), indicating the originating server is responsible for authorization
  • No code path currently retrieves or processes these stored invite events through room state validation
  • Invite events are stored for federation protocol compliance and relaying, not immediately processed into room state

Recommendation: Clarify whether this design is intentional by:

  1. Adding a comment explaining why authorization checks are not performed for received invites, or
  2. If checks ARE needed, add basic event structure validation and auth event chain verification before storing

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

📜 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 9d7fec4 and da7e3a8.

📒 Files selected for processing (7)
  • packages/federation-sdk/src/repositories/event.repository.ts (2 hunks)
  • packages/federation-sdk/src/sdk.ts (2 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.spec.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (4 hunks)
  • packages/homeserver/src/controllers/federation/invite.controller.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/federation-sdk/src/services/federation.service.ts
  • packages/federation-sdk/src/sdk.ts
  • packages/federation-sdk/src/services/room.service.spec.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:30:07.786Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:15:46.588Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-10-07T13:17:41.878Z
Learnt from: debdutdeb
Repo: RocketChat/homeserver PR: 252
File: packages/federation-sdk/src/repositories/state-graph.repository.ts:60-63
Timestamp: 2025-10-07T13:17:41.878Z
Learning: In the codebase, when types explicitly declare a field as a non-nullable, non-optional string (e.g., `stateKey: string` in `StateGraphStore`), trust the type system and avoid adding defensive coalescing or null-checks in data pipelines. If violations occur, they should be fixed at the source where the contract is broken, not masked with defensive code.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (3)
packages/federation-sdk/src/services/room.service.ts (3)
packages/room/src/manager/event-wrapper.ts (4)
  • roomId (111-113)
  • event (147-156)
  • extractDomainFromId (23-29)
  • sender (115-117)
packages/room/src/types/_common.ts (4)
  • RoomID (16-16)
  • UserID (20-20)
  • PduForType (26-26)
  • EventID (8-8)
packages/room/src/manager/factory.ts (1)
  • PersistentEventFactory (30-123)
packages/federation-sdk/src/repositories/event.repository.ts (4)
packages/room/src/types/_common.ts (2)
  • EventID (8-8)
  • StateID (12-12)
packages/room/src/types/v3-11.ts (1)
  • Pdu (828-828)
packages/room/src/state_resolution/definitions/definitions.ts (1)
  • EventStore (101-103)
packages/core/src/models/event.model.ts (1)
  • EventStore (26-38)
packages/federation-sdk/src/services/invite.service.ts (2)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (29-29)
  • RoomVersion (30-30)
packages/room/src/manager/event-wrapper.ts (3)
  • residentServer (129-135)
  • event (147-156)
  • extractDomainFromId (23-29)
🔇 Additional comments (13)
packages/homeserver/src/controllers/federation/invite.controller.ts (1)

15-26: LGTM! Cleaner API while preserving security.

The refactored processInvite call no longer requires explicit roomId and authenticatedServer parameters, as the SDK now derives this information from the event itself (via event.room_id and domain extraction). The authentication validation at the controller level (lines 16-18) is correctly preserved before the SDK call.

packages/federation-sdk/src/repositories/event.repository.ts (1)

409-424: LGTM! Well-structured method for outlier event persistence.

The insertOutlierEvent method correctly supports the invite workflow for rooms where state is not yet available. The method:

  • Properly initializes all EventStore fields with appropriate defaults
  • Sets outlier: true to mark the event as received out-of-band
  • Uses partial: false since the event itself is complete
  • Returns the MongoDB InsertOneResult for caller verification

This enables the non-host invite path in InviteService to persist invite events before room state is established.

packages/federation-sdk/src/services/room.service.ts (6)

571-601: LGTM! Proper implementation of leave template generation.

The makeLeave method correctly implements the Matrix federation make_leave flow:

  • Retrieves the actual room version from room information
  • Builds a membership event template with membership: 'leave'
  • Adds the origin field to match federation protocol expectations
  • Returns both the event template and room version for the caller

This method is used by both leaveRoom (line 683) and rejectInvite (line 1160) to obtain properly formatted leave event templates.


603-640: LGTM! Secure leave event processing with proper validation.

The sendLeave method correctly implements the Matrix federation send_leave flow with important security checks:

  • Validates room version exists before processing (lines 608-611)
  • Security: Verifies this server is the resident server for the room (lines 622-627), preventing malicious leave requests for rooms hosted elsewhere
  • Validates the event ID matches the computed hash (lines 618-620)
  • Checks for event rejection and propagates errors (lines 630-632)
  • Properly persists via handlePdu and broadcasts to all servers

642-698: LGTM! Cleaner refactor using makeLeave.

The leaveRoom method now delegates to makeLeave (line 683) instead of constructing the leave event inline. This refactor:

  • Eliminates code duplication with the new leave flow
  • Ensures consistent room version handling via makeLeave
  • Maintains the same permission checks and processing logic
  • Follows the same pattern used in rejectInvite (remote leave case)

1118-1130: LGTM! Consider adding unit tests.

The acceptInvite method correctly:

  • Finds the invite event via findInviteEvent
  • Extracts the inviter from inviteEventStore.event.sender
  • Delegates to joinUser with the inviter as the sender parameter, ensuring the join goes through the inviting server's federation endpoint

As noted in previous review comments, this method has clear test coverage opportunities (mocking invite lookup, testing missing invite error, etc.), but deferring test implementation is acceptable for this PR.


1132-1176: LGTM! Thoughtful handling of local vs. remote invite rejection.

The rejectInvite method correctly handles both scenarios:

Local rejection (lines 1152-1155): When the inviter is from our server, delegates to leaveRoom which handles full state updates.

Remote rejection (lines 1157-1170): When the inviter is from another server:

  • The inline comment (lines 1157-1158) correctly notes we may not have room state yet
  • Calls federationService.makeLeave to get the leave template from the inviting server
  • Signs and sends the leave event via federationService.sendLeave
  • Manually handles the flow since we can't rely on local state

The membership event emission (lines 1172-1175) ensures both paths notify other components.


909-1116: All callers of joinUser have been updated with the new sender parameter. The method is used in two test cases (line 218, 318 in room.service.spec.ts) and in acceptInvite (line 1129 in room.service.ts where it correctly passes inviteEventStore.event.sender). The signature change does not break existing code within this codebase.

packages/federation-sdk/src/services/invite.service.ts (5)

1-18: LGTM! Proper dependency injection setup.

The import and constructor changes correctly introduce EventRepository via dependency injection with delay() to handle potential circular dependencies (line 36). This supports the new outlier event persistence in the non-host invite path.

Also applies to: 36-37


178-191: LGTM! Cleaner API with explicit stripped state parameter.

The processInvite signature now requires strippedStateEvents as an explicit parameter (line 182-190) instead of accessing event.unsigned.invite_room_state. This:

  • Makes the API contract clearer
  • Addresses the previous null safety concern
  • Allows the controller to validate and pass the data explicitly
  • Maintains type safety with the specific union of allowed event types

204-204: LGTM! Using the residentServer getter as suggested.

Line 204 correctly uses the residentServer getter from inviteEvent (which extracts the domain from room_id) instead of manual extraction. This follows the pattern suggested in previous review feedback and is cleaner than calling extractDomainFromId directly.

Based on past review comments, as per ggazzo's suggestion.


217-227: LGTM! Security validation addresses past critical issue.

Lines 217-227 correctly validate that the invited user (from event.state_key) belongs to this server before signing the invite. This addresses the critical security issue raised in previous reviews where a malicious remote server could request signatures for invites to users on other servers.

The validation:

  • Extracts the domain from state_key (line 217)
  • Validates the domain exists (lines 218-222)
  • Verifies it matches this.configService.serverName (lines 223-227)
  • Occurs before signing (line 229), preventing unauthorized signatures

Based on past review comments, as per previous security feedback.


229-255: LGTM! Thoughtful refactor handling state availability.

The non-host path correctly handles two scenarios:

When we have room state (lines 241-242): If a m.room.create event exists, we're already aware of the room and can process the invite normally via handlePdu.

When we don't have state (lines 244-249): For out-of-band invites (first contact with the room), the invite is stored as an outlier via insertOutlierEvent. This allows the invite to be processed later when accepting (which triggers a join flow that fetches room state).

Additional details:

  • Line 233: Stores stripped state in unsigned.invite_room_state for later policy validation (as explained in the comment)
  • Line 229: Signs before persisting (security requirement)
  • Lines 252-255: Emits membership event after successful persistence

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

📜 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 da7e3a8 and 59ae9ac.

📒 Files selected for processing (1)
  • packages/federation-sdk/src/sdk.ts (2 hunks)
🔇 Additional comments (2)
packages/federation-sdk/src/sdk.ts (2)

86-92: LGTM! Well-structured delegation methods.

The makeLeave and sendLeave methods follow the established delegation pattern and are positioned logically before leaveRoom. The type-safe parameter forwarding using TypeScript's Parameters utility is consistent with the rest of the class. Both methods are properly exposed by RoomService.


159-165: SDK delegation methods follow established patterns correctly.

The acceptInvite and rejectInvite methods properly delegate to this.roomService with type-safe parameter forwarding, consistent with other methods in the class. However, RoomService's implementation is not visible in the repository, so the underlying method availability cannot be verified from the codebase alone.

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.

6 participants