Skip to content

Commit 6f04192

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

File tree

9 files changed

+120
-11
lines changed

9 files changed

+120
-11
lines changed

packages/federation-sdk/src/index.ts

Lines changed: 1 addition & 0 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';

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/homeserver/src/controllers/federation/invite.controller.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,55 @@ 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: 'This server does not allow joining this type of room based on federation settings.',
42+
};
43+
}
44+
45+
set.status = 500;
46+
return {
47+
errcode: 'M_UNKNOWN',
48+
error:
49+
error instanceof Error
50+
? error.message
51+
: 'Internal server error while processing request',
52+
};
53+
}
2954
},
3055
{
3156
params: ProcessInviteParamsDto,
@@ -34,6 +59,11 @@ export const invitePlugin = (app: Elysia) => {
3459
room_version: RoomVersionDto,
3560
invite_room_state: t.Any(),
3661
}),
62+
response: {
63+
200: ProcessInviteResponseDto,
64+
403: FederationErrorResponseDto,
65+
500: FederationErrorResponseDto,
66+
},
3767
detail: {
3868
tags: ['Federation'],
3969
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)