|
1 | 1 | import { Body, Controller, Param, Put } from '@nestjs/common'; |
2 | 2 |
|
3 | | -import type { EventBase } from "@hs/core/src/events/eventBase"; |
4 | 3 | import { isRoomMemberEvent } from "@hs/core/src/events/m.room.member"; |
5 | | -import type { HashedEvent } from "../../authentication"; |
6 | 4 | import { ConfigService } from '../../services/config.service'; |
7 | 5 | import { EventService } from '../../services/event.service'; |
8 | | -import type { SignedEvent } from "../../signJson"; |
| 6 | +import { ZodValidationPipe } from '../../validation/pipes/zod-validation.pipe'; |
| 7 | +import { z } from 'zod'; |
| 8 | +import { ROOM_ID_REGEX, USERNAME_REGEX } from '../../utils/validation-regex'; |
| 9 | +import type { EventBase } from '@hs/core/src/events/eventBase'; |
9 | 10 |
|
10 | | -@Controller('/_matrix/federation/v1') |
| 11 | +const SendJoinEventSchema = z.object({ |
| 12 | + type: z.literal('m.room.member'), |
| 13 | + content: z.object({ |
| 14 | + membership: z.literal('join'), |
| 15 | + displayname: z.string().nullable().optional(), |
| 16 | + avatar_url: z.string().nullable().optional(), |
| 17 | + join_authorised_via_users_server: z.string().nullable().optional(), |
| 18 | + is_direct: z.boolean().nullable().optional(), |
| 19 | + }).and(z.record(z.any())), |
| 20 | + sender: z.string().regex(USERNAME_REGEX), |
| 21 | + state_key: z.string().regex(USERNAME_REGEX), |
| 22 | + room_id: z.string().regex(ROOM_ID_REGEX), |
| 23 | + origin_server_ts: z.number().int().positive(), |
| 24 | + depth: z.number().int().nonnegative(), |
| 25 | + prev_events: z.array(z.string().or(z.tuple([z.string(), z.string()]))), |
| 26 | + auth_events: z.array(z.string().or(z.tuple([z.string(), z.string()]))), |
| 27 | + origin: z.string().nullable().optional(), |
| 28 | + hashes: z.record(z.string()).nullable().optional(), |
| 29 | + signatures: z.record(z.record(z.string())).nullable().optional(), |
| 30 | + unsigned: z.record(z.any()).nullable().optional(), |
| 31 | +}); |
| 32 | + |
| 33 | +type SendJoinEventDto = Omit<EventBase, 'type' | 'content'> & { |
| 34 | + type: 'm.room.member'; |
| 35 | + content: { |
| 36 | + membership: 'join'; |
| 37 | + displayname?: string; |
| 38 | + avatar_url?: string; |
| 39 | + join_authorised_via_users_server?: string; |
| 40 | + is_direct?: boolean; |
| 41 | + }; |
| 42 | +}; |
| 43 | + |
| 44 | +type SendJoinResponseDto = { |
| 45 | + event: Record<string, any>; |
| 46 | + state: Record<string, any>[]; |
| 47 | + auth_chain: Record<string, any>[]; |
| 48 | + members_omitted: boolean; |
| 49 | + origin: string; |
| 50 | +}; |
| 51 | + |
| 52 | + |
| 53 | +@Controller('/_matrix/federation/v2') |
11 | 54 | export class SendJoinController { |
12 | 55 | constructor( |
13 | 56 | private readonly eventService: EventService, |
14 | 57 | private readonly configService: ConfigService, |
15 | | - ) {} |
| 58 | + ) { } |
16 | 59 |
|
17 | 60 | @Put("/send_join/:roomId/:stateKey") |
18 | | - async sendJoin(@Param('roomId') roomId: string, @Param('stateKey') stateKey: string, @Body() body: unknown) { |
19 | | - const event = body as SignedEvent<HashedEvent<EventBase>>; |
| 61 | + async sendJoin( |
| 62 | + @Param('roomId') roomId: string, |
| 63 | + @Param('stateKey') stateKey: string, |
| 64 | + @Body(new ZodValidationPipe(SendJoinEventSchema)) body: SendJoinEventDto |
| 65 | + ): Promise<SendJoinResponseDto> { |
| 66 | + const event = body; |
20 | 67 |
|
21 | 68 | const records = await this.eventService.findEvents({ "event.room_id": roomId }, { sort: { "event.depth": 1 } }); |
22 | 69 |
|
23 | 70 | const events = records.map((event) => event.event); |
24 | 71 |
|
25 | 72 | const lastInviteEvent = records.find( |
26 | 73 | (record) => |
27 | | - isRoomMemberEvent(record.event as unknown as EventBase) && |
| 74 | + isRoomMemberEvent(record.event) && |
28 | 75 | record.event.content.membership === "invite", |
29 | 76 | ); |
30 | 77 |
|
| 78 | + const eventToSave = { |
| 79 | + ...event, |
| 80 | + origin: event.origin || this.configService.getServerConfig().name |
| 81 | + }; |
| 82 | + |
31 | 83 | const result = { |
32 | 84 | event: { |
33 | 85 | ...event, |
34 | | - unsigned: lastInviteEvent && { |
| 86 | + unsigned: lastInviteEvent ? { |
35 | 87 | replaces_state: lastInviteEvent._id, |
36 | 88 | prev_content: lastInviteEvent.event.content, |
37 | 89 | prev_sender: lastInviteEvent.event.sender, |
38 | | - }, |
39 | | - } as SignedEvent<HashedEvent<EventBase>>, |
40 | | - state: events, |
41 | | - auth_chain: events.filter((event) => event.depth && event.depth <= 4), |
| 90 | + } : undefined, |
| 91 | + }, |
| 92 | + state: events.map(event => ({ ...event })), |
| 93 | + auth_chain: events |
| 94 | + .filter((event) => event.depth && event.depth <= 4) |
| 95 | + .map(event => ({ ...event })), |
42 | 96 | members_omitted: false, |
43 | 97 | origin: this.configService.getServerConfig().name, |
44 | | - } as const; |
| 98 | + }; |
45 | 99 |
|
46 | | - if (!(await this.eventService.findEvents({ _id: stateKey }))) { |
47 | | - await this.eventService.insertEvent(event as any); |
| 100 | + if ((await this.eventService.findEvents({ _id: stateKey })).length === 0) { |
| 101 | + await this.eventService.insertEvent(eventToSave, stateKey); |
48 | 102 | } |
49 | 103 |
|
50 | 104 | return result; |
|
0 commit comments