Skip to content

Commit cecfffb

Browse files
committed
add allowedEncryptedRooms and allowedNonPrivateRooms support
1 parent 7ebe99b commit cecfffb

File tree

10 files changed

+121
-17
lines changed

10 files changed

+121
-17
lines changed

packages/federation-sdk/src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export {
9090
type BaseEventType,
9191
} from './utils/event-schemas';
9292
export { errCodes } from './utils/response-codes';
93+
export { NotAllowedError } from './services/invite.service';
9394

9495
export { EventRepository } from './repositories/event.repository';
9596
export { RoomRepository } from './repositories/room.repository';
@@ -166,9 +167,6 @@ export type HomeserverEventSignatures = {
166167
'homeserver.matrix.encryption': {
167168
event_id: EventID;
168169
event: PduForType<'m.room.encryption'>;
169-
room_id: string;
170-
sender: string;
171-
origin_server_ts: number;
172170
};
173171
'homeserver.matrix.encrypted': {
174172
event_id: EventID;

packages/federation-sdk/src/services/config.service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export interface AppConfig {
3131
downloadPerMinute: number;
3232
};
3333
};
34+
invite: {
35+
allowedEncryptedRooms: boolean;
36+
allowedNonPrivateRooms: boolean;
37+
};
3438
}
3539

3640
export const AppConfigSchema = z.object({
@@ -69,6 +73,10 @@ export const AppConfigSchema = z.object({
6973
.min(1, 'Download rate limit must be at least 1'),
7074
}),
7175
}),
76+
invite: z.object({
77+
allowedEncryptedRooms: z.boolean(),
78+
allowedNonPrivateRooms: z.boolean(),
79+
}),
7280
});
7381

7482
export class ConfigService {
@@ -113,6 +121,10 @@ export class ConfigService {
113121
return this.config.media;
114122
}
115123

124+
getInviteConfig(): AppConfig['invite'] {
125+
return this.config.invite;
126+
}
127+
116128
async getSigningKey() {
117129
// If config contains a signing key, use it
118130
if (!this.config.signingKey) {

packages/federation-sdk/src/services/invite.service.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export type ProcessInviteEvent = {
2525
room_version: string;
2626
};
2727

28+
export class NotAllowedError extends Error {
29+
constructor(message: string) {
30+
super(message);
31+
this.name = 'NotAllowedError';
32+
}
33+
}
34+
2835
@singleton()
2936
export class InviteService {
3037
private readonly logger = createLogger('InviteService');
@@ -142,6 +149,42 @@ export class InviteService {
142149
};
143150
}
144151

152+
private async shouldProcessInvite(
153+
event: PduForType<'m.room.member'>,
154+
): Promise<void> {
155+
const isRoomNonPrivate = event.unsigned.invite_room_state.some(
156+
(
157+
stateEvent: PersistentEventBase<
158+
RoomVersion,
159+
'm.room.join_rules'
160+
>['event'],
161+
) =>
162+
stateEvent.type === 'm.room.join_rules' &&
163+
stateEvent.content.join_rule === 'public',
164+
);
165+
166+
const isRoomEncrypted = event.unsigned.invite_room_state.some(
167+
(
168+
stateEvent: PersistentEventBase<
169+
RoomVersion,
170+
'm.room.encryption'
171+
>['event'],
172+
) => stateEvent.type === 'm.room.encryption',
173+
);
174+
175+
const { allowedEncryptedRooms, allowedNonPrivateRooms } =
176+
this.configService.getInviteConfig();
177+
178+
const shouldRejectInvite =
179+
(!allowedEncryptedRooms && isRoomEncrypted) ||
180+
(!allowedNonPrivateRooms && isRoomNonPrivate);
181+
if (shouldRejectInvite) {
182+
throw new NotAllowedError(
183+
`Could not process invite due to room being ${isRoomEncrypted ? 'encrypted' : 'public'}`,
184+
);
185+
}
186+
}
187+
145188
async processInvite(
146189
event: PduForType<'m.room.member'>,
147190
roomId: RoomID,
@@ -150,6 +193,7 @@ export class InviteService {
150193
authenticatedServer: string,
151194
) {
152195
// SPEC: when a user invites another user on a different homeserver, a request to that homeserver to have the event signed and verified must be made
196+
await this.shouldProcessInvite(event);
153197

154198
const residentServer = roomId.split(':').pop();
155199
if (!residentServer) {

packages/federation-sdk/src/services/room.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ export class RoomService {
845845

846846
// trying to join room from another server
847847
const makeJoinResponse = await federationService.makeJoin(
848-
residentServer as string,
848+
residentServer,
849849
roomId,
850850
userId,
851851
roomVersion, // NOTE: check the comment in the called method

packages/federation-sdk/src/services/staging-area.service.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,6 @@ export class StagingAreaService {
312312
this.eventEmitterService.emit('homeserver.matrix.encryption', {
313313
event_id: eventId,
314314
event: event.event,
315-
room_id: roomId,
316-
sender: event.event.sender,
317-
origin_server_ts: event.event.origin_server_ts,
318315
});
319316
break;
320317
case event.event.type === 'm.room.encrypted':

packages/homeserver/src/controllers/federation/invite.controller.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,56 @@ import { EventID, RoomID } from '@rocket.chat/federation-room';
22
import {
33
EventAuthorizationService,
44
InviteService,
5+
NotAllowedError,
56
} from '@rocket.chat/federation-sdk';
67
import { isAuthenticatedMiddleware } from '@rocket.chat/homeserver/middlewares/isAuthenticated';
78
import { Elysia, t } from 'elysia';
89
import { container } from 'tsyringe';
9-
import { ProcessInviteParamsDto, RoomVersionDto } from '../../dtos';
10+
import {
11+
FederationErrorResponseDto,
12+
ProcessInviteParamsDto,
13+
ProcessInviteResponseDto,
14+
RoomVersionDto,
15+
} from '../../dtos';
1016

1117
export const invitePlugin = (app: Elysia) => {
1218
const inviteService = container.resolve(InviteService);
1319
const eventAuthService = container.resolve(EventAuthorizationService);
1420

1521
return app.use(isAuthenticatedMiddleware(eventAuthService)).put(
1622
'/_matrix/federation/v2/invite/:roomId/:eventId',
17-
async ({ body, params: { roomId, eventId }, authenticatedServer }) => {
23+
async ({ body, set, params: { roomId, eventId }, authenticatedServer }) => {
1824
if (!authenticatedServer) {
1925
throw new Error('Missing authenticated server from request');
2026
}
2127

22-
return inviteService.processInvite(
23-
body.event,
24-
roomId as RoomID,
25-
eventId as EventID,
26-
body.room_version,
27-
authenticatedServer,
28-
);
28+
try {
29+
return await inviteService.processInvite(
30+
body.event,
31+
roomId as RoomID,
32+
eventId as EventID,
33+
body.room_version,
34+
authenticatedServer,
35+
);
36+
} catch (error) {
37+
if (error instanceof NotAllowedError) {
38+
set.status = 403;
39+
return {
40+
errcode: 'M_FORBIDDEN',
41+
error:
42+
'This server does not allow joining this type of room based on federation settings.',
43+
};
44+
}
45+
46+
set.status = 500;
47+
return {
48+
errcode: 'M_UNKNOWN',
49+
error:
50+
error instanceof Error
51+
? error.message
52+
: 'Internal server error while processing request',
53+
};
54+
}
2955
},
3056
{
3157
params: ProcessInviteParamsDto,
@@ -34,6 +60,11 @@ export const invitePlugin = (app: Elysia) => {
3460
room_version: RoomVersionDto,
3561
invite_room_state: t.Any(),
3662
}),
63+
response: {
64+
200: ProcessInviteResponseDto,
65+
403: FederationErrorResponseDto,
66+
500: FederationErrorResponseDto,
67+
},
3768
detail: {
3869
tags: ['Federation'],
3970
summary: 'Process room invite',
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { type Static, t } from 'elysia';
2+
3+
export const FederationErrorResponseDto = t.Object({
4+
errcode: t.Enum({
5+
M_UNRECOGNIZED: 'M_UNRECOGNIZED',
6+
M_UNAUTHORIZED: 'M_UNAUTHORIZED',
7+
M_FORBIDDEN: 'M_FORBIDDEN',
8+
M_UNKNOWN: 'M_UNKNOWN',
9+
}),
10+
error: t.String(),
11+
});
12+
13+
export type FederationErrorResponseDto = Static<
14+
typeof FederationErrorResponseDto
15+
>;

packages/homeserver/src/dtos/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './federation/state-ids.dto';
1212
export * from './federation/state.dto';
1313
export * from './federation/transactions.dto';
1414
export * from './federation/versions.dto';
15+
export * from './federation/error.dto';
1516

1617
// Internal DTOs
1718
export * from './internal/invite.dto';

packages/homeserver/src/homeserver.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ export async function setup(options?: HomeserverSetupOptions) {
8888
),
8989
},
9090
},
91+
invite: {
92+
allowedEncryptedRooms:
93+
process.env.INVITE_ALLOWED_ENCRYPTED_ROOMS === 'true',
94+
allowedNonPrivateRooms:
95+
process.env.INVITE_ALLOWED_NON_PRIVATE_ROOMS === 'true',
96+
},
9197
});
9298

9399
const containerOptions: FederationContainerOptions = {

packages/room/src/types/v3-11.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ const EventPduTypeRoomEncrypted = z.object({
714714
});
715715

716716
const EventPduTypeRoomEncryption = z.object({
717-
...PduNoContentTimelineEventSchema,
717+
...PduNoContentEmptyStateKeyStateEventSchema,
718718
type: z.literal('m.room.encryption'),
719719
content: PduEncryptionEventContentSchema,
720720
});

0 commit comments

Comments
 (0)