Skip to content

Commit 4b555a2

Browse files
author
Marcos Spessatto Defendi
authored
fix: Invite users working again (#21)
* fix: make invite fully functional * chore: fix tsc * chore: remove forced type
1 parent a3fb9a5 commit 4b555a2

File tree

14 files changed

+309
-153
lines changed

14 files changed

+309
-153
lines changed

packages/core/src/events/m.room.guest_access.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface RoomGuestAccessEvent extends EventBase {
1111
content: {
1212
guest_access: "can_join" | "forbidden";
1313
};
14-
unsigned?: object;
14+
unsigned?: Record<string, unknown>;
1515
}
1616

1717
export const roomGuestAccessEvent = ({

packages/core/src/events/m.room.member-invite.spec.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { generateKeyPairsFromString } from "../../../homeserver/src/keys";
55
import { signEvent } from "../../../homeserver/src/signEvent";
66
import { roomMemberEvent } from "./m.room.member";
77

8-
const finalEventId = "$GAcbc4lUMhfCAWFZxoVZ6Pmzhcea1zKoY92ji4LjMqk";
8+
const finalEventId = "$nzfaHPXjmyOQerkm4WOlFupYVq56ZDHqC42DlgPydaI";
99
const finalEvent = {
1010
auth_events: [
1111
"$0AQU5dG_mtjH6qavAxYrQsDC0a_-6T3DHs1yoxf5fz4",
1212
"$T20EETjD2OuaC1OVyg8iIbJGTNeGBsMiWoAagBOVRNE",
1313
"$Uxo9MgF-4HQNEZdkkQDzgh9wlZ1yJbDXTMXCh6aZBi4",
14-
"$tZRt2bwceX4sG913Ee67tJiwe-gk859kY2mCeYSncw8",
14+
"$HistVisAuthEvent123456789012345678901234567890",
1515
],
1616
prev_events: ["$gdAY3-3DdjuG-uyFkDn8q5wPS4fbymH__fch9BQmOas"],
1717
type: "m.room.member",
@@ -27,11 +27,11 @@ const finalEvent = {
2727
state_key: "@asd6:rc1",
2828
origin: "hs1",
2929
origin_server_ts: 1733107418773,
30-
hashes: { sha256: "669gCNgB3VnQWmH+vIg/9CwyC5wOQmGuA8+PiIhiT50" },
30+
hashes: { sha256: "1tVvOpcM7iQz9kacDoUa8vhNMQVZZhA+5txzwsH8NR8" },
3131
signatures: {
3232
hs1: {
3333
"ed25519:a_HDhg":
34-
"ZGtHq5OuryBhQZOhZRAxGSej9BKU+5nDhzhQ9GfuUoAvP3InUch+Jznca3sblfy0LeZcdGJ866QpbH1eAGYsBQ",
34+
"8/qPp2d0PTc4bVMNdbTl32OSFnNqXan9ACQr1QcDV3SgdsDnm+sZv2mXW8rdhIOLOohRG2cED0+1aNxV7VH2Cw",
3535
},
3636
},
3737
unsigned: {
@@ -73,10 +73,11 @@ test("roomMemberInviteEvent", async () => {
7373
ts: 1733107418773,
7474
depth: 7,
7575
auth_events: {
76-
create: "$0AQU5dG_mtjH6qavAxYrQsDC0a_-6T3DHs1yoxf5fz4",
77-
power_levels: "$T20EETjD2OuaC1OVyg8iIbJGTNeGBsMiWoAagBOVRNE",
78-
join_rules: "$Uxo9MgF-4HQNEZdkkQDzgh9wlZ1yJbDXTMXCh6aZBi4",
79-
history_visibility: "$tZRt2bwceX4sG913Ee67tJiwe-gk859kY2mCeYSncw8",
76+
"m.room.create": "$0AQU5dG_mtjH6qavAxYrQsDC0a_-6T3DHs1yoxf5fz4",
77+
"m.room.power_levels": "$T20EETjD2OuaC1OVyg8iIbJGTNeGBsMiWoAagBOVRNE",
78+
"m.room.join_rules": "$Uxo9MgF-4HQNEZdkkQDzgh9wlZ1yJbDXTMXCh6aZBi4",
79+
"m.room.member:@admin:hs1": "$tZRt2bwceX4sG913Ee67tJiwe-gk859kY2mCeYSncw8",
80+
"m.room.history_visibility": "$HistVisAuthEvent123456789012345678901234567890",
8081
},
8182
prev_events: ["$gdAY3-3DdjuG-uyFkDn8q5wPS4fbymH__fch9BQmOas"],
8283
content: {
@@ -112,11 +113,12 @@ test("roomMemberInviteEvent", async () => {
112113
},
113114
} as const);
114115
const signed = await signEvent(memberEvent, signature, "hs1");
116+
115117
// @ts-ignore
116118
expect(signed).toStrictEqual(finalEvent);
117119
expect(signed).toHaveProperty(
118120
"signatures.hs1.ed25519:a_HDhg",
119-
"ZGtHq5OuryBhQZOhZRAxGSej9BKU+5nDhzhQ9GfuUoAvP3InUch+Jznca3sblfy0LeZcdGJ866QpbH1eAGYsBQ",
121+
"8/qPp2d0PTc4bVMNdbTl32OSFnNqXan9ACQr1QcDV3SgdsDnm+sZv2mXW8rdhIOLOohRG2cED0+1aNxV7VH2Cw",
120122
);
121123

122124
const memberEventId = generateId(signed);

packages/core/src/events/m.room.member.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test("roomMemberEvent", async () => {
5252
ts: 1733107418672,
5353
depth: 2,
5454
auth_events: {
55-
create: createEventId,
55+
"m.room.create": createEventId,
5656
},
5757
prev_events: [createEventId],
5858
});

packages/core/src/events/m.room.member.ts

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ declare module "./eventBase" {
1818
}
1919
}
2020

21+
export type AuthEvents = {
22+
"m.room.create": string;
23+
"m.room.power_levels"?: string;
24+
"m.room.join_rules"?: string;
25+
"m.room.history_visibility"?: string;
26+
} & {
27+
[K in `m.room.member:${string}`]?: string;
28+
}
29+
2130
export const isRoomMemberEvent = (
2231
event: EventBase,
2332
): event is RoomMemberEvent => {
@@ -48,26 +57,29 @@ export interface RoomMemberEvent extends EventBase {
4857
age_ts: number;
4958
invite_room_state: (
5059
| {
51-
type: "m.room.join_rules";
52-
state_key: "";
53-
content: { join_rule: "invite" };
54-
sender: string;
55-
}
60+
type: "m.room.join_rules";
61+
state_key: "";
62+
content: { join_rule: "invite" };
63+
sender: string;
64+
}
5665
| {
57-
type: "m.room.create";
58-
state_key: "";
59-
content: { room_version: "10"; creator: string };
60-
sender: string;
61-
}
66+
type: "m.room.create";
67+
state_key: "";
68+
content: { room_version: "10"; creator: string };
69+
sender: string;
70+
}
6271
| {
63-
type: "m.room.member";
64-
state_key: string;
65-
content: { displayname: "admin"; membership: "join" };
66-
sender: string;
67-
}
72+
type: "m.room.member";
73+
state_key: string;
74+
content: { displayname: "admin"; membership: "join" };
75+
sender: string;
76+
}
6877
)[];
6978
};
7079
}
80+
const isTruthy = <T>(value: T | null | undefined | false | 0 | ''): value is T => {
81+
return Boolean(value);
82+
};
7183

7284
export const roomMemberEvent = ({
7385
membership,
@@ -86,12 +98,7 @@ export const roomMemberEvent = ({
8698
roomId: string;
8799
sender: string;
88100
state_key: string;
89-
auth_events: {
90-
create: string;
91-
power_levels?: string;
92-
join_rules?: string;
93-
history_visibility?: string;
94-
};
101+
auth_events: AuthEvents;
95102
prev_events: string[];
96103
depth: number;
97104
unsigned?: RoomMemberEvent["unsigned"];
@@ -103,11 +110,12 @@ export const roomMemberEvent = ({
103110
roomId,
104111
sender,
105112
auth_events: [
106-
auth_events.create,
107-
auth_events.power_levels,
108-
auth_events.join_rules,
109-
auth_events.history_visibility,
110-
].filter(Boolean) as string[],
113+
auth_events["m.room.create"],
114+
auth_events["m.room.power_levels"],
115+
auth_events["m.room.join_rules"],
116+
auth_events["m.room.history_visibility"],
117+
auth_events[`m.room.member:${state_key}`],
118+
].filter(isTruthy),
111119
prev_events,
112120
depth,
113121
content: {

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

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
1-
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
1+
import { Body, Controller, Get, Param, Post, Query, UsePipes } from '@nestjs/common';
22
import { ProfilesService } from '../../services/profiles.service';
3+
import { z } from 'zod';
4+
import { ZodValidationPipe } from '../../validation/pipes/zod-validation.pipe';
35

6+
const MakeJoinQueryParamsSchema = z.object({
7+
ver: z.array(z.string()).optional()
8+
});
9+
10+
type MakeJoinQueryParamsDto = z.infer<typeof MakeJoinQueryParamsSchema>;
11+
type MakeJoinResponseDto = {
12+
room_version: string;
13+
event: {
14+
content: {
15+
membership: 'join';
16+
join_authorised_via_users_server?: string;
17+
[key: string]: any;
18+
};
19+
room_id: string;
20+
sender: string;
21+
state_key: string;
22+
type: 'm.room.member';
23+
origin_server_ts: number;
24+
origin: string;
25+
[key: string]: any;
26+
};
27+
};
428
@Controller('/_matrix/federation/v1')
529
export class ProfilesController {
6-
constructor(private readonly profilesService: ProfilesService) {}
30+
constructor(private readonly profilesService: ProfilesService) { }
731

832
@Get("/query/profile")
933
async queryProfile(@Query() queryParams: { user_id: string }) {
@@ -24,9 +48,28 @@ export class ProfilesController {
2448
async makeJoin(
2549
@Param("roomId") roomId: string,
2650
@Param("userId") userId: string,
27-
@Query() query: any
28-
) {
29-
return this.profilesService.makeJoin(roomId, userId, query.ver);
51+
@Query(new ZodValidationPipe(MakeJoinQueryParamsSchema)) query: MakeJoinQueryParamsDto,
52+
): Promise<MakeJoinResponseDto> {
53+
const response = await this.profilesService.makeJoin(roomId, userId, query.ver);
54+
55+
return {
56+
room_version: response.room_version,
57+
event: {
58+
...response.event,
59+
content: {
60+
...response.event.content,
61+
membership: 'join',
62+
join_authorised_via_users_server: response.event.content.join_authorised_via_users_server,
63+
64+
},
65+
room_id: response.event.room_id,
66+
sender: response.event.sender,
67+
state_key: response.event.state_key,
68+
type: 'm.room.member',
69+
origin_server_ts: response.event.origin_server_ts,
70+
origin: response.event.origin,
71+
}
72+
}
3073
}
3174

3275
@Post("/get_missing_events/:roomId")

packages/homeserver/src/controllers/federation/send-join.controller.ts

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,104 @@
11
import { Body, Controller, Param, Put } from '@nestjs/common';
22

3-
import type { EventBase } from "@hs/core/src/events/eventBase";
43
import { isRoomMemberEvent } from "@hs/core/src/events/m.room.member";
5-
import type { HashedEvent } from "../../authentication";
64
import { ConfigService } from '../../services/config.service';
75
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';
910

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')
1154
export class SendJoinController {
1255
constructor(
1356
private readonly eventService: EventService,
1457
private readonly configService: ConfigService,
15-
) {}
58+
) { }
1659

1760
@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;
2067

2168
const records = await this.eventService.findEvents({ "event.room_id": roomId }, { sort: { "event.depth": 1 } });
2269

2370
const events = records.map((event) => event.event);
2471

2572
const lastInviteEvent = records.find(
2673
(record) =>
27-
isRoomMemberEvent(record.event as unknown as EventBase) &&
74+
isRoomMemberEvent(record.event) &&
2875
record.event.content.membership === "invite",
2976
);
3077

78+
const eventToSave = {
79+
...event,
80+
origin: event.origin || this.configService.getServerConfig().name
81+
};
82+
3183
const result = {
3284
event: {
3385
...event,
34-
unsigned: lastInviteEvent && {
86+
unsigned: lastInviteEvent ? {
3587
replaces_state: lastInviteEvent._id,
3688
prev_content: lastInviteEvent.event.content,
3789
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 })),
4296
members_omitted: false,
4397
origin: this.configService.getServerConfig().name,
44-
} as const;
98+
};
4599

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);
48102
}
49103

50104
return result;

packages/homeserver/src/models/event.model.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ export interface EventBase {
33
room_id: string;
44
type: string;
55
sender: string;
6-
content: any;
6+
content?: Record<string, unknown>;
77
origin_server_ts: number;
88
origin: string;
99
state_key?: string;
10-
depth?: number;
11-
prev_events?: string[];
12-
auth_events?: string[];
10+
depth: number;
11+
prev_events: string[];
12+
auth_events: string[];
1313
signatures?: Record<string, Record<string, string>>;
14+
unsigned?: Record<string, unknown> | undefined;
1415
}
1516

1617
export interface EventStore {
1718
_id: string;
1819
event: EventBase;
1920
staged?: boolean;
21+
outlier?: boolean;
2022
}
2123

2224
export interface StateEvent extends EventBase {

packages/homeserver/src/procedures/createRoom.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const createRoom = async (
8181
},
8282
state_key: sender,
8383
auth_events: {
84-
create: createEvent._id,
84+
"m.room.create": createEvent._id,
8585
},
8686
prev_events: [createEvent._id],
8787
});

0 commit comments

Comments
 (0)