Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/heavy-lions-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@rocket.chat/federation-matrix': patch
'@rocket.chat/meteor': patch
---

Fixes federation endpoints rejecting requests that are valid per the Matrix specification:

- `publicRooms` (GET and POST) required params/fields the spec marks optional
- `query/profile` rejected spec-valid profile fields such as `m.tz`
- `get_missing_events` required the optional `limit` field and bounded it
- `make_join` returned 500 instead of 400 `M_INCOMPATIBLE_ROOM_VERSION` for unsupported room versions
- `backfill` rejected spec-valid `limit` values
- `send` rejected an entire transaction when a single PDU didn't match a fixed event shape, instead of reporting failures per PDU

Also links every federation endpoint to its definition in the Matrix specification.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const isWellKnownServerResponseProps = ajv.compile(WellKnownServerResponseSchema
// TODO: After changing the domain setting this route is still reporting the old domain until the server is restarted
// TODO: this is wrong, is siteurl !== domain this path should return 404. this path is to discover the final address, domain being the "proxy" and siteurl the final destination, if domain is different, well-known should be served there, not here.
export const getWellKnownRoutes = () => {
// https://spec.matrix.org/v1.19/server-server-api/#getwell-knownmatrixserver
return new Router('/matrix').get(
'/server',
{
Expand Down
20 changes: 18 additions & 2 deletions ee/packages/federation-matrix/src/api/_matrix/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,25 @@ const ProcessInviteResponseSchema = {
const isProcessInviteResponseProps = ajv.compile(ProcessInviteResponseSchema);

export const getMatrixInviteRoutes = () => {
// https://spec.matrix.org/v1.19/server-server-api/#put_matrixfederationv2inviteroomideventid
return new Router('/federation').put(
'/v2/invite/:roomId/:eventId',
{
body: ajv.compile({ type: 'object' }), // TODO: add schema from room package.
// TODO: add schema from room package. `event` is a PDU whose format varies by room
// version, so it stays unconstrained here; room_version and event are required per spec.
body: ajv.compile({
type: 'object',
properties: {
room_version: { type: 'string' },
event: { type: 'object' },
invite_room_state: {
type: 'array',
items: { type: 'object' },
nullable: true,
},
},
required: ['room_version', 'event'],
}),
params: isProcessInviteParamsProps,
response: {
200: isProcessInviteResponseProps,
Expand All @@ -152,10 +167,11 @@ export const getMatrixInviteRoutes = () => {
throw new Error('join event has missing state key, unable to determine user to join');
}

// spec: servers SHOULD return M_INVALID_PARAM if m.room.create is missing from invite_room_state
if (!strippedStateEvents?.some((e: any) => e.type === 'm.room.create')) {
return {
body: {
errcode: 'M_MISSING_PARAM',
errcode: 'M_INVALID_PARAM',
error: 'Missing invite_room_state: m.room.create event is required',
},
statusCode: 400,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const ServerKeyResponseSchema = {
const isServerKeyResponseProps = ajv.compile(ServerKeyResponseSchema);

export const getKeyServerRoutes = () => {
// https://spec.matrix.org/v1.19/server-server-api/#get_matrixkeyv2server
return new Router('/key').get(
'/v2/server',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const isMakeLeaveErrorResponseProps = ajv.compile({
});

export const getMatrixMakeLeaveRoutes = () => {
// https://spec.matrix.org/v1.19/server-server-api/#get_matrixfederationv1make_leaveroomiduserid
return new Router('/federation').get(
'/v1/make_leave/:roomId/:userId',
{
Expand Down
136 changes: 70 additions & 66 deletions ee/packages/federation-matrix/src/api/_matrix/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,76 +74,80 @@ async function getMediaFile(mediaId: string, serverName: string): Promise<{ file
}

export const getMatrixMediaRoutes = () => {
return new Router('/federation')
.get(
'/v1/media/download/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
429: isErrorResponseProps,
500: isErrorResponseProps,
return (
new Router('/federation')
// https://spec.matrix.org/v1.19/server-server-api/#get_matrixfederationv1mediadownloadmediaid
.get(
'/v1/media/download/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
429: isErrorResponseProps,
500: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
tags: ['Federation', 'Media'],
},
canAccessResourceMiddleware('media'),
async (c) => {
try {
const { mediaId } = c.req.param();
const serverName = federationSDK.getConfig('serverName');

// TODO: Add file streaming support
const result = await getMediaFile(mediaId, serverName);
if (!result) {
canAccessResourceMiddleware('media'),
async (c) => {
try {
const { mediaId } = c.req.param();
const serverName = federationSDK.getConfig('serverName');

// TODO: Add file streaming support
const result = await getMediaFile(mediaId, serverName);
if (!result) {
return {
statusCode: 404,
body: { errcode: 'M_NOT_FOUND', error: 'Media not found' },
};
}

const { file, buffer } = result;

const mimeType = file.type || 'application/octet-stream';
const fileName = file.name || mediaId;

const multipartResponse = createMultipartResponse(buffer, mimeType, fileName);

return {
statusCode: 200,
headers: {
...SECURITY_HEADERS,
'content-type': multipartResponse.contentType,
'content-length': String(multipartResponse.body.length),
},
body: multipartResponse.body,
};
} catch (error) {
return {
statusCode: 404,
body: { errcode: 'M_NOT_FOUND', error: 'Media not found' },
statusCode: 500,
body: { errcode: 'M_UNKNOWN', error: 'Internal server error' },
};
}

const { file, buffer } = result;

const mimeType = file.type || 'application/octet-stream';
const fileName = file.name || mediaId;

const multipartResponse = createMultipartResponse(buffer, mimeType, fileName);

return {
statusCode: 200,
headers: {
...SECURITY_HEADERS,
'content-type': multipartResponse.contentType,
'content-length': String(multipartResponse.body.length),
},
body: multipartResponse.body,
};
} catch (error) {
return {
statusCode: 500,
body: { errcode: 'M_UNKNOWN', error: 'Internal server error' },
};
}
},
)
.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
404: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
canAccessResourceMiddleware('media'),
async (_c) => ({
statusCode: 404,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
)
// https://spec.matrix.org/v1.19/server-server-api/#get_matrixfederationv1mediathumbnailmediaid
.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
404: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
}),
);
canAccessResourceMiddleware('media'),
async (_c) => ({
statusCode: 404,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
},
}),
)
);
};
Loading
Loading