Skip to content

Conversation

@ricardogarim
Copy link
Contributor

@ricardogarim ricardogarim commented Sep 12, 2025

Works along with RocketChat/homeserver#185.

For anyone testing with Synapse as HS1, remember to add these two properties to the YAML spec file:

  • enable_media_repo: true
  • enable_authenticated_media: true

Summary by CodeRabbit

  • New Features
    • Enforced media access control for federated media downloads and thumbnails, requiring valid authorization before content is served.
  • Bug Fixes
    • Explicit 401/403 responses with clear, structured messages when access is denied.
    • Prevented storing/serving null media content by validating downloaded content.
  • Chores
    • Reduced internal error logging for download failures without changing user-facing behavior.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 12, 2025

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

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 12, 2025

⚠️ No Changeset found

Latest commit: 9186a1e

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

This PR includes no changesets

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

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

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

Walkthrough

Adds a canAccessMedia middleware for federation media routes, wires it into download and thumbnail endpoints, returns 401/403/500 Matrix-style errors when appropriate, adds a null-content guard in remote file download, and updates route error schemas. No public API signatures change.

Changes

Cohort / File(s) Summary
Middleware: Federation media access control
ee/packages/federation-matrix/src/api/middlewares.ts
Adds exported factory canAccessMedia(federationAuth) returning Hono middleware. Parses request, constructs signature URI (path + query [+ body for non-GET]), calls federationAuth.canAccessMediaFromAuthorizationHeader, and short-circuits with 401/403/500 Matrix-style error payloads when unauthorized/forbidden/errors occur.
Media routes wiring and schemas
ee/packages/federation-matrix/src/api/_matrix/media.ts
Imports and applies canAccessMedia(federationAuth) to /v1/media/download/:mediaId and /v1/media/thumbnail/:mediaId. Extends route error schemas to include 403. Inserts middleware before handlers and removes error logging in download catch; thumbnail catch still logs. No public API surface changes.
Media download guard
ee/packages/federation-matrix/src/services/MatrixMediaService.ts
Adds a null-content guard in downloadAndStoreRemoteFile that throws if the downloaded buffer is falsy, preventing upload/processing of null content.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant M as Media API Route
  participant MW as canAccessMedia (middleware)
  participant A as EventAuthorizationService

  C->>M: Request /v1/media/(download|thumbnail)/:mediaId
  M->>MW: Invoke middleware
  MW->>MW: Build signature URI (path + query [+ body if non-GET])
  MW->>A: canAccessMediaFromAuthorizationHeader(mediaId, auth, method, uri, body)
  alt Authorized
    A-->>MW: allowed
    MW-->>M: next()
    M-->>C: Proceed to handler (stream/thumbnail)
  else Unauthorized
    A-->>MW: unauthorized
    MW-->>C: 401 { errcode: M_UNAUTHORIZED, error }
  else Forbidden
    A-->>MW: forbidden
    MW-->>C: 403 { errcode: M_FORBIDDEN, error }
  else Error
    A-->>MW: throws/error
    MW-->>C: 500 { errcode: M_UNKNOWN, error }
  end
  note over M: Download route catch no longer logs errors\nThumbnail route catch still logs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I thump my paws on cables fine,
A gate now guards the media line—
401s for sneaky hops,
403s where access stops.
With signed URI I take a peek,
Then hop along when tokens speak. 🥕🐇

Pre-merge checks and finishing touches

✅ 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: adds Federation canAccessMedia middleware" concisely and accurately summarizes the primary change — introducing a canAccessMedia middleware for federation media access. It directly reflects the main change in the changeset and is clear to reviewers scanning history. The title avoids unrelated details and is sufficiently specific for this PR.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/federation-canAccessMedia-middleware

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
ee/packages/federation-matrix/src/api/middlewares.ts (2)

38-38: Avoid JSON-only parsing for non-GET; use raw text and gate by method.

HEAD/DELETE or non-JSON bodies will throw on json().

Apply:

-			const body = method === 'GET' ? undefined : await c.req.json();
+			const body =
+				method === 'GET' || method === 'HEAD' ? undefined : await c.req.text();

5-21: Tighten typing of errCodes.

Use a discriminated constant map to prevent typos and enable exhaustiveness.

Apply:

-const errCodes: Record<string, { errcode: string; error: string; status: ContentfulStatusCode }> = {
+const errCodes = {
 	M_UNAUTHORIZED: {
 		errcode: 'M_UNAUTHORIZED',
 		error: 'Invalid or missing signature',
 		status: 401 as ContentfulStatusCode,
 	},
 	M_FORBIDDEN: {
 		errcode: 'M_FORBIDDEN',
 		error: 'Access denied',
 		status: 403 as ContentfulStatusCode,
 	},
 	M_UNKNOWN: {
 		errcode: 'M_UNKNOWN',
 		error: 'Internal server error while processing request',
 		status: 500 as ContentfulStatusCode,
 	},
-};
+} as const;
ee/packages/federation-matrix/src/api/_matrix/media.ts (1)

149-149: Unify handler param typing.

Use the same variable/type as the download route for consistency.

Apply:

-		async (context: any) => {
+		async (c) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3cf61c and 5343239.

📒 Files selected for processing (2)
  • ee/packages/federation-matrix/src/api/_matrix/media.ts (4 hunks)
  • ee/packages/federation-matrix/src/api/middlewares.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/federation-matrix/src/api/_matrix/media.ts (1)
ee/packages/federation-matrix/src/api/middlewares.ts (1)
  • canAccessMedia (23-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
ee/packages/federation-matrix/src/api/_matrix/media.ts (2)

97-98: Middleware integration LGTM.

Applying canAccessMedia before handlers on both routes is correct.

Also applies to: 148-149


127-132: Confirm logging intent (inconsistent between routes).

Download no longer logs on 500; thumbnail still logs. If noise reduction was intentional, consider debug-level logging in both or neither for consistency.

Also applies to: 178-184

Comment on lines 141 to 145
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
},
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

Thumbnail route schema missing 500 while handler returns 500.

This mismatches declared responses and can fail validation.

Apply:

 			response: {
+				500: isErrorResponseProps,
 			},
📝 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
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
},
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
500: isErrorResponseProps,
},
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/media.ts around lines 141 to
145, the thumbnail route's response schema declares 200/401/403/404 but omits
500 while the handler can return 500; add a 500 entry mapping to
isErrorResponseProps in the responses object for the thumbnail route so the
schema matches the handler and validation passes.

Comment on lines +28 to +35
const { path } = c.req;
const query = c.req.query();

let uriForSignature = path;
const queryString = new URLSearchParams(query).toString();
if (queryString) {
uriForSignature = `${path}?${queryString}`;
}
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

Preserve raw query string (ordering and duplicates) for signature verification.

Rebuilding the query via URLSearchParams can reorder keys and drop multi-valued params, causing signature mismatches. Use the raw URL.

Apply:

-		const { path } = c.req;
-		const query = c.req.query();
-
-		let uriForSignature = path;
-		const queryString = new URLSearchParams(query).toString();
-		if (queryString) {
-			uriForSignature = `${path}?${queryString}`;
-		}
+		const url = new URL(c.req.url);
+		const uriForSignature = `${url.pathname}${url.search}`;

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

🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/middlewares.ts around lines 28-35, the
code rebuilds the query string with URLSearchParams which can reorder keys and
drop repeated params; instead obtain the raw request URL including its original
query string (e.g. use c.req.url or c.req.rawUrl / c.req.originalUrl depending
on the framework) and use that for uriForSignature; if the raw URL may include
the origin, strip the origin leaving path + query, and also strip any fragment
(#...) before using it for signature verification so ordering and duplicates are
preserved.

Comment on lines +48 to +56
if (!verificationResult.authorized) {
const errorResponse = errCodes[verificationResult.errorCode];
return c.json(
{
errcode: errorResponse.errcode,
error: errorResponse.error,
},
errorResponse.status,
);
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

Handle unknown errorCode to avoid runtime crash.

If the service returns an unexpected code, errorResponse is undefined and accessing its fields throws.

Apply:

-			if (!verificationResult.authorized) {
-				const errorResponse = errCodes[verificationResult.errorCode];
+			if (!verificationResult.authorized) {
+				const errorResponse = errCodes[verificationResult.errorCode] ?? errCodes.M_FORBIDDEN;
 				return c.json(
 					{
 						errcode: errorResponse.errcode,
 						error: errorResponse.error,
 					},
 					errorResponse.status,
 				);
 			}
📝 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
if (!verificationResult.authorized) {
const errorResponse = errCodes[verificationResult.errorCode];
return c.json(
{
errcode: errorResponse.errcode,
error: errorResponse.error,
},
errorResponse.status,
);
if (!verificationResult.authorized) {
const errorResponse = errCodes[verificationResult.errorCode] ?? errCodes.M_FORBIDDEN;
return c.json(
{
errcode: errorResponse.errcode,
error: errorResponse.error,
},
errorResponse.status,
);
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/middlewares.ts around lines 48 to 56,
the code assumes errCodes[verificationResult.errorCode] always exists and will
crash if undefined; guard against missing errorResponse by checking its
existence and using a sensible default (e.g., generic errcode, error message and
500 status) or map unknown codes to a fallback entry, then return that fallback
instead; optionally log the unexpected errorCode for debugging before returning.

Comment on lines +60 to +62
} catch (error) {
return c.json(errCodes.M_UNKNOWN, 500);
}
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

Return only Matrix error fields; drop extra “status” property.

Body currently includes “status,” which violates the route error schema.

Apply:

-		} catch (error) {
-			return c.json(errCodes.M_UNKNOWN, 500);
-		}
+		} catch (error) {
+			return c.json({ errcode: errCodes.M_UNKNOWN.errcode, error: errCodes.M_UNKNOWN.error }, 500);
+		}
📝 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
} catch (error) {
return c.json(errCodes.M_UNKNOWN, 500);
}
} catch (error) {
return c.json({ errcode: errCodes.M_UNKNOWN.errcode, error: errCodes.M_UNKNOWN.error }, 500);
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/middlewares.ts around lines 60 to 62,
the catch block returns a response body containing an extra "status" property
which violates the Matrix route error schema; update the error response so the
JSON body contains only the Matrix error fields (errcode and error) and remove
any "status" field while still returning the appropriate HTTP status code (500).
Ensure you build the response object with only the Matrix-standard keys and send
that with the 500 status.

@ricardogarim ricardogarim marked this pull request as ready for review September 12, 2025 03:43
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)
ee/packages/federation-matrix/src/services/MatrixMediaService.ts (1)

115-117: Defensive check is good; also guard against zero-length and wrong type.

Today this only catches null/undefined. Add a Buffer-type assertion and zero-length check to avoid uploading empty/invalid payloads.

-			if (!buffer) {
-				throw new Error('Download from remote server returned null content.');
-			}
+			if (!buffer || !Buffer.isBuffer(buffer) || buffer.length === 0) {
+				throw new Error(
+					`Download from remote server returned empty or invalid content for ${parts.serverName}/${parts.mediaId}.`,
+				);
+			}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5343239 and 9186a1e.

📒 Files selected for processing (1)
  • ee/packages/federation-matrix/src/services/MatrixMediaService.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/federation-matrix/src/services/MatrixMediaService.ts (1)
apps/meteor/server/startup/initialData.js (1)
  • buffer (148-148)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

@ricardogarim ricardogarim merged commit 620670b into feat/federation-files-2 Sep 13, 2025
30 of 32 checks passed
@ricardogarim ricardogarim deleted the feat/federation-canAccessMedia-middleware branch September 13, 2025 18:07
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.

2 participants