Skip to content

Conversation

@debdutdeb
Copy link
Member

@debdutdeb debdutdeb commented Sep 26, 2025

required for further prs where we use these to save rejections
example log

event not allowed StateResolverAuthorizationError: auth_error: {"eventId":"$D0DBXj6yXAG5xxwRodZonN6iZ8S-lMY0gV_euS_YQnM","roomId":"!test:example.com","sender":"@evelyn:example.com","stateKey":"@evelyn:example.com","type":"m.room.member"} failed authorization check against auth event {"eventId":"$zdg24nzX8zbXL51a9UfUhK6HURmoCoFsD22hwAfdSGI","roomId":"!test:example.com","sender":"@alice:example.com","stateKey":"","type":"m.room.join_rules"}: join_rule is invite but membership is not invite or join
     reason: "join_rule is invite but membership is not invite or join",
 rejectedBy: "$zdg24nzX8zbXL51a9UfUhK6HURmoCoFsD22hwAfdSGI",
       code: "auth_error"

Summary by CodeRabbit

  • New Features

    • Introduced structured authorization errors with codes and clearer messages, improving visibility into why events are rejected.
    • Exposed error codes for downstream consumers to handle rejections more reliably.
  • Bug Fixes

    • Corrected usage of rejection fields to ensure accurate error reporting across invites, joins, messages, and state handling.
  • Refactor

    • Standardized rejection handling across services and state resolution, continuing processing on known authorization failures while halting on unknown errors.
    • Tightened typings and streamlined unused dependencies for improved stability and maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Aligns rejection handling across services to use rejectCode/rejectReason/rejectedBy on PersistentEventBase. Introduces structured authorization errors (RejectCodes, StateResolverAuthorizationError), updates room authorization flow to throw/catch them, and propagates rejectReason usage throughout federation SDK and homeserver. Adds toStrippedJson(), adjusts factory RoomVersion typing, and re-exports errors.

Changes

Cohort / File(s) Summary
Federation SDK: invite/message/send-join
packages/federation-sdk/src/services/invite.service.ts, packages/federation-sdk/src/services/message.service.ts, packages/federation-sdk/src/services/send-join.service.ts
Replace uses of rejectedReason with rejectReason when throwing on rejected events; minor import cleanups/removals.
Federation SDK: room/state services
packages/federation-sdk/src/services/room.service.ts, packages/federation-sdk/src/services/state.service.ts
Switch to rejectReason, add RejectCodes import, adjust rejection signaling in state paths; map key type change to EventID; remove unused imports.
Homeserver controller
packages/homeserver/src/controllers/internal/invite.controller.ts
Throw with membershipEvent.rejectReason instead of rejectedReason.
Room: authorization error model
packages/room/src/authorizartion-rules/errors.ts, packages/room/src/index.ts
Add RejectCodes/RejectCode and StateResolverAuthorizationError (with code/reason/rejectedBy); re-export errors from index.
Room: authorization rules
packages/room/src/authorizartion-rules/rules.ts
Replace generic errors with StateResolverAuthorizationError using RejectCodes; include rejectedEvent/rejectedBy/reason across checks; minor signature/type import updates.
Room: event wrapper and factory
packages/room/src/manager/event-wrapper.ts, packages/room/src/manager/factory.ts
Add public rejectCode/rejectReason/rejectedBy, new reject(code, reason, rejectedBy?), toStrippedJson(); change factory createFromRawEvent to accept RoomVersion.
Room: state resolution integration
packages/room/src/state_resolution/definitions/definitions.ts
Catch StateResolverAuthorizationError during auth checks; call event.reject(code, reason, rejectedBy); rethrow others; adjust imports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Service as Federation/Room Services
  participant Rules as Authorization Rules
  participant StateRes as State Resolver
  participant Event as PersistentEventBase

  Client->>Service: Submit event (join/message/invite)
  Service->>Rules: checkEventAuth*(event, authState)
  alt Auth passes
    Rules-->>Service: ok
    Service->>StateRes: iterativeAuthChecks(event,...)
    StateRes-->>Service: ok
  else Auth fails
    Rules-->>Service: throw StateResolverAuthorizationError(code, {rejectedEvent, reason, rejectedBy?})
    Service->>StateRes: iterativeAuthChecks(event,...)
    StateRes->>Event: reject(code, reason, rejectedBy)
    StateRes-->>Service: continue processing
    Service->>Client: Error(event.rejectReason)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • rodrigok

Poem

A rabbit taps code with a gentle thump,
RejectCodes in paw, it clears each bump.
Reasons now tidy, with who and why,
Events hop forward, no longer shy.
toStrippedJson gleams—concise delight!
Onward we bound, through the review night. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main focus of the pull request, which is refactoring how state resolution propagates errors, and directly aligns with the changes described in the summary and objectives without extra noise or unrelated details.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch correct-state-resolver-error

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.

@codecov-commenter
Copy link

codecov-commenter commented Sep 26, 2025

Codecov Report

❌ Patch coverage is 49.82332% with 142 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.39%. Comparing base (b52b385) to head (42c1974).

Files with missing lines Patch % Lines
packages/room/src/authorizartion-rules/rules.ts 43.60% 141 Missing ⚠️
...om/src/state_resolution/definitions/definitions.ts 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #229      +/-   ##
==========================================
- Coverage   81.74%   81.39%   -0.36%     
==========================================
  Files          63       63              
  Lines        4695     4773      +78     
==========================================
+ Hits         3838     3885      +47     
- Misses        857      888      +31     

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

@debdutdeb debdutdeb marked this pull request as ready for review September 26, 2025 10:12
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: 6

Caution

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

⚠️ Outside diff range comments (2)
packages/room/src/authorizartion-rules/rules.ts (1)

102-111: Check for membership property correctly.

The spec requires rejecting when the membership property is missing. The current check uses isMembershipEvent() instead of verifying content.membership presence, which can lead to a generic “unknown membership state undefined” later.

Apply this diff:

-  if (
-    !membershipEventToCheck.stateKey ||
-    !membershipEventToCheck.isMembershipEvent()
-  ) {
+  if (
+    !membershipEventToCheck.stateKey ||
+    !('membership' in membershipEventToCheck.getContent())
+  ) {
     throw new StateResolverAuthorizationError(RejectCodes.AuthError, {
       rejectedEvent: membershipEventToCheck,
       reason: 'm.room.member event has no state_key or membership property',
     });
   }
packages/room/src/manager/event-wrapper.ts (1)

1-6: Convert these to import type
• packages/federation-sdk/src/services/state.service.ts:21 – import { RejectCodes } from '@rocket.chat/federation-room';
• packages/room/src/authorizartion-rules/rules.ts:12 – import { RejectCodes, StateResolverAuthorizationError } from './errors';
• packages/room/src/manager/event-wrapper.ts:6 – import { RejectCode } from '../authorizartion-rules/errors';
• packages/room/src/state_resolution/definitions/definitions.ts:8 – import { PersistentEventBase } from '../../manager/event-wrapper';
• packages/room/src/state_resolution/definitions/algorithm/v2.ts:61 – import { PersistentEventBase } from '../../../manager/event-wrapper';
• packages/room/src/authorizartion-rules/rules.spec.ts:2 – import { PersistentEventBase } from '../manager/event-wrapper';
• packages/room/src/authorizartion-rules/errors.ts:1 – import { PersistentEventBase } from '../manager/event-wrapper';
• packages/room/src/manager/power-level-event-wrapper.ts:6 – import { PersistentEventBase } from './event-wrapper';

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

985-987: Use HttpException for rejected remote joins; prefer checking persisted event

  • Use HttpException with a fallback message.
  • Note: You’re checking joinEventFinal.rejected (the local instance) after calling processIncomingPDUs. Consider verifying the persisted event’s rejection instead to avoid stale status.

Apply:

-		if (joinEventFinal.rejected) {
-			throw new Error(joinEventFinal.rejectReason);
-		}
+		if (joinEventFinal.rejected) {
+			throw new HttpException(
+				joinEventFinal.rejectReason || 'Join rejected',
+				HttpStatus.FORBIDDEN,
+			);
+		}

Optional follow-up: fetch the stored event by ID after processing and check its rejection fields before throwing.

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

53-54: Add a fallback message for safety

rejectReason can be empty/undefined. Provide a default.

Apply:

-			throw new Error(joinEvent.rejectReason);
+			throw new Error(joinEvent.rejectReason || 'Join rejected');
packages/homeserver/src/controllers/internal/invite.controller.ts (1)

63-65: Prefer a safe fallback and consider returning a 400 response

  • Add a default message for rejectReason.
  • Optional: Instead of throwing, return an ErrorResponse (400) to match the declared route responses.

Apply minimal safety:

-				throw new Error(membershipEvent.rejectReason);
+				throw new Error(membershipEvent.rejectReason || 'Invite rejected');

Optional route-shape refactor (if you want 400 instead of exception bubbling):

-			if (membershipEvent.rejected) {
-				throw new Error(membershipEvent.rejectReason || 'Invite rejected');
-			}
+			if (membershipEvent.rejected) {
+				return {
+					error: membershipEvent.rejectReason || 'Invite rejected',
+					details: {},
+				};
+			}
packages/room/src/manager/power-level-event-wrapper.ts (1)

71-74: LGTM; optionally add explicit return type

Signature tightening is good. Consider annotating the return type for clarity.

Apply:

-	getPowerLevelForUser(
+	getPowerLevelForUser(
 		userId: string,
 		createEvent?: PersistentEventBase<RoomVersion, 'm.room.create'>,
-	) {
+	): number {
packages/federation-sdk/src/services/message.service.ts (1)

85-86: Add a default message for thrown rejections

Protect against undefined/empty rejectReason to avoid throwing Error(undefined).

Apply pattern:

-			throw new Error(event.rejectReason);
+			throw new Error(event.rejectReason || 'Event rejected');

Repeat at each occurrence within this file.

Also applies to: 133-134, 169-170, 225-226, 276-277

packages/room/src/authorizartion-rules/errors.ts (1)

26-29: Constructor payload uses event instance for rejectedBy, but class property stores EventID.

This is fine, but it’s worth documenting in the JSDoc/tsdoc to prevent misuse. Alternatively, consider exposing rejectedEventId alongside rejectedBy to make the semantics explicit to consumers.

packages/room/src/manager/event-wrapper.ts (3)

48-53: Model rejectedBy as optional instead of sentinel empty string.

Using '' as a sentinel for EventID is leaky. Prefer optional to express absence.

Apply this diff:

-  public rejectCode = '';
-
-  public rejectReason = '';
-
-  public rejectedBy = '' as EventID;
+  public rejectCode = '';
+  public rejectReason = '';
+  public rejectedBy?: EventID;

No further changes are required because reject() already guards assignment with a truthy check.


23-25: Duplicate extractDomain implementation (also in rules.ts).

Consider centralizing in a shared util to avoid divergence.


439-447: toStrippedJson returns canonical JSON string; name suggests object.

No issue, but a small rename to toCanonicalStrippedJson or a brief docstring can prevent confusion.

packages/room/src/authorizartion-rules/rules.ts (3)

6-6: Import RoomVersion as a type.

Only used for typing; avoid runtime import.

Apply this diff:

-import { RoomVersion } from '../manager/type';
+import type { RoomVersion } from '../manager/type';

833-837: Remove console.warn in auth path.

Logging here adds noise; the structured NotImplemented error suffices.

Apply this diff:

-    console.warn('third_party_invite not implemented');
     throw new StateResolverAuthorizationError(RejectCodes.NotImplemented, {
       rejectedEvent: event,
       reason: 'third_party_invite not implemented',
     });

387-695: Power level change validation is comprehensive.

Comparisons use “greater than sender’s current level” across defaults, per-event, and per-user entries with accurate rejectedBy. Minor nit: truthy checks (if (value && value > level)) could be made explicit (value !== undefined && value > level) for clarity, but current logic is functionally fine for non-negative levels.

📜 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 94cf2b2 and 44bb0e3.

📒 Files selected for processing (18)
  • packages/federation-sdk/src/services/invite.service.ts (3 hunks)
  • packages/federation-sdk/src/services/message.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (1 hunks)
  • packages/federation-sdk/src/services/state.service.ts (5 hunks)
  • packages/homeserver/src/controllers/internal/invite.controller.ts (1 hunks)
  • packages/room/src/authorizartion-rules/errors.ts (1 hunks)
  • packages/room/src/authorizartion-rules/rules.ts (30 hunks)
  • packages/room/src/index.ts (1 hunks)
  • packages/room/src/manager/event-wrapper.ts (4 hunks)
  • packages/room/src/manager/factory.ts (2 hunks)
  • packages/room/src/manager/power-level-event-wrapper.ts (1 hunks)
  • packages/room/src/manager/v11.ts (1 hunks)
  • packages/room/src/manager/v3.ts (2 hunks)
  • packages/room/src/manager/v6.ts (1 hunks)
  • packages/room/src/manager/v8.ts (1 hunks)
  • packages/room/src/manager/v9.ts (1 hunks)
  • packages/room/src/state_resolution/definitions/definitions.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
packages/room/src/manager/factory.ts (6)
packages/room/src/manager/v3.ts (1)
  • PersistentEventV3 (12-79)
packages/room/src/manager/event-wrapper.ts (1)
  • event (110-119)
packages/room/src/manager/v6.ts (1)
  • PersistentEventV6 (4-15)
packages/room/src/manager/v8.ts (1)
  • PersistentEventV8 (4-14)
packages/room/src/manager/v9.ts (1)
  • PersistentEventV9 (4-16)
packages/room/src/manager/v11.ts (1)
  • PersistentEventV11 (5-46)
packages/room/src/authorizartion-rules/errors.ts (1)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/manager/v9.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v8.ts (1)
  • PersistentEventV8 (4-14)
packages/room/src/manager/power-level-event-wrapper.ts (2)
packages/federation-sdk/src/index.ts (1)
  • PersistentEventBase (21-21)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/state_resolution/definitions/definitions.ts (3)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/authorizartion-rules/errors.ts (1)
  • StateResolverAuthorizationError (48-48)
packages/room/src/manager/event-wrapper.ts (1)
  • event (110-119)
packages/room/src/manager/v11.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v9.ts (1)
  • PersistentEventV9 (4-16)
packages/room/src/manager/v6.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v3.ts (1)
  • PersistentEventV3 (12-79)
packages/room/src/manager/event-wrapper.ts (4)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCode (10-10)
packages/core/src/utils/signJson.ts (1)
  • encodeCanonicalJson (175-195)
packages/room/src/manager/v3.ts (3)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/federation-sdk/src/index.ts (1)
  • PersistentEventBase (21-21)
packages/room/src/manager/type.ts (1)
  • RoomVersion3To11 (3-12)
packages/room/src/authorizartion-rules/rules.ts (3)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/authorizartion-rules/errors.ts (2)
  • StateResolverAuthorizationError (48-48)
  • RejectCodes (4-8)
packages/room/src/manager/event-wrapper.ts (2)
  • event (110-119)
  • stateKey (98-102)
packages/room/src/manager/v8.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v6.ts (1)
  • PersistentEventV6 (4-15)
packages/federation-sdk/src/services/state.service.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCodes (4-8)
packages/room/src/authorizartion-rules/rules.ts (1)
  • checkEventAuthWithState (774-886)
🔇 Additional comments (28)
packages/room/src/index.ts (1)

5-5: LGTM: public re-export of errors

Re-exporting errors at the barrel looks good and matches the new rejection model exposure needs.

packages/room/src/manager/v11.ts (1)

1-7: LGTM: generic parameterization on PersistentEventV11

Aligns with V3/V6/V8/V9; improves typing across event handling.

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

88-89: LGTM! Consistent error property naming.

The change from rejectedReason to rejectReason aligns with the new rejection model used throughout the codebase.


159-160: LGTM! Error property naming consistency maintained.

The update to use rejectReason instead of rejectedReason is consistent with the refactoring pattern.


176-177: LGTM! Error handling standardized.

The change maintains consistency with the new rejectReason property across all error handling paths.

packages/room/src/manager/v9.ts (1)

1-6: LGTM! Generic type parameter properly propagated.

The addition of the generic type parameter Type extends PduType = PduType and its propagation to the base class enables more precise typing of PDU payloads across the event hierarchy.

packages/room/src/manager/v6.ts (1)

1-6: LGTM! Type safety enhanced with generics.

The introduction of the generic type parameter follows the established pattern and properly extends the base class with the type constraint.

packages/room/src/manager/v8.ts (1)

1-6: LGTM! Consistent generic typing pattern.

The generic type parameter addition maintains consistency with the refactoring across all version managers.

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

57-58: LGTM! Error property naming updated.

The change from rejectedReason to rejectReason is consistent with the new rejection model.


62-63: LGTM! Consistent error handling.

The error property update maintains consistency across all rejection paths.


516-517: LGTM! State event error handling updated.

The error property change aligns with the unified error model.


767-769: Improved type safety with EventID.

Good change from string to EventID type for the Map key, ensuring type consistency with event identifiers throughout the codebase.


793-797: Structured rejection with error codes.

The rejection now includes a proper error code (RejectCodes.AuthError), making error handling more deterministic and easier to process downstream.


803-807: LGTM! Enhanced rejection metadata.

Good addition of the rejectedBy parameter (Line 806) to track which event caused the rejection, providing better traceability for debugging authorization failures.


814-815: LGTM! Consistent error property usage.

The error property change maintains consistency with the new rejection model.

packages/room/src/manager/v3.ts (1)

3-3: LGTM! Generic type parameter properly implemented.

The addition of the generic type parameter Type extends PduType = PduType and its propagation to PersistentEventBase<RoomVersion3To11, Type> enables more precise typing throughout the event hierarchy. This is a foundational change that improves type safety across all room versions.

Also applies to: 12-14

packages/room/src/state_resolution/definitions/definitions.ts (3)

25-29: Improved type safety with optional return.

Good change to explicitly include undefined in the return type. This makes the API contract clearer and forces callers to handle the case where a state event might not exist.


432-432: Enhanced type specificity for power level events.

The parameter type refinement from generic PersistentEventBase to PersistentEventBase<RoomVersion, 'm.room.power_levels'> improves type safety and makes the function's expectations explicit.


594-603: Well-structured error handling with typed exceptions.

Excellent implementation of typed error handling. The code now properly catches StateResolverAuthorizationError instances and extracts structured error information (code, reason, rejectedBy) for proper event rejection. Unknown errors are re-thrown to prevent silent failures. This is a significant improvement over generic error handling.

packages/room/src/manager/event-wrapper.ts (1)

408-416: LGTM on reject() shape.

The new reject(code, reason, rejectedBy?) aligns with the propagated error model and downstream usage.

packages/room/src/authorizartion-rules/rules.ts (8)

30-71: Create-event checks look correct and aligned with spec.

Prev events, domain match, known room_version, and creator presence are handled with structured errors.


171-224: Join flow auth checks look solid.

Sender/state_key match, ban guard, public/invite join rules, and precise rejectedBy context are correctly enforced.

Consider adding tests for:

  • join with invite join_rule when invitee has no invite state (should reject with code=auth_error and rejectedBy=join_rules event).
  • join as creator with only the create event as prev (should allow).

257-293: Invite flow: power-level and state guards are correctly enforced.

Good use of rejectedBy to point at power_levels and membership auth events.


295-344: Leave flow: kick/ban semantics are implemented correctly.

Checks for self-leave, sender join requirement, and kick threshold are in place.


346-376: Ban flow: aligns with spec with proper rejectedBy context.


697-772: Auth without state: good validation of auth_events set (duplicates, unexpected, missing create).


784-872: Auth with state: general checks look good.

m.federate enforcement, sender join requirement, power-level threshold, and user-state_key rules are handled consistently.


1-1: Codecov shows low coverage for this file; add targeted tests.

Patch coverage is ~44% with many new branches. Please add tests for:

  • alias validations (state_key missing and domain mismatch),
  • membership branches (join/public, join/invite mismatch, invite sender not join, leave self and kick/bans),
  • power level change rejections (user_default/events_default/state_default and per-user/per-event deltas).

Would you like help drafting these tests?

Comment on lines +803 to 804
throw new Error(membershipEvent.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Surface a proper HTTP error and add a safe fallback message

rejectReason may be undefined. Also, other methods in this service use HttpException/HttpStatus. Align and carry a default message.

Apply:

-				throw new Error(membershipEvent.rejectReason);
+				throw new HttpException(
+					membershipEvent.rejectReason || 'Join rejected',
+					HttpStatus.FORBIDDEN,
+				);
🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/room.service.ts around lines 803-804,
replace the raw throw new Error(membershipEvent.rejectReason) with throwing an
HttpException using Nest's HttpException/HttpStatus and a safe default message;
construct a message like membershipEvent.rejectReason ?? 'Membership request
rejected' and throw new HttpException(message, HttpStatus.BAD_REQUEST) (add the
HttpException and HttpStatus imports if missing) so the error is an HTTP error
and never undefined.

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/room/src/authorizartion-rules/errors.ts (1)

32-36: Serialise events before interpolating into the error message

PersistentEventBase#toStrippedJson() returns a plain object, so the current template string will emit [object Object] for both the rejected event and the auth event, throwing away the useful details we rely on in logs. Please stringify the objects before concatenating them into the message.

-		let message = `${code}: ${rejectedEvent.toStrippedJson()} failed authorization check`;
+		const rejectedEventJson = JSON.stringify(rejectedEvent.toStrippedJson());
+		let message = `${code}: ${rejectedEventJson} failed authorization check`;
 
 		if (rejectedBy) {
-			message += ` against auth event ${rejectedBy.toStrippedJson()}`;
+			message += ` against auth event ${JSON.stringify(rejectedBy.toStrippedJson())}`;
 		}
📜 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 44bb0e3 and 494c581.

📒 Files selected for processing (2)
  • packages/room/src/authorizartion-rules/errors.ts (1 hunks)
  • packages/room/src/manager/event-wrapper.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/room/src/manager/event-wrapper.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/room/src/authorizartion-rules/errors.ts (1)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)

@ggazzo ggazzo force-pushed the correct-state-resolver-error branch from 494c581 to 39746db Compare September 26, 2025 21:47
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 (13)
packages/room/src/manager/v6.ts (1)

1-1: Use type‑only import to avoid runtime cost.

Mark PduType import as type‑only.

-import { PduType } from '../types/v3-11';
+import { type PduType } from '../types/v3-11';
packages/room/src/manager/v9.ts (1)

1-1: Use type‑only import to avoid runtime cost.

Mark PduType import as type‑only.

-import { PduType } from '../types/v3-11';
+import { type PduType } from '../types/v3-11';
packages/federation-sdk/src/services/state.service.ts (1)

767-786: Unify Map key typing to EventID across both auth maps.

You updated requiredAuthEventsWeHaveSeenMap to Map<EventID, ...>; mirror that for authEventsReferencedMap to avoid string/EventID drift and get better type safety.

-		const authEventsReferencedMap = new Map<string, PersistentEventBase>();
+		const authEventsReferencedMap = new Map<EventID, PersistentEventBase>();
packages/room/src/manager/v3.ts (1)

3-3: Import PduType as a type-only import to avoid a runtime require.

Prevents unnecessary JS emission and reduces the risk of cycles.

Apply this diff:

-import { PduType } from '../types/v3-11';
+import type { PduType } from '../types/v3-11';
packages/room/src/authorizartion-rules/errors.ts (2)

15-21: Make error fields readonly.

They’re immutable after construction; marking readonly communicates intent and prevents accidental mutation.

Apply this diff:

-	reason: string;
+	readonly reason: string;
 
-	rejectedBy?: EventID;
+	readonly rejectedBy?: EventID;
 
 	constructor(
-		public code: RejectCode,
+		public readonly code: RejectCode,

31-39: Consider slimming the error message payload.

Embedding canonical JSON of events in Error.message can bloat logs. Consider logging only IDs in the message, and use structured logging for rich context where needed.

Example alternative:

- let message = `${code}: ${rejectedEvent.toStrippedJson()} failed authorization check`;
+ let message = `${code}: event ${rejectedEvent.eventId} failed authorization check`;
- if (rejectedBy) {
-   message += ` against auth event ${rejectedBy.toStrippedJson()}`;
- }
+ if (rejectedBy) {
+   message += ` against auth event ${rejectedBy.eventId}`;
+ }
packages/room/src/state_resolution/definitions/definitions.ts (3)

8-8: Import PersistentEventBase as type-only.

Only used in type positions; drops a runtime dependency edge.

Apply this diff:

-import { PersistentEventBase } from '../../manager/event-wrapper';
+import type { PersistentEventBase } from '../../manager/event-wrapper';

25-29: Annotate the return type to avoid relying on casts.

Improves readability and type inference.

Apply this diff:

-export function getStateByMapKey<T extends PduType>(
+export function getStateByMapKey<T extends PduType>(
   map: Map<StateMapKey, PersistentEventBase>,
   filter: {
     type: T;
     state_key?: string;
   },
-) {
-	return map.get(getStateMapKey(filter)) as
-		| PersistentEventBase<RoomVersion, T>
-		| undefined;
+) : PersistentEventBase<RoomVersion, T> | undefined {
+  return map.get(getStateMapKey(filter)) as
+    | PersistentEventBase<RoomVersion, T>
+    | undefined;
 }

595-603: Log after rejection with structured fields; avoid dumping full Error.

Sets rejection first, then logs minimal context. Keeps unknown errors fatal.

Apply this diff:

-		} catch (error) {
-			console.warn('event not allowed', error);
-			if (error instanceof StateResolverAuthorizationError) {
-				event.reject(error.code, error.reason, error.rejectedBy);
-				continue;
-			}
-
-			// if unknown error we halt building new state
-			throw error;
-		}
+		} catch (error) {
+			if (error instanceof StateResolverAuthorizationError) {
+				event.reject(error.code, error.reason, error.rejectedBy);
+				console.warn('event rejected', {
+					eventId: event.eventId,
+					code: error.code,
+					reason: error.reason,
+					rejectedBy: error.rejectedBy,
+				});
+				continue;
+			}
+			throw error; // unknown errors halt building new state
+		}
packages/room/src/manager/event-wrapper.ts (3)

48-53: Prefer undefined over empty-string sentinels for rejection fields.

Removes sentinel coupling and simplifies truthiness checks.

Apply this diff:

-	public rejectCode = '';
-
-	public rejectReason = '';
-
-	public rejectedBy = '' as EventID;
+	public rejectCode?: RejectCode;
+
+	public rejectReason?: string;
+
+	public rejectedBy?: EventID;

409-416: Align rejected getter and reject() with optional fields.

Avoid string comparisons for state.

Apply this diff:

-	get rejected() {
-		return this.rejectCode !== '';
-	}
+	get rejected() {
+		return this.rejectCode !== undefined;
+	}
 
-	reject(code: RejectCode, reason: string, rejectedBy?: EventID) {
-		this.rejectCode = code;
-		this.rejectReason = reason;
-		if (rejectedBy) this.rejectedBy = rejectedBy;
-	}
+	reject(code: RejectCode, reason: string, rejectedBy?: EventID) {
+		this.rejectCode = code;
+		this.rejectReason = reason;
+		this.rejectedBy = rejectedBy;
+	}

439-447: Optionally return a POJO and let callers choose serialization.

Returning a string reduces flexibility (e.g., structured logs). Consider returning an object and letting callers call encodeCanonicalJson.

Example:

-	toStrippedJson() {
-		return encodeCanonicalJson({
+	toStrippedJson() {
+		return encodeCanonicalJson(this.toStrippedObject());
+	}
+
+	toStrippedObject() {
+		return {
 			eventId: this.eventId,
 			type: this.type,
 			roomId: this.roomId,
 			sender: this.sender,
 			stateKey: this.stateKey,
-		});
+		};
 	}
packages/room/src/authorizartion-rules/rules.ts (1)

103-111: Check for membership property presence, not just event type.

Current check can pass when content.membership is missing; the thrown reason promises to catch that earlier.

Apply this diff:

-	if (
-		!membershipEventToCheck.stateKey ||
-		!membershipEventToCheck.isMembershipEvent()
-	) {
+	if (
+		!membershipEventToCheck.stateKey ||
+		membershipEventToCheck.getContent().membership === undefined
+	) {
 		throw new StateResolverAuthorizationError(RejectCodes.AuthError, {
 			rejectedEvent: membershipEventToCheck,
 			reason: 'm.room.member event has no state_key or membership property',
 		});
 	}
📜 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 494c581 and 39746db.

📒 Files selected for processing (18)
  • packages/federation-sdk/src/services/invite.service.ts (3 hunks)
  • packages/federation-sdk/src/services/message.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (1 hunks)
  • packages/federation-sdk/src/services/state.service.ts (5 hunks)
  • packages/homeserver/src/controllers/internal/invite.controller.ts (1 hunks)
  • packages/room/src/authorizartion-rules/errors.ts (1 hunks)
  • packages/room/src/authorizartion-rules/rules.ts (30 hunks)
  • packages/room/src/index.ts (1 hunks)
  • packages/room/src/manager/event-wrapper.ts (4 hunks)
  • packages/room/src/manager/factory.ts (2 hunks)
  • packages/room/src/manager/power-level-event-wrapper.ts (1 hunks)
  • packages/room/src/manager/v11.ts (1 hunks)
  • packages/room/src/manager/v3.ts (2 hunks)
  • packages/room/src/manager/v6.ts (1 hunks)
  • packages/room/src/manager/v8.ts (1 hunks)
  • packages/room/src/manager/v9.ts (1 hunks)
  • packages/room/src/state_resolution/definitions/definitions.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/room/src/manager/power-level-event-wrapper.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/federation-sdk/src/services/message.service.ts
  • packages/room/src/manager/factory.ts
  • packages/federation-sdk/src/services/invite.service.ts
  • packages/room/src/manager/v8.ts
  • packages/homeserver/src/controllers/internal/invite.controller.ts
  • packages/federation-sdk/src/services/send-join.service.ts
🧰 Additional context used
🧬 Code graph analysis (9)
packages/room/src/manager/v6.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v3.ts (1)
  • PersistentEventV3 (12-79)
packages/room/src/manager/v3.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/type.ts (1)
  • RoomVersion3To11 (3-12)
packages/room/src/manager/v11.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v9.ts (1)
  • PersistentEventV9 (4-16)
packages/federation-sdk/src/services/state.service.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCodes (4-8)
packages/room/src/authorizartion-rules/rules.ts (1)
  • checkEventAuthWithState (774-886)
packages/room/src/authorizartion-rules/errors.ts (1)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/manager/event-wrapper.ts (5)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCode (10-10)
packages/core/src/utils/signJson.ts (1)
  • encodeCanonicalJson (175-195)
packages/room/src/authorizartion-rules/rules.ts (6)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (21-21)
  • RoomVersion (22-22)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/authorizartion-rules/errors.ts (2)
  • StateResolverAuthorizationError (48-48)
  • RejectCodes (4-8)
packages/room/src/manager/power-level-event-wrapper.ts (1)
  • PowerLevelEvent (199-199)
packages/core/src/events/m.room.create.ts (1)
  • roomCreateEvent (26-43)
packages/room/src/manager/event-wrapper.ts (2)
  • event (110-119)
  • stateKey (98-102)
packages/room/src/manager/v9.ts (2)
packages/room/src/types/v3-11.ts (1)
  • PduType (47-47)
packages/room/src/manager/v8.ts (1)
  • PersistentEventV8 (4-14)
packages/room/src/state_resolution/definitions/definitions.ts (3)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/authorizartion-rules/errors.ts (1)
  • StateResolverAuthorizationError (48-48)
packages/room/src/manager/event-wrapper.ts (1)
  • event (110-119)
🔇 Additional comments (15)
packages/room/src/index.ts (1)

5-5: Public re‑export of errors is fine; confirm API surface change.

Exposing errors via the barrel looks intentional and matches the new error model. Please confirm this is part of the public API and won’t require a semver bump or deprecation notes.

packages/room/src/manager/v6.ts (1)

4-6: Generic propagation LGTM.

Making PersistentEventV6 generic and forwarding the type to the base aligns with the rest of the hierarchy.

packages/room/src/manager/v9.ts (1)

4-6: Generic propagation LGTM.

Matches v6/v11 patterns and keeps typings consistent.

packages/room/src/manager/v11.ts (2)

1-1: Type‑only import LGTM.

Keeps the import erased at runtime.


5-7: Generic propagation LGTM.

Correctly extends with Type extends PduType.

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

803-806: Validate rejectedBy semantics.

You pass eventId (the expected auth event) as rejectedBy. Confirm this matches the contract (ID of the auth event that caused the rejection). If the message references a wrong auth event (present but incorrect), consider passing that actual referenced ID instead.


21-21: Import of RejectCodes LGTM.

Consistent with the new error model.


57-57: Preserve structured rejection in state.service.ts
Replace all throw new Error(...rejectReason) calls (lines 57, 62, 516, 797, 807, 814) with a throwRejection(event) helper that throws StateResolverAuthorizationError carrying rejectCode, rejectReason, and rejectedBy.

At the top of packages/federation-sdk/src/services/state.service.ts add:

import { StateResolverAuthorizationError, RejectCodes } from '@rocket.chat/federation-room';

function throwRejection(event: { rejectReason?: string; rejectCode?: RejectCodes; rejectedBy?: string }): never {
  const code   = event.rejectCode ?? RejectCodes.ValidationError;
  const reason = event.rejectReason ?? 'event rejected';
  throw new StateResolverAuthorizationError(code, {
    rejectedEvent: event,
    rejectedBy:    event.rejectedBy,
    reason,
  });
}

Confirm upstream callers still expect and correctly handle this typed error.

packages/room/src/manager/v3.ts (1)

12-14: LGTM: generic typing on PersistentEventV3.

The generic constraint and inheritance align with PersistentEventBase<RoomVersion3To11, Type>.

packages/room/src/authorizartion-rules/errors.ts (1)

4-11: LGTM: standardized reject codes and type.

Clear, extensible code system for downstream consumers.

packages/room/src/manager/event-wrapper.ts (2)

45-47: LGTM: default RoomVersion generic broadened.

Aligns with multi-version usage.


48-53: No stale uses of rejectedReason; all call sites use event.reject(code, reason, rejectedBy).

packages/room/src/authorizartion-rules/rules.ts (3)

30-33: LGTM: typed create event guard.

Signature restriction to 'm.room.create' improves safety.


75-92: Fix event type in error messages (aliases vs canonical_alias).

Messages should reference m.room.aliases.

Apply this diff:

-      reason: 'm.room.canonical_alias event has no state_key',
+      reason: 'm.room.aliases event has no state_key',
...
-      reason:
-        'm.room.canonical_alias event sender domain does not match state_key',
+      reason:
+        'm.room.aliases event sender domain does not match state_key',

697-706: Add tests for new rejection paths (Codecov −0.26%)
Cover at least:

  • join: non-matching sender/state_key; invite join_rule without invite; banned sender
  • invite: sender not in room; invitee already join/ban; insufficient power
  • leave/ban: insufficient power; sender not in room
  • create: prev_events present; domain mismatch; unrecognized version; missing creator

Use rg -n "throw new StateResolverAuthorizationError" packages/room/src/authorization-rules/rules.ts to locate all branches and npm test -- --coverage to identify untested paths.

@ggazzo ggazzo force-pushed the correct-state-resolver-error branch from 39746db to 42c1974 Compare September 26, 2025 22:23
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: 6

Caution

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

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

83-93: Propagate structured invite rejection info

checkEventAuthWithState now raises StateResolverAuthorizationError (and tags the event). Catching that as a plain Error here drops the new rejectCode/rejectReason/rejectedBy details that the follow-up work relies on. Please forward the structured error (or at least its fields) instead of constructing a generic Error, so consumers can distinguish auth errors from other failures.


150-177: Don’t swallow the rejection metadata on remote invites

Same concern as above: throwing a fresh Error with only rejectReason throws away rejectCode and rejectedBy. Propagate the StateResolverAuthorizationError (or clone its fields) so upstream logic can persist the rejection accurately.


169-188: Persisted rejection loses structured context

When the persisted invite is rejected after we fetch room state, we again collapse the structured rejection into a bare Error. Please return/throw an error that retains rejectCode, rejectReason, and rejectedBy so callers can handle it appropriately.

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

53-58: Catch and rethrow structured auth failures

checkEventAuthWithState now raises StateResolverAuthorizationError. Without a try/catch here, that exception bubbles out, bypassing the event.rejected check and losing the metadata. Wrap the auth call, intercept StateResolverAuthorizationError, call event.reject(...), and rethrow (or return) the structured error instead of new Error(event.rejectReason).

🧹 Nitpick comments (1)
packages/room/src/state_resolution/definitions/definitions.ts (1)

595-603: Avoid losing rejection context

Good call catching StateResolverAuthorizationError, but we still end up dropping error when we Console.warn and continue. Consider returning/propagating the error (or at least the metadata) to the caller so it can persist the rejection instead of silently skipping the event.

📜 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 39746db and 42c1974.

📒 Files selected for processing (12)
  • packages/federation-sdk/src/services/invite.service.ts (3 hunks)
  • packages/federation-sdk/src/services/message.service.ts (5 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (1 hunks)
  • packages/federation-sdk/src/services/state.service.ts (5 hunks)
  • packages/homeserver/src/controllers/internal/invite.controller.ts (1 hunks)
  • packages/room/src/authorizartion-rules/errors.ts (1 hunks)
  • packages/room/src/authorizartion-rules/rules.ts (30 hunks)
  • packages/room/src/index.ts (1 hunks)
  • packages/room/src/manager/event-wrapper.ts (4 hunks)
  • packages/room/src/manager/factory.ts (1 hunks)
  • packages/room/src/state_resolution/definitions/definitions.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/federation-sdk/src/services/send-join.service.ts
  • packages/room/src/manager/factory.ts
🧰 Additional context used
🧬 Code graph analysis (5)
packages/federation-sdk/src/services/state.service.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCodes (4-8)
packages/room/src/authorizartion-rules/rules.ts (1)
  • checkEventAuthWithState (776-888)
packages/room/src/authorizartion-rules/errors.ts (1)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/rules.ts (3)
packages/federation-sdk/src/index.ts (2)
  • PersistentEventBase (21-21)
  • RoomVersion (22-22)
packages/room/src/manager/type.ts (1)
  • RoomVersion (14-14)
packages/room/src/authorizartion-rules/errors.ts (2)
  • StateResolverAuthorizationError (48-48)
  • RejectCodes (4-8)
packages/room/src/state_resolution/definitions/definitions.ts (2)
packages/room/src/authorizartion-rules/errors.ts (1)
  • StateResolverAuthorizationError (48-48)
packages/room/src/manager/event-wrapper.ts (1)
  • event (113-122)
packages/room/src/manager/event-wrapper.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/authorizartion-rules/errors.ts (1)
  • RejectCode (10-10)
packages/core/src/utils/signJson.ts (1)
  • encodeCanonicalJson (175-195)
🔇 Additional comments (4)
packages/federation-sdk/src/services/message.service.ts (1)

85-85: Consistent rejection messaging propagated

Using rejectReason matches the updated PersistentEventBase contract, so callers will now see the same rejection text the state resolver emits. Looks good to me.

Also applies to: 134-134, 170-170, 226-226, 277-277

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

803-804: Throw an HttpException with a fallback message

new Error bubbles up as a 500 with an undefined message when rejectReason is missing. Surface a proper 4xx so clients get a stable error contract.

-			throw new Error(membershipEvent.rejectReason);
+			throw new HttpException(
+				membershipEvent.rejectReason || 'Join rejected',
+				HttpStatus.FORBIDDEN,
+			);
packages/room/src/index.ts (1)

5-5: Exporting the new error surface looks good

Barrel now re-exports the authorization errors, letting SDK consumers import RejectCodes and StateResolverAuthorizationError from the package root. 👍

packages/room/src/authorizartion-rules/rules.ts (1)

80-91: Fix alias error strings to reference m.room.aliases

Line 82 / Line 90: these rejection reasons still mention m.room.canonical_alias even though the validator operates on m.room.aliases. Please align the wording (this was called out in the prior review round).

-			reason: 'm.room.canonical_alias event has no state_key',
+			reason: 'm.room.aliases event has no state_key',-			reason:
-				'm.room.canonical_alias event sender domain does not match state_key',
+			reason:
+				'm.room.aliases event sender domain does not match state_key',

Comment on lines +986 to 987
throw new Error(joinEventFinal.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Return a structured HTTP error

Same issue on the remote join path: a plain Error becomes a 500 and leaks an "undefined" message when the join was legitimately rejected. Emit an HttpException with a safe fallback instead.

-		if (joinEventFinal.rejected) {
-			throw new Error(joinEventFinal.rejectReason);
+		if (joinEventFinal.rejected) {
+			throw new HttpException(
+				joinEventFinal.rejectReason || 'Join rejected',
+				HttpStatus.FORBIDDEN,
+			);
📝 Committable suggestion

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

Suggested change
throw new Error(joinEventFinal.rejectReason);
}
if (joinEventFinal.rejected) {
throw new HttpException(
joinEventFinal.rejectReason || 'Join rejected',
HttpStatus.FORBIDDEN,
);
}
🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/room.service.ts around lines 986-987,
replace throwing a plain Error with throwing a structured HttpException that
uses the joinEventFinal.rejectReason if present or a safe fallback message like
"Join request rejected", and set an appropriate HTTP status (e.g.,
HttpStatus.FORBIDDEN); also add the necessary imports (HttpException and
HttpStatus from @nestjs/common) and apply the same change to the remote join
path to avoid leaking "undefined" and to return a proper HTTP error.

Comment on lines +781 to 785
RejectCodes.AuthError,
`Auth events referenced in message do not match, expected ${requiredAuthEventsWeHaveSeenMap.size} but got ${authEventsReferencedMap.size}`,
);
throw new Error(event.rejectedReason);
throw new Error(event.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve RejectCodes when failing auth prechecks

After calling event.reject(RejectCodes.AuthError, …), throwing new Error(event.rejectReason) discards the code and rejectedBy. Please throw/return the StateResolverAuthorizationError (or include its fields) so higher layers can differentiate auth errors from other failures.

Comment on lines 787 to 795
for (const [eventId] of requiredAuthEventsWeHaveSeenMap) {
if (!authEventsReferencedMap.has(eventId)) {
event.reject(
RejectCodes.AuthError,
`wrong auth event in message, expected ${eventId} but not found in event`,
eventId,
);
throw new Error(event.rejectedReason);
throw new Error(event.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Propagate full rejection context

Same as above: we reject the event with RejectCodes.AuthError, but then throw away the structured metadata by emitting a plain Error. Please surface the structured error object instead.

🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/state.service.ts around lines 787 to
795, the code calls event.reject(RejectCodes.AuthError, ...) but then throws a
plain Error using event.rejectReason, losing the structured rejection metadata;
change this to capture and rethrow the structured rejection object returned by
event.reject (or construct and throw an object containing the RejectCode,
rejectReason and relevant metadata) instead of throwing new
Error(event.rejectReason) so callers receive the full rejection context.

Comment on lines 798 to 802
// now we validate against auth rules
await checkEventAuthWithState(event, room, store);
if (event.rejected) {
throw new Error(event.rejectedReason);
throw new Error(event.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unhandled StateResolverAuthorizationError

checkEventAuthWithState can now throw StateResolverAuthorizationError. Catch it here, ensure event.reject is called, and propagate the structured error so callers can handle authorization failures deterministically.

🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/state.service.ts around lines 798 to
802, the call to checkEventAuthWithState can throw
StateResolverAuthorizationError but the code only checks event.rejected
afterwards; wrap the await checkEventAuthWithState(event, room, store) in a
try/catch, catch StateResolverAuthorizationError specifically, call
event.reject(...) (providing the error message/reason and setting
event.rejectReason as needed), then rethrow the caught
StateResolverAuthorizationError so callers receive the structured error; for any
other exceptions rethrow them unchanged.

Comment on lines 63 to 65
if (membershipEvent.rejected) {
throw new Error(membershipEvent.rejectedReason);
throw new Error(membershipEvent.rejectReason);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Surface the rejection details instead of masking them

We now tag state-resolver rejections with rejectCode, rejectReason, and rejectedBy. Re-throwing a generic Error discards that context, making it impossible for callers to tell why the invite was rejected or which auth event caused it. Please propagate the structured rejection (e.g., by rethrowing the original StateResolverAuthorizationError or wrapping the code/reason in a richer error type) so downstream logic can act on it.

🤖 Prompt for AI Agents
In packages/homeserver/src/controllers/internal/invite.controller.ts around
lines 63 to 65, the code throws a generic Error when membershipEvent.rejected is
true, which discards the structured rejection details (rejectCode, rejectReason,
rejectedBy); instead rethrow the original structured rejection (e.g. the
StateResolverAuthorizationError instance) or construct and throw a new error
that preserves those fields (include rejectCode, rejectReason, rejectedBy and
original error as cause) so callers can inspect the rejection details for
downstream handling.

Comment on lines +447 to +455
toStrippedJson() {
return encodeCanonicalJson({
eventId: this.eventId,
type: this.type,
roomId: this.roomId,
sender: this.sender,
stateKey: this.stateKey,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent toStrippedJson from emitting invalid JSON when stateKey is missing

Line 452: when stateKey is undefined (timeline events), encodeCanonicalJson renders it as stateKey:undefined, which is not valid JSON and breaks downstream parsing. Please omit the property when it is absent.

-		return encodeCanonicalJson({
-			eventId: this.eventId,
-			type: this.type,
-			roomId: this.roomId,
-			sender: this.sender,
-			stateKey: this.stateKey,
-		});
+		const stripped: Record<string, string> = {
+			eventId: this.eventId,
+			type: this.type,
+			roomId: this.roomId,
+			sender: this.sender,
+		};
+
+		if (this.stateKey !== undefined) {
+			stripped.stateKey = this.stateKey;
+		}
+
+		return encodeCanonicalJson(stripped);
📝 Committable suggestion

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

Suggested change
toStrippedJson() {
return encodeCanonicalJson({
eventId: this.eventId,
type: this.type,
roomId: this.roomId,
sender: this.sender,
stateKey: this.stateKey,
});
}
toStrippedJson() {
const stripped: Record<string, string> = {
eventId: this.eventId,
type: this.type,
roomId: this.roomId,
sender: this.sender,
};
if (this.stateKey !== undefined) {
stripped.stateKey = this.stateKey;
}
return encodeCanonicalJson(stripped);
}
🤖 Prompt for AI Agents
In packages/room/src/manager/event-wrapper.ts around lines 447 to 455,
toStrippedJson currently always includes stateKey which results in invalid JSON
when stateKey is undefined; change the construction of the object passed to
encodeCanonicalJson so stateKey is only included when it is not undefined (e.g.
build a base object with eventId,type,roomId,sender and conditionally add
stateKey when !== undefined) and then pass that cleaned object to
encodeCanonicalJson so timeline events omit the property.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants