Skip to content
Merged
2 changes: 1 addition & 1 deletion ee/packages/federation-matrix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@rocket.chat/core-services": "workspace:^",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/emitter": "^0.31.25",
"@rocket.chat/federation-sdk": "0.1.23",
"@rocket.chat/federation-sdk": "0.1.25",
"@rocket.chat/http-router": "workspace:^",
"@rocket.chat/license": "workspace:^",
"@rocket.chat/models": "workspace:^",
Expand Down
14 changes: 11 additions & 3 deletions ee/packages/federation-matrix/src/api/_matrix/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Rooms, Users } from '@rocket.chat/models';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { createOrUpdateFederatedUser, getUsernameServername } from '../../FederationMatrix';
import { isAuthenticatedMiddleware } from '../middlewares/isAuthenticated';

const EventBaseSchema = {
type: 'object',
Expand Down Expand Up @@ -181,7 +182,7 @@ async function joinRoom({
const isDM = inviteEvent.getContent<PduMembershipEventContent>().is_direct;

if (!isDM && !matrixRoom.isPublic() && !matrixRoom.isInviteOnly()) {
throw new Error('room is neither public, private, nor direct message - rocketchat is unable to join for now');
throw new Error('room is neither direct message - rocketchat is unable to join for now');
}

// need both the sender and the participating user to exist in the room
Expand Down Expand Up @@ -320,7 +321,7 @@ export const acceptInvite = async (
};

export const getMatrixInviteRoutes = (services: HomeserverServices) => {
const { invite, state, room } = services;
const { invite, state, room, federationAuth } = services;

return new Router('/federation').put(
'/v2/invite/:roomId/:eventId',
Expand All @@ -333,6 +334,7 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => {
tags: ['Federation'],
license: ['federation'],
},
isAuthenticatedMiddleware(federationAuth),
async (c) => {
const { roomId, eventId } = c.req.param();
const { event, room_version: roomVersion } = await c.req.json();
Expand All @@ -353,7 +355,13 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => {
throw new Error('user not found not processing invite');
}

const inviteEvent = await invite.processInvite(event, roomIdSchema.parse(roomId), eventIdSchema.parse(eventId), roomVersion);
const inviteEvent = await invite.processInvite(
event,
roomIdSchema.parse(roomId),
eventIdSchema.parse(eventId),
roomVersion,
c.get('authenticatedServer'),
);

setTimeout(
() => {
Expand Down
137 changes: 67 additions & 70 deletions ee/packages/federation-matrix/src/api/_matrix/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Router } from '@rocket.chat/http-router';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { MatrixMediaService } from '../../services/MatrixMediaService';
import { canAccessMedia } from '../middlewares';
import { canAccessResourceMiddleware } from '../middlewares/canAccessResource';

const MediaDownloadParamsSchema = {
type: 'object',
Expand Down Expand Up @@ -75,79 +75,76 @@ async function getMediaFile(mediaId: string, serverName: string): Promise<{ file

export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => {
const { config, federationAuth } = homeserverServices;
const router = new Router('/federation');

router.get(
'/v1/media/download/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
200: isBufferResponseProps,
401: isErrorResponseProps,
403: isErrorResponseProps,
404: isErrorResponseProps,
429: isErrorResponseProps,
500: isErrorResponseProps,
return new Router('/federation')
.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'],
},
canAccessMedia(federationAuth),
async (c) => {
try {
const { mediaId } = c.req.param();
const { serverName } = config;

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

// 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: 404,
body: { errcode: 'M_NOT_FOUND', error: 'Media not found' },
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' },
};
}

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' },
};
}
},
);

router.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
404: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
async () => ({
statusCode: 404,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
)
.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
404: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
}),
);

return router;
canAccessResourceMiddleware(federationAuth, 'media'),
async (_c) => ({
statusCode: 404,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
},
}),
);
Comment on lines +132 to +149
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 | 🟡 Minor

Status code mismatch: use 501 for unimplemented endpoints.

The endpoint returns a 404 status code with an error message stating "This endpoint is not implemented on the homeserver side." This is semantically incorrect—404 indicates "resource not found," while 501 indicates "not implemented."

Apply this diff to use the correct status code:

 		.get(
 			'/v1/media/thumbnail/:mediaId',
 			{
 				params: isMediaDownloadParamsProps,
 				response: {
-					404: isErrorResponseProps,
+					501: isErrorResponseProps,
 				},
 				tags: ['Federation', 'Media'],
 			},
 			canAccessResourceMiddleware(federationAuth, 'media'),
 			async (_c) => ({
-				statusCode: 404,
+				statusCode: 501,
 				body: {
 					errcode: 'M_UNRECOGNIZED',
 					error: 'This endpoint is not implemented on the homeserver side',
 				},
 			}),
 		);
📝 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
.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
404: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
}),
);
return router;
canAccessResourceMiddleware(federationAuth, 'media'),
async (_c) => ({
statusCode: 404,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
},
}),
);
.get(
'/v1/media/thumbnail/:mediaId',
{
params: isMediaDownloadParamsProps,
response: {
501: isErrorResponseProps,
},
tags: ['Federation', 'Media'],
},
canAccessResourceMiddleware(federationAuth, 'media'),
async (_c) => ({
statusCode: 501,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
},
}),
);
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/media.ts around lines 132 to
149, the thumbnail route returns a 404 while the handler message says the
endpoint is not implemented; change the response statusCode to 501 and ensure
the returned body remains the same so the HTTP status semantically matches "Not
Implemented" (501) instead of "Not Found" (404).

};
22 changes: 14 additions & 8 deletions ee/packages/federation-matrix/src/api/_matrix/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import { Router } from '@rocket.chat/http-router';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { canAccessResourceMiddleware } from '../middlewares/canAccessResource';
import { isAuthenticatedMiddleware } from '../middlewares/isAuthenticated';

const UsernameSchema = {
type: 'string',
pattern: '^@[A-Za-z0-9_=\\/.+-]+:(.+)$',
Expand Down Expand Up @@ -148,7 +151,7 @@
required: ['roomId', 'userId'],
};

// @ts-ignore

Check warning on line 154 in ee/packages/federation-matrix/src/api/_matrix/profiles.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isMakeJoinParamsProps = ajv.compile(MakeJoinParamsSchema);

Expand All @@ -174,7 +177,7 @@
},
};

// @ts-ignore

Check warning on line 180 in ee/packages/federation-matrix/src/api/_matrix/profiles.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isMakeJoinQueryProps = ajv.compile(MakeJoinQuerySchema);

Expand Down Expand Up @@ -260,7 +263,7 @@
required: ['room_version', 'event'],
};

// @ts-ignore

Check warning on line 266 in ee/packages/federation-matrix/src/api/_matrix/profiles.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isMakeJoinResponseProps = ajv.compile(MakeJoinResponseSchema);

Expand Down Expand Up @@ -350,9 +353,10 @@
const isEventAuthResponseProps = ajv.compile(EventAuthResponseSchema);

export const getMatrixProfilesRoutes = (services: HomeserverServices) => {
const { profile } = services;
const { profile, federationAuth } = services;

return new Router('/federation')
.use(isAuthenticatedMiddleware(federationAuth))
.get(
'/v1/query/profile',
{
Expand Down Expand Up @@ -414,14 +418,13 @@
tags: ['Federation'],
license: ['federation'],
},
async (c) => {
const { userId } = c.req.param();

const response = await profile.getDevices(userId);

async (_c) => {
return {
body: response,
statusCode: 200,
body: {
errcode: 'M_UNRECOGNIZED',
error: 'This endpoint is not implemented on the homeserver side',
},
statusCode: 501,
};
},
)
Expand All @@ -436,6 +439,7 @@
tags: ['Federation'],
license: ['federation'],
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const { roomId, userId } = c.req.param();
const url = new URL(c.req.url);
Expand Down Expand Up @@ -467,6 +471,7 @@
tags: ['Federation'],
license: ['federation'],
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const { roomId } = c.req.param();
const body = await c.req.json();
Expand All @@ -489,6 +494,7 @@
tags: ['Federation'],
license: ['federation'],
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const { roomId, eventId } = c.req.param();

Expand Down
5 changes: 4 additions & 1 deletion ee/packages/federation-matrix/src/api/_matrix/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { HomeserverServices } from '@rocket.chat/federation-sdk';
import { Router } from '@rocket.chat/http-router';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { isAuthenticatedMiddleware } from '../middlewares/isAuthenticated';

const PublicRoomsQuerySchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -122,9 +124,10 @@ const PublicRoomsPostBodySchema = {
const isPublicRoomsPostBodyProps = ajv.compile(PublicRoomsPostBodySchema);

export const getMatrixRoomsRoutes = (services: HomeserverServices) => {
const { state } = services;
const { state, federationAuth } = services;

return new Router('/federation')
.use(isAuthenticatedMiddleware(federationAuth))
.get(
'/v1/publicRooms',
{
Expand Down
5 changes: 4 additions & 1 deletion ee/packages/federation-matrix/src/api/_matrix/send-join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { Router } from '@rocket.chat/http-router';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { canAccessResourceMiddleware } from '../middlewares/canAccessResource';

const UsernameSchema = {
type: 'string',
pattern: '^@[A-Za-z0-9_=\\/.+-]+:(.+)$',
Expand Down Expand Up @@ -46,7 +48,7 @@
required: ['roomId', 'stateKey'],
};

// @ts-ignore

Check warning on line 51 in ee/packages/federation-matrix/src/api/_matrix/send-join.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isSendJoinParamsProps = ajv.compile(SendJoinParamsSchema);

Expand Down Expand Up @@ -183,7 +185,7 @@
],
};

// @ts-ignore

Check warning on line 188 in ee/packages/federation-matrix/src/api/_matrix/send-join.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isSendJoinEventProps = ajv.compile(SendJoinEventSchema);

Expand Down Expand Up @@ -217,12 +219,12 @@
required: ['event', 'state', 'auth_chain', 'members_omitted', 'origin'],
};

// @ts-ignore

Check warning on line 222 in ee/packages/federation-matrix/src/api/_matrix/send-join.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Do not use "@ts-ignore" because it alters compilation errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isSendJoinResponseProps = ajv.compile(SendJoinResponseSchema);

export const getMatrixSendJoinRoutes = (services: HomeserverServices) => {
const { sendJoin } = services;
const { sendJoin, federationAuth } = services;

return new Router('/federation').put(
'/v2/send_join/:roomId/:stateKey',
Expand All @@ -235,6 +237,7 @@
tags: ['Federation'],
license: ['federation'],
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const { roomId, stateKey } = c.req.param();
const body = await c.req.json();
Expand Down
10 changes: 7 additions & 3 deletions ee/packages/federation-matrix/src/api/_matrix/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { HomeserverServices, EventID } from '@rocket.chat/federation-sdk';
import { Router } from '@rocket.chat/http-router';
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';

import { canAccessEvent } from '../middlewares';
import { canAccessResourceMiddleware } from '../middlewares/canAccessResource';
import { isAuthenticatedMiddleware } from '../middlewares/isAuthenticated';

const SendTransactionParamsSchema = {
type: 'object',
Expand Down Expand Up @@ -319,6 +320,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
// PUT /_matrix/federation/v1/send/{txnId}
return (
new Router('/federation')
.use(isAuthenticatedMiddleware(federationAuth))
.put(
'/v1/send/:txnId',
{
Expand Down Expand Up @@ -365,7 +367,6 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
)

// GET /_matrix/federation/v1/state_ids/{roomId}

.get(
'/v1/state_ids/:roomId',
{
Expand All @@ -374,6 +375,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
200: isGetStateIdsResponseProps,
},
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const roomId = c.req.param('roomId');
const eventId = c.req.query('event_id');
Expand Down Expand Up @@ -404,6 +406,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
200: isGetStateResponseProps,
},
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const roomId = c.req.param('roomId');
const eventId = c.req.query('event_id');
Expand Down Expand Up @@ -435,7 +438,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
tags: ['Federation'],
license: ['federation'],
},
canAccessEvent(federationAuth),
canAccessResourceMiddleware(federationAuth, 'event'),
async (c) => {
const eventData = await event.getEventById(c.req.param('eventId') as EventID);
if (!eventData) {
Expand Down Expand Up @@ -470,6 +473,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => {
tags: ['Federation'],
license: ['federation'],
},
canAccessResourceMiddleware(federationAuth, 'room'),
async (c) => {
const roomId = c.req.param('roomId');
const limit = Number(c.req.query('limit') || 100);
Expand Down
Loading
Loading