matrix-nodejs-homeserver 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +75 -0
  2. package/README.md +479 -2
  3. package/dist/adapters/auth.d.ts +50 -0
  4. package/dist/adapters/auth.js +18 -0
  5. package/dist/adapters/cluster.d.ts +55 -0
  6. package/dist/adapters/cluster.js +249 -0
  7. package/dist/adapters/media.d.ts +50 -0
  8. package/dist/adapters/media.js +63 -0
  9. package/dist/adapters/s3.d.ts +33 -0
  10. package/dist/adapters/s3.js +95 -0
  11. package/dist/api/account.js +594 -0
  12. package/dist/api/admin.js +267 -0
  13. package/dist/api/appservice.js +45 -0
  14. package/dist/api/backup.js +280 -0
  15. package/dist/api/federation.js +826 -0
  16. package/dist/api/filters.js +88 -0
  17. package/dist/api/index.js +47 -0
  18. package/dist/api/legacy.js +183 -0
  19. package/dist/api/media.js +271 -0
  20. package/dist/api/misc.js +95 -0
  21. package/dist/api/preview.js +201 -0
  22. package/dist/api/profile.js +176 -0
  23. package/dist/api/push.js +202 -0
  24. package/dist/api/relationships.js +270 -0
  25. package/dist/api/rooms.js +1487 -0
  26. package/dist/api/rtc.d.ts +23 -0
  27. package/dist/api/rtc.js +66 -0
  28. package/dist/api/search.js +161 -0
  29. package/dist/api/slidingsync.js +474 -0
  30. package/dist/api/spaces.js +177 -0
  31. package/dist/api/sync.js +609 -0
  32. package/dist/api/threadsubs.js +140 -0
  33. package/dist/api/threepid.js +155 -0
  34. package/dist/api/uia.js +127 -0
  35. package/dist/api/user.js +329 -0
  36. package/dist/core/admin.d.ts +84 -0
  37. package/dist/core/admin.js +308 -0
  38. package/dist/core/adminroom.d.ts +49 -0
  39. package/dist/core/adminroom.js +483 -0
  40. package/dist/core/appservices.d.ts +74 -0
  41. package/dist/core/appservices.js +369 -0
  42. package/dist/core/audit.d.ts +50 -0
  43. package/dist/core/audit.js +86 -0
  44. package/dist/core/auth.d.ts +48 -0
  45. package/dist/core/auth.js +237 -0
  46. package/dist/core/authrules.d.ts +4 -0
  47. package/dist/core/authrules.js +348 -0
  48. package/dist/core/cluster.d.ts +66 -0
  49. package/dist/core/cluster.js +191 -0
  50. package/dist/core/crypto.d.ts +10 -0
  51. package/dist/core/crypto.js +100 -0
  52. package/dist/core/delayed.d.ts +39 -0
  53. package/dist/core/delayed.js +160 -0
  54. package/dist/core/errors.d.ts +9 -0
  55. package/dist/core/errors.js +45 -0
  56. package/dist/core/events.d.ts +32 -0
  57. package/dist/core/events.js +186 -0
  58. package/dist/core/glob.js +66 -0
  59. package/dist/core/identity.d.ts +24 -0
  60. package/dist/core/identity.js +96 -0
  61. package/dist/core/ids.js +36 -0
  62. package/dist/core/invitefilter.js +36 -0
  63. package/dist/core/json.js +16 -0
  64. package/dist/core/keys.d.ts +36 -0
  65. package/dist/core/keys.js +450 -0
  66. package/dist/core/m.d.ts +209 -0
  67. package/dist/core/m.js +182 -0
  68. package/dist/core/notifier.d.ts +17 -0
  69. package/dist/core/notifier.js +54 -0
  70. package/dist/core/oauth.d.ts +38 -0
  71. package/dist/core/oauth.js +108 -0
  72. package/dist/core/partialstate.d.ts +32 -0
  73. package/dist/core/partialstate.js +198 -0
  74. package/dist/core/phone.js +268 -0
  75. package/dist/core/policy.d.ts +29 -0
  76. package/dist/core/policy.js +97 -0
  77. package/dist/core/profile.js +39 -0
  78. package/dist/core/push.d.ts +46 -0
  79. package/dist/core/push.js +307 -0
  80. package/dist/core/pushrules.js +229 -0
  81. package/dist/core/ratelimit.d.ts +23 -0
  82. package/dist/core/ratelimit.js +77 -0
  83. package/dist/core/rooms.d.ts +215 -0
  84. package/dist/core/rooms.js +1731 -0
  85. package/dist/core/settings.d.ts +11 -0
  86. package/dist/core/settings.js +29 -0
  87. package/dist/core/sso.d.ts +46 -0
  88. package/dist/core/sso.js +165 -0
  89. package/dist/core/stateres.d.ts +4 -0
  90. package/dist/core/stateres.js +209 -0
  91. package/dist/core/threepid.d.ts +80 -0
  92. package/dist/core/threepid.js +275 -0
  93. package/dist/core/typing.d.ts +16 -0
  94. package/dist/core/typing.js +60 -0
  95. package/dist/federation/client.d.ts +51 -0
  96. package/dist/federation/client.js +279 -0
  97. package/dist/federation/index.d.ts +153 -0
  98. package/dist/federation/index.js +1059 -0
  99. package/dist/federation/keyring.d.ts +26 -0
  100. package/dist/federation/keyring.js +122 -0
  101. package/dist/federation/sender.d.ts +26 -0
  102. package/dist/federation/sender.js +164 -0
  103. package/dist/homeserver.d.ts +224 -0
  104. package/dist/homeserver.js +302 -0
  105. package/dist/http/adapters.d.ts +40 -0
  106. package/dist/http/adapters.js +130 -0
  107. package/dist/http/handler.d.ts +8 -0
  108. package/dist/http/handler.js +181 -0
  109. package/dist/http/input.js +24 -0
  110. package/dist/http/procedure.js +25 -0
  111. package/dist/http/router.d.ts +43 -0
  112. package/dist/http/router.js +56 -0
  113. package/dist/index.d.ts +26 -0
  114. package/dist/index.js +15 -0
  115. package/dist/storage/memory.d.ts +5 -0
  116. package/dist/storage/memory.js +122 -0
  117. package/dist/storage/schema.d.ts +4 -0
  118. package/dist/storage/schema.js +664 -0
  119. package/dist/storage/sql.d.ts +72 -0
  120. package/dist/storage/sql.js +274 -0
  121. package/dist/storage/types.d.ts +55 -0
  122. package/dist/vendor/zod/LICENSE +21 -0
  123. package/dist/vendor/zod/v4/classic/errors.js +54 -0
  124. package/dist/vendor/zod/v4/classic/parse.js +17 -0
  125. package/dist/vendor/zod/v4/classic/schemas.js +865 -0
  126. package/dist/vendor/zod/v4/core/api.js +512 -0
  127. package/dist/vendor/zod/v4/core/checks.js +300 -0
  128. package/dist/vendor/zod/v4/core/core.js +109 -0
  129. package/dist/vendor/zod/v4/core/doc.js +35 -0
  130. package/dist/vendor/zod/v4/core/errors.js +118 -0
  131. package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
  132. package/dist/vendor/zod/v4/core/memoizer.js +255 -0
  133. package/dist/vendor/zod/v4/core/parse.js +206 -0
  134. package/dist/vendor/zod/v4/core/regexes.js +73 -0
  135. package/dist/vendor/zod/v4/core/registries.js +48 -0
  136. package/dist/vendor/zod/v4/core/schemas.js +1378 -0
  137. package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
  138. package/dist/vendor/zod/v4/core/util.js +537 -0
  139. package/dist/vendor/zod/v4/core/versions.js +8 -0
  140. package/dist/vendor/zod/v4/locales/en.js +110 -0
  141. package/package.json +65 -4
@@ -0,0 +1,308 @@
1
+ import { randomString } from "./crypto.js";
2
+ import { M } from "./m.js";
3
+ import { errors } from "./errors.js";
4
+ import { LOCALPART_RE, isValidUserId } from "./ids.js";
5
+ import { createUser } from "../api/account.js";
6
+ //#region src/core/admin.ts
7
+ /** Programmatic administration, for embedding applications and the admin HTTP API. */
8
+ var AdminService = class {
9
+ hs;
10
+ constructor(hs) {
11
+ this.hs = hs;
12
+ }
13
+ async createUser(localpart, opts = {}) {
14
+ const lp = localpart.toLowerCase();
15
+ if (!LOCALPART_RE.test(lp)) throw errors.invalidUsername();
16
+ if (await this.getUser(`@${lp}:${this.hs.serverName}`)) throw errors.userInUse();
17
+ return createUser(this.hs, lp, opts);
18
+ }
19
+ async getUser(userId) {
20
+ const row = await this.hs.db.findOne("users", [{
21
+ field: "user_id",
22
+ value: userId
23
+ }]);
24
+ if (!row) return null;
25
+ const { password_hash, ...rest } = row;
26
+ return rest;
27
+ }
28
+ async listUsers(opts = {}) {
29
+ let rows = await this.hs.db.findMany("users", { sortBy: {
30
+ field: "created_ts",
31
+ direction: "asc"
32
+ } });
33
+ if (!opts.includeDeactivated) rows = rows.filter((r) => !r.deactivated);
34
+ if (opts.search) {
35
+ const term = opts.search.toLowerCase();
36
+ rows = rows.filter((r) => r.user_id.toLowerCase().includes(term) || (r.displayname ?? "").toLowerCase().includes(term));
37
+ }
38
+ const from = opts.from ?? 0;
39
+ const limit = opts.limit ?? 100;
40
+ return {
41
+ users: rows.slice(from, from + limit).map(({ password_hash, ...u }) => u),
42
+ total: rows.length,
43
+ next: from + limit < rows.length ? from + limit : null
44
+ };
45
+ }
46
+ async setAdmin(userId, admin) {
47
+ if (!await this.hs.db.update("users", [{
48
+ field: "user_id",
49
+ value: userId
50
+ }], { is_admin: admin })) throw errors.notFound("Unknown user");
51
+ await this.hs.auth.invalidateUser(userId);
52
+ if (admin) await this.hs.adminRoom?.addAdmin(userId);
53
+ else await this.hs.adminRoom?.removeAdmin(userId);
54
+ }
55
+ /** Lock or unlock an account (reversible; sessions are kept but unusable while locked). */
56
+ async setLocked(userId, locked, actor = "server") {
57
+ if (!await this.hs.db.update("users", [{
58
+ field: "user_id",
59
+ value: userId
60
+ }], { locked })) throw errors.notFound("Unknown user");
61
+ await this.hs.auth.invalidateUser(userId);
62
+ await this.hs.audit.record({
63
+ action: locked ? "user_lock" : "user_unlock",
64
+ actor,
65
+ target: userId
66
+ });
67
+ }
68
+ /** Suspend or unsuspend an account (reversible; the user can still read, leave and log out). */
69
+ async setSuspended(userId, suspended, actor = "server") {
70
+ if (!await this.hs.db.update("users", [{
71
+ field: "user_id",
72
+ value: userId
73
+ }], { suspended })) throw errors.notFound("Unknown user");
74
+ await this.hs.auth.invalidateUser(userId);
75
+ await this.hs.audit.record({
76
+ action: suspended ? "user_suspend" : "user_unsuspend",
77
+ actor,
78
+ target: userId
79
+ });
80
+ }
81
+ async resetPassword(userId, password, logoutDevices = true) {
82
+ const hash = await this.hs.userAuth.hashPassword(password);
83
+ if (!await this.hs.db.update("users", [{
84
+ field: "user_id",
85
+ value: userId
86
+ }], { password_hash: hash })) throw errors.notFound("Unknown user");
87
+ if (logoutDevices) await this.hs.auth.deleteAllDevices(userId);
88
+ }
89
+ /** Deactivate an account: leave all rooms, log out everywhere and (optionally) erase the profile. */
90
+ async deactivateUser(userId, opts = {}) {
91
+ if (!await this.hs.db.findOne("users", [{
92
+ field: "user_id",
93
+ value: userId
94
+ }])) throw errors.notFound("Unknown user");
95
+ for (const membership of [
96
+ "join",
97
+ "invite",
98
+ "knock"
99
+ ]) for (const roomId of await this.hs.rooms.roomsForUser(userId, membership)) try {
100
+ await this.hs.rooms.setMembership(roomId, userId, userId, "leave");
101
+ } catch {}
102
+ await this.hs.auth.deleteAllDevices(userId);
103
+ await this.hs.threepids.removeAll(userId);
104
+ await this.hs.db.delete("pushers", [{
105
+ field: "user_id",
106
+ value: userId
107
+ }]);
108
+ const update = {
109
+ deactivated: true,
110
+ password_hash: null
111
+ };
112
+ if (opts.erase !== false) Object.assign(update, {
113
+ displayname: null,
114
+ avatar_url: null
115
+ });
116
+ await this.hs.db.update("users", [{
117
+ field: "user_id",
118
+ value: userId
119
+ }], update);
120
+ await this.hs.auth.invalidateUser(userId);
121
+ }
122
+ async createRegistrationToken(opts = {}) {
123
+ const token = opts.token ?? randomString(opts.length ?? 16);
124
+ if (!/^[A-Za-z0-9._~-]{1,64}$/.test(token)) throw errors.invalidParam("Invalid token");
125
+ if (await this.hs.db.findOne("registration_tokens", [{
126
+ field: "token",
127
+ value: token
128
+ }])) throw errors.invalidParam("Token already exists");
129
+ const row = {
130
+ token,
131
+ uses_allowed: opts.usesAllowed ?? null,
132
+ pending: 0,
133
+ completed: 0,
134
+ expiry_time: opts.expiryTime ?? null
135
+ };
136
+ await this.hs.db.create("registration_tokens", row);
137
+ return row;
138
+ }
139
+ listRegistrationTokens() {
140
+ return this.hs.db.findMany("registration_tokens");
141
+ }
142
+ async deleteRegistrationToken(token) {
143
+ if (!await this.hs.db.delete("registration_tokens", [{
144
+ field: "token",
145
+ value: token
146
+ }])) throw errors.notFound("Unknown token");
147
+ }
148
+ async listRooms(opts = {}) {
149
+ const rows = await this.hs.db.findMany("rooms", { sortBy: {
150
+ field: "created_ts",
151
+ direction: "asc"
152
+ } });
153
+ const from = opts.from ?? 0;
154
+ const page = rows.slice(from, from + (opts.limit ?? 100));
155
+ return {
156
+ rooms: await Promise.all(page.map(async (r) => ({
157
+ ...r,
158
+ joined_members: (await this.hs.rooms.joinedMembers(r.room_id)).length
159
+ }))),
160
+ total: rows.length
161
+ };
162
+ }
163
+ /** Remove everyone from a room, delist it and drop its aliases. With `purge`, also delete its history. */
164
+ async deleteRoom(roomId, opts = {}) {
165
+ if (!await this.hs.rooms.exists(roomId)) throw errors.notFound("Unknown room");
166
+ const kicked = [];
167
+ for (const membership of [
168
+ "join",
169
+ "invite",
170
+ "knock"
171
+ ]) for (const row of await this.hs.db.findMany("memberships", { where: [{
172
+ field: "room_id",
173
+ value: roomId
174
+ }, {
175
+ field: "membership",
176
+ value: membership
177
+ }] })) {
178
+ if (!this.hs.isLocalUser(row.user_id)) continue;
179
+ try {
180
+ await this.hs.rooms.setMembership(roomId, row.user_id, row.user_id, "leave", opts.reason);
181
+ kicked.push(row.user_id);
182
+ } catch (err) {
183
+ this.hs.logger.warn("Could not remove", row.user_id, "from", roomId, err);
184
+ }
185
+ }
186
+ await this.hs.db.update("rooms", [{
187
+ field: "room_id",
188
+ value: roomId
189
+ }], { is_public: false });
190
+ await this.hs.db.delete("room_aliases", [{
191
+ field: "room_id",
192
+ value: roomId
193
+ }]);
194
+ if (opts.purge) {
195
+ const where = [{
196
+ field: "room_id",
197
+ value: roomId
198
+ }];
199
+ await this.hs.transaction(async (db) => {
200
+ for (const table of [
201
+ "events",
202
+ "state_snapshots",
203
+ "current_state",
204
+ "memberships",
205
+ "receipts",
206
+ "relations",
207
+ "notifications",
208
+ "rooms"
209
+ ]) await db.delete(table, where);
210
+ });
211
+ this.hs.rooms.forgetRoom(roomId);
212
+ }
213
+ return { kicked };
214
+ }
215
+ async deleteMedia(mediaId) {
216
+ await this.hs.media.delete?.(mediaId);
217
+ await this.hs.db.delete("media", [{
218
+ field: "media_id",
219
+ value: mediaId
220
+ }]);
221
+ }
222
+ /** Delete local media uploaded before `beforeTs`. Returns the deleted media IDs. */
223
+ async purgeMediaBefore(beforeTs) {
224
+ const rows = await this.hs.db.findMany("media", { where: [{
225
+ field: "created_ts",
226
+ operator: "lt",
227
+ value: beforeTs
228
+ }] });
229
+ for (const r of rows) await this.deleteMedia(r.media_id);
230
+ return rows.map((r) => r.media_id);
231
+ }
232
+ get noticeUserId() {
233
+ return `@${this.hs.config.serverNotices.localpart}:${this.hs.serverName}`;
234
+ }
235
+ /** Whether `roomId` is a server notices room (members may not reject or leave its invites). */
236
+ async isNoticeRoom(roomId) {
237
+ return (await this.hs.db.findOne("rooms", [{
238
+ field: "room_id",
239
+ value: roomId
240
+ }]))?.creator === this.noticeUserId;
241
+ }
242
+ /** Send a message to a user in their server notices room, creating or re-inviting as needed. */
243
+ async sendServerNotice(userId, content, opts = {}) {
244
+ if (!isValidUserId(userId) || !this.hs.isLocalUser(userId)) throw errors.invalidParam("Server notices can only be sent to local users");
245
+ if (!await this.hs.db.findOne("users", [{
246
+ field: "user_id",
247
+ value: userId
248
+ }])) throw errors.notFound("Unknown user");
249
+ const txnKey = opts.txnId ? `notice-txn:${userId}|${opts.txnId}` : null;
250
+ if (txnKey) {
251
+ const existing = await this.hs.cluster.get(txnKey);
252
+ if (existing) return existing;
253
+ }
254
+ const noticeUser = this.noticeUserId;
255
+ if (!await this.hs.db.findOne("users", [{
256
+ field: "user_id",
257
+ value: noticeUser
258
+ }])) await createUser(this.hs, noticeUser.slice(1).split(":")[0], { displayname: this.hs.config.serverNotices.displayName });
259
+ const roomId = await this.noticeRoomFor(userId);
260
+ const membership = await this.hs.rooms.membership(roomId, userId);
261
+ if (membership !== "join" && membership !== "invite") await this.hs.rooms.invite(roomId, noticeUser, userId);
262
+ const eventId = await this.hs.rooms.sendEvent(roomId, noticeUser, {
263
+ type: opts.type ?? M.ROOM.MESSAGE,
264
+ content
265
+ });
266
+ if (txnKey) await this.hs.cluster.set(txnKey, eventId, 864e5);
267
+ return eventId;
268
+ }
269
+ async noticeRoomFor(userId) {
270
+ const noticeUser = this.noticeUserId;
271
+ const rooms = await this.hs.db.findMany("rooms", { where: [{
272
+ field: "creator",
273
+ value: noticeUser
274
+ }] });
275
+ for (const room of rooms) if ((await this.hs.db.findOne("account_data", [
276
+ {
277
+ field: "user_id",
278
+ value: userId
279
+ },
280
+ {
281
+ field: "room_id",
282
+ value: room.room_id
283
+ },
284
+ {
285
+ field: "type",
286
+ value: M.TAG
287
+ }
288
+ ]))?.content?.tags?.[M.SERVER_NOTICE]) return room.room_id;
289
+ const { room_id } = await this.hs.rooms.createRoom(noticeUser, {
290
+ preset: "private_chat",
291
+ name: this.hs.config.serverNotices.roomName,
292
+ power_level_content_override: {
293
+ users: { [noticeUser]: 100 },
294
+ events_default: 100
295
+ }
296
+ });
297
+ await this.hs.streams.write((stream) => this.hs.db.upsert("account_data", {
298
+ user_id: userId,
299
+ room_id,
300
+ type: M.TAG,
301
+ content: { tags: { [M.SERVER_NOTICE]: { order: .5 } } },
302
+ stream
303
+ }));
304
+ return room_id;
305
+ }
306
+ };
307
+ //#endregion
308
+ export { AdminService };
@@ -0,0 +1,49 @@
1
+ import { PDU } from "./events.js";
2
+ import { Homeserver } from "../homeserver.js";
3
+ //#region src/core/adminroom.d.ts
4
+ export interface AdminRoomOptions {
5
+ /** Enable the admin room and admin bot. Default: true when the option object is given. */
6
+ enabled?: boolean;
7
+ /** The first account registered on the server becomes a server admin. Default: true. */
8
+ firstUserIsAdmin?: boolean;
9
+ /** Let the very first account register even when registration is closed. Default: true. */
10
+ firstUserCanRegister?: boolean;
11
+ /** Localpart of the admin bot. Default: "admin". */
12
+ botLocalpart?: string;
13
+ botDisplayName?: string;
14
+ roomName?: string;
15
+ /** Localpart of the admin room alias (#admins:server). Default: "admins". */
16
+ aliasLocalpart?: string;
17
+ }
18
+ /**
19
+ * A tuwunel-style admin room: server admins chat with an admin bot to manage the
20
+ * server. The first registered account becomes an admin and is put in the room.
21
+ */
22
+ export declare class AdminRoomService {
23
+ private hs;
24
+ readonly options: Required<AdminRoomOptions>;
25
+ private roomIdCache;
26
+ constructor(hs: Homeserver, options: AdminRoomOptions);
27
+ get botUserId(): string;
28
+ get alias(): string;
29
+ /** Whether no real (non-bot) account exists yet. */
30
+ noUsersYet(): Promise<boolean>;
31
+ roomId(): Promise<string | null>;
32
+ /** Create the admin bot and room if needed. */
33
+ ensureRoom(): Promise<string>;
34
+ /** Called after an account was created. */
35
+ onUserCreated(userId: string): Promise<void>;
36
+ /** Put an admin into the admin room. */
37
+ addAdmin(userId: string): Promise<void>;
38
+ /** Remove a former admin from the admin room. */
39
+ removeAdmin(userId: string): Promise<void>;
40
+ /** Post a message from the admin bot. */
41
+ say(text: string, replyTo?: string): Promise<void>;
42
+ /** Called for every new event; handles commands posted in the admin room. */
43
+ onEvent(event: PDU): Promise<void>;
44
+ private resolveUser;
45
+ private resolveRoom;
46
+ /** Run one admin command; returns the reply text. */
47
+ execute(actor: string, args: string[]): Promise<string>;
48
+ }
49
+ //#endregion