diff --git a/ee/packages/federation-matrix/src/api/_matrix/media.ts b/ee/packages/federation-matrix/src/api/_matrix/media.ts index c6f3b8a4f0d0c..7ab093f9034a8 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/media.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/media.ts @@ -7,6 +7,7 @@ import { Logger } from '@rocket.chat/logger'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; import { MatrixMediaService } from '../../services/MatrixMediaService'; +import { canAccessMedia } from '../middlewares'; const logger = new Logger('federation-matrix:media'); @@ -76,7 +77,7 @@ async function getMediaFile(mediaId: string, serverName: string): Promise<{ file } export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => { - const { config } = homeserverServices; + const { config, federationAuth } = homeserverServices; const router = new Router('/federation'); router.get( @@ -86,12 +87,14 @@ export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => response: { 200: isBufferResponseProps, 401: isErrorResponseProps, + 403: isErrorResponseProps, 404: isErrorResponseProps, 429: isErrorResponseProps, 500: isErrorResponseProps, }, tags: ['Federation', 'Media'], }, + canAccessMedia(federationAuth), async (c) => { try { const { mediaId } = c.req.param(); @@ -122,7 +125,6 @@ export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => body: multipartResponse.body, }; } catch (error) { - logger.error('Federation media download error:', error); return { statusCode: 500, body: { errcode: 'M_UNKNOWN', error: 'Internal server error' }, @@ -138,10 +140,12 @@ export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => response: { 200: isBufferResponseProps, 401: isErrorResponseProps, + 403: isErrorResponseProps, 404: isErrorResponseProps, }, tags: ['Federation', 'Media'], }, + canAccessMedia(federationAuth), async (context: any) => { try { const { mediaId } = context.req.param(); diff --git a/ee/packages/federation-matrix/src/api/middlewares.ts b/ee/packages/federation-matrix/src/api/middlewares.ts new file mode 100644 index 0000000000000..8e1b0701c86f8 --- /dev/null +++ b/ee/packages/federation-matrix/src/api/middlewares.ts @@ -0,0 +1,64 @@ +import type { EventAuthorizationService } from '@hs/federation-sdk'; +import type { Context, Next } from 'hono'; +import type { ContentfulStatusCode } from 'hono/utils/http-status'; + +const errCodes: Record = { + M_UNAUTHORIZED: { + errcode: 'M_UNAUTHORIZED', + error: 'Invalid or missing signature', + status: 401, + }, + M_FORBIDDEN: { + errcode: 'M_FORBIDDEN', + error: 'Access denied', + status: 403, + }, + M_UNKNOWN: { + errcode: 'M_UNKNOWN', + error: 'Internal server error while processing request', + status: 500, + }, +}; + +export const canAccessMedia = (federationAuth: EventAuthorizationService) => { + return async (c: Context, next: Next) => { + const mediaId = c.req.param('mediaId'); + const authHeader = c.req.header('Authorization') || ''; + const { method } = c.req; + const { path } = c.req; + const query = c.req.query(); + + let uriForSignature = path; + const queryString = new URLSearchParams(query).toString(); + if (queryString) { + uriForSignature = `${path}?${queryString}`; + } + + try { + const body = method === 'GET' ? undefined : await c.req.json(); + + const verificationResult = await federationAuth.canAccessMediaFromAuthorizationHeader( + mediaId, + authHeader, + method, + uriForSignature, // use URI with query params for signature verification + body, + ); + + if (!verificationResult.authorized) { + const errorResponse = errCodes[verificationResult.errorCode]; + return c.json( + { + errcode: errorResponse.errcode, + error: errorResponse.error, + }, + errorResponse.status, + ); + } + + return next(); + } catch (error) { + return c.json(errCodes.M_UNKNOWN, 500); + } + }; +}; diff --git a/ee/packages/federation-matrix/src/services/MatrixMediaService.ts b/ee/packages/federation-matrix/src/services/MatrixMediaService.ts index c218885dbaaf7..e25a4a97377f3 100644 --- a/ee/packages/federation-matrix/src/services/MatrixMediaService.ts +++ b/ee/packages/federation-matrix/src/services/MatrixMediaService.ts @@ -112,6 +112,9 @@ export class MatrixMediaService { } const buffer = await this.homeserverServices.media.downloadFromRemoteServer(parts.serverName, parts.mediaId); + if (!buffer) { + throw new Error('Download from remote server returned null content.'); + } const uploadedFile = await Upload.uploadFile({ userId: metadata.userId || 'federation',