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.
- package/LICENSE +75 -0
- package/README.md +479 -2
- package/dist/adapters/auth.d.ts +50 -0
- package/dist/adapters/auth.js +18 -0
- package/dist/adapters/cluster.d.ts +55 -0
- package/dist/adapters/cluster.js +249 -0
- package/dist/adapters/media.d.ts +50 -0
- package/dist/adapters/media.js +63 -0
- package/dist/adapters/s3.d.ts +33 -0
- package/dist/adapters/s3.js +95 -0
- package/dist/api/account.js +594 -0
- package/dist/api/admin.js +267 -0
- package/dist/api/appservice.js +45 -0
- package/dist/api/backup.js +280 -0
- package/dist/api/federation.js +826 -0
- package/dist/api/filters.js +88 -0
- package/dist/api/index.js +47 -0
- package/dist/api/legacy.js +183 -0
- package/dist/api/media.js +271 -0
- package/dist/api/misc.js +95 -0
- package/dist/api/preview.js +201 -0
- package/dist/api/profile.js +176 -0
- package/dist/api/push.js +202 -0
- package/dist/api/relationships.js +270 -0
- package/dist/api/rooms.js +1487 -0
- package/dist/api/rtc.d.ts +23 -0
- package/dist/api/rtc.js +66 -0
- package/dist/api/search.js +161 -0
- package/dist/api/slidingsync.js +474 -0
- package/dist/api/spaces.js +177 -0
- package/dist/api/sync.js +609 -0
- package/dist/api/threadsubs.js +140 -0
- package/dist/api/threepid.js +155 -0
- package/dist/api/uia.js +127 -0
- package/dist/api/user.js +329 -0
- package/dist/core/admin.d.ts +84 -0
- package/dist/core/admin.js +308 -0
- package/dist/core/adminroom.d.ts +49 -0
- package/dist/core/adminroom.js +483 -0
- package/dist/core/appservices.d.ts +74 -0
- package/dist/core/appservices.js +369 -0
- package/dist/core/audit.d.ts +50 -0
- package/dist/core/audit.js +86 -0
- package/dist/core/auth.d.ts +48 -0
- package/dist/core/auth.js +237 -0
- package/dist/core/authrules.d.ts +4 -0
- package/dist/core/authrules.js +348 -0
- package/dist/core/cluster.d.ts +66 -0
- package/dist/core/cluster.js +191 -0
- package/dist/core/crypto.d.ts +10 -0
- package/dist/core/crypto.js +100 -0
- package/dist/core/delayed.d.ts +39 -0
- package/dist/core/delayed.js +160 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +45 -0
- package/dist/core/events.d.ts +32 -0
- package/dist/core/events.js +186 -0
- package/dist/core/glob.js +66 -0
- package/dist/core/identity.d.ts +24 -0
- package/dist/core/identity.js +96 -0
- package/dist/core/ids.js +36 -0
- package/dist/core/invitefilter.js +36 -0
- package/dist/core/json.js +16 -0
- package/dist/core/keys.d.ts +36 -0
- package/dist/core/keys.js +450 -0
- package/dist/core/m.d.ts +209 -0
- package/dist/core/m.js +182 -0
- package/dist/core/notifier.d.ts +17 -0
- package/dist/core/notifier.js +54 -0
- package/dist/core/oauth.d.ts +38 -0
- package/dist/core/oauth.js +108 -0
- package/dist/core/partialstate.d.ts +32 -0
- package/dist/core/partialstate.js +198 -0
- package/dist/core/phone.js +268 -0
- package/dist/core/policy.d.ts +29 -0
- package/dist/core/policy.js +97 -0
- package/dist/core/profile.js +39 -0
- package/dist/core/push.d.ts +46 -0
- package/dist/core/push.js +307 -0
- package/dist/core/pushrules.js +229 -0
- package/dist/core/ratelimit.d.ts +23 -0
- package/dist/core/ratelimit.js +77 -0
- package/dist/core/rooms.d.ts +215 -0
- package/dist/core/rooms.js +1731 -0
- package/dist/core/settings.d.ts +11 -0
- package/dist/core/settings.js +29 -0
- package/dist/core/sso.d.ts +46 -0
- package/dist/core/sso.js +165 -0
- package/dist/core/stateres.d.ts +4 -0
- package/dist/core/stateres.js +209 -0
- package/dist/core/threepid.d.ts +80 -0
- package/dist/core/threepid.js +275 -0
- package/dist/core/typing.d.ts +16 -0
- package/dist/core/typing.js +60 -0
- package/dist/federation/client.d.ts +51 -0
- package/dist/federation/client.js +279 -0
- package/dist/federation/index.d.ts +153 -0
- package/dist/federation/index.js +1059 -0
- package/dist/federation/keyring.d.ts +26 -0
- package/dist/federation/keyring.js +122 -0
- package/dist/federation/sender.d.ts +26 -0
- package/dist/federation/sender.js +164 -0
- package/dist/homeserver.d.ts +224 -0
- package/dist/homeserver.js +302 -0
- package/dist/http/adapters.d.ts +40 -0
- package/dist/http/adapters.js +130 -0
- package/dist/http/handler.d.ts +8 -0
- package/dist/http/handler.js +181 -0
- package/dist/http/input.js +24 -0
- package/dist/http/procedure.js +25 -0
- package/dist/http/router.d.ts +43 -0
- package/dist/http/router.js +56 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +15 -0
- package/dist/storage/memory.d.ts +5 -0
- package/dist/storage/memory.js +122 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.js +664 -0
- package/dist/storage/sql.d.ts +72 -0
- package/dist/storage/sql.js +274 -0
- package/dist/storage/types.d.ts +55 -0
- package/dist/vendor/zod/LICENSE +21 -0
- package/dist/vendor/zod/v4/classic/errors.js +54 -0
- package/dist/vendor/zod/v4/classic/parse.js +17 -0
- package/dist/vendor/zod/v4/classic/schemas.js +865 -0
- package/dist/vendor/zod/v4/core/api.js +512 -0
- package/dist/vendor/zod/v4/core/checks.js +300 -0
- package/dist/vendor/zod/v4/core/core.js +109 -0
- package/dist/vendor/zod/v4/core/doc.js +35 -0
- package/dist/vendor/zod/v4/core/errors.js +118 -0
- package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
- package/dist/vendor/zod/v4/core/memoizer.js +255 -0
- package/dist/vendor/zod/v4/core/parse.js +206 -0
- package/dist/vendor/zod/v4/core/regexes.js +73 -0
- package/dist/vendor/zod/v4/core/registries.js +48 -0
- package/dist/vendor/zod/v4/core/schemas.js +1378 -0
- package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
- package/dist/vendor/zod/v4/core/util.js +537 -0
- package/dist/vendor/zod/v4/core/versions.js +8 -0
- package/dist/vendor/zod/v4/locales/en.js +110 -0
- package/package.json +65 -4
package/dist/api/user.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { canonicalJson, isObject } from "../core/json.js";
|
|
2
|
+
import { M } from "../core/m.js";
|
|
3
|
+
import { errors } from "../core/errors.js";
|
|
4
|
+
import { array, record, string, unknown } from "../vendor/zod/v4/classic/schemas.js";
|
|
5
|
+
import { body, fail } from "../http/input.js";
|
|
6
|
+
import { procedure } from "../http/procedure.js";
|
|
7
|
+
import { requireUia } from "./uia.js";
|
|
8
|
+
//#region src/api/user.ts
|
|
9
|
+
const deleteDevicesBody = body({ devices: array(unknown(), fail("M_BAD_JSON", "devices must be a list")) });
|
|
10
|
+
const keysQueryBody = body({
|
|
11
|
+
device_keys: record(string(), array(string(), fail("M_BAD_JSON", "device_keys values must be lists of device IDs")), fail("M_BAD_JSON", "device_keys must be an object")),
|
|
12
|
+
timeout: unknown().optional()
|
|
13
|
+
});
|
|
14
|
+
const keysClaimBody = body({ one_time_keys: record(string(), unknown(), fail("M_BAD_JSON", "one_time_keys must be an object")) });
|
|
15
|
+
const SERVER_MANAGED_TYPES = [M.FULLY_READ, M.PUSH_RULES];
|
|
16
|
+
async function setAccountData(hs, userId, roomId, type, content) {
|
|
17
|
+
await hs.streams.write((stream) => hs.db.upsert("account_data", {
|
|
18
|
+
user_id: userId,
|
|
19
|
+
room_id: roomId,
|
|
20
|
+
type,
|
|
21
|
+
content,
|
|
22
|
+
stream
|
|
23
|
+
}));
|
|
24
|
+
hs.notifier.notify([userId]);
|
|
25
|
+
}
|
|
26
|
+
async function getAccountData(hs, userId, roomId, type) {
|
|
27
|
+
const row = await hs.db.findOne("account_data", [
|
|
28
|
+
{
|
|
29
|
+
field: "user_id",
|
|
30
|
+
value: userId
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
field: "room_id",
|
|
34
|
+
value: roomId
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
field: "type",
|
|
38
|
+
value: type
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
if (!row || Object.keys(row.content ?? {}).length === 0) throw errors.notFound("Account data not found");
|
|
42
|
+
return row.content;
|
|
43
|
+
}
|
|
44
|
+
async function checkUser(ctx) {
|
|
45
|
+
const s = await ctx.auth();
|
|
46
|
+
if (ctx.params.userId !== s.userId) throw errors.forbidden("Cannot access another user's data");
|
|
47
|
+
return s;
|
|
48
|
+
}
|
|
49
|
+
async function checkRoom(ctx) {
|
|
50
|
+
if (!ctx.params.roomId.startsWith("!")) throw errors.invalidParam("Invalid room ID");
|
|
51
|
+
}
|
|
52
|
+
function registerUserRoutes(r) {
|
|
53
|
+
r.client("PUT", "/user/:userId/account_data/:type", procedure({ handler: async (ctx) => {
|
|
54
|
+
const s = await checkUser(ctx);
|
|
55
|
+
if (SERVER_MANAGED_TYPES.includes(ctx.params.type)) throw errors.badJson("This type is managed by the server");
|
|
56
|
+
await setAccountData(ctx.hs, s.userId, "", ctx.params.type, await ctx.json());
|
|
57
|
+
return {};
|
|
58
|
+
} }));
|
|
59
|
+
r.client("GET", "/user/:userId/account_data/:type", procedure({ handler: async (ctx) => {
|
|
60
|
+
const s = await checkUser(ctx);
|
|
61
|
+
return getAccountData(ctx.hs, s.userId, "", ctx.params.type);
|
|
62
|
+
} }));
|
|
63
|
+
r.client("PUT", "/user/:userId/rooms/:roomId/account_data/:type", procedure({ handler: async (ctx) => {
|
|
64
|
+
const s = await checkUser(ctx);
|
|
65
|
+
await checkRoom(ctx);
|
|
66
|
+
if (SERVER_MANAGED_TYPES.includes(ctx.params.type)) throw errors.badJson("This type is managed by the server");
|
|
67
|
+
await setAccountData(ctx.hs, s.userId, ctx.params.roomId, ctx.params.type, await ctx.json());
|
|
68
|
+
return {};
|
|
69
|
+
} }));
|
|
70
|
+
r.client("GET", "/user/:userId/rooms/:roomId/account_data/:type", procedure({ handler: async (ctx) => {
|
|
71
|
+
const s = await checkUser(ctx);
|
|
72
|
+
await checkRoom(ctx);
|
|
73
|
+
return getAccountData(ctx.hs, s.userId, ctx.params.roomId, ctx.params.type);
|
|
74
|
+
} }));
|
|
75
|
+
const del = "/_matrix/client/unstable/org.matrix.msc3391";
|
|
76
|
+
r.delete(`${del}/user/:userId/account_data/:type`, procedure({ handler: async (ctx) => {
|
|
77
|
+
const s = await checkUser(ctx);
|
|
78
|
+
await setAccountData(ctx.hs, s.userId, "", ctx.params.type, {});
|
|
79
|
+
return {};
|
|
80
|
+
} }));
|
|
81
|
+
r.delete(`${del}/user/:userId/rooms/:roomId/account_data/:type`, procedure({ handler: async (ctx) => {
|
|
82
|
+
const s = await checkUser(ctx);
|
|
83
|
+
await setAccountData(ctx.hs, s.userId, ctx.params.roomId, ctx.params.type, {});
|
|
84
|
+
return {};
|
|
85
|
+
} }));
|
|
86
|
+
const readTags = async (hs, userId, roomId) => {
|
|
87
|
+
const row = await hs.db.findOne("account_data", [
|
|
88
|
+
{
|
|
89
|
+
field: "user_id",
|
|
90
|
+
value: userId
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
field: "room_id",
|
|
94
|
+
value: roomId
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
field: "type",
|
|
98
|
+
value: M.TAG
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
return isObject(row?.content?.tags) ? row.content.tags : {};
|
|
102
|
+
};
|
|
103
|
+
r.client("GET", "/user/:userId/rooms/:roomId/tags", procedure({ handler: async (ctx) => {
|
|
104
|
+
const s = await checkUser(ctx);
|
|
105
|
+
return { tags: await readTags(ctx.hs, s.userId, ctx.params.roomId) };
|
|
106
|
+
} }));
|
|
107
|
+
r.client("PUT", "/user/:userId/rooms/:roomId/tags/:tag", procedure({ handler: async (ctx) => {
|
|
108
|
+
const s = await checkUser(ctx);
|
|
109
|
+
const body = await ctx.json();
|
|
110
|
+
if (body.order !== void 0 && typeof body.order !== "number") throw errors.badJson("order must be a number");
|
|
111
|
+
const tags = await readTags(ctx.hs, s.userId, ctx.params.roomId);
|
|
112
|
+
tags[ctx.params.tag] = body;
|
|
113
|
+
await setAccountData(ctx.hs, s.userId, ctx.params.roomId, M.TAG, { tags });
|
|
114
|
+
return {};
|
|
115
|
+
} }));
|
|
116
|
+
r.client("DELETE", "/user/:userId/rooms/:roomId/tags/:tag", procedure({ handler: async (ctx) => {
|
|
117
|
+
const s = await checkUser(ctx);
|
|
118
|
+
const tags = await readTags(ctx.hs, s.userId, ctx.params.roomId);
|
|
119
|
+
delete tags[ctx.params.tag];
|
|
120
|
+
await setAccountData(ctx.hs, s.userId, ctx.params.roomId, M.TAG, { tags });
|
|
121
|
+
return {};
|
|
122
|
+
} }));
|
|
123
|
+
const formatDevice = (d) => ({
|
|
124
|
+
device_id: d.device_id,
|
|
125
|
+
display_name: d.display_name ?? void 0,
|
|
126
|
+
last_seen_ip: d.last_seen_ip ?? void 0,
|
|
127
|
+
last_seen_ts: d.last_seen_ts ?? void 0
|
|
128
|
+
});
|
|
129
|
+
r.client("GET", "/devices", procedure({
|
|
130
|
+
auth: "user",
|
|
131
|
+
handler: async (ctx) => {
|
|
132
|
+
const s = ctx.session;
|
|
133
|
+
return { devices: (await ctx.hs.db.findMany("devices", { where: [{
|
|
134
|
+
field: "user_id",
|
|
135
|
+
value: s.userId
|
|
136
|
+
}] })).map(formatDevice) };
|
|
137
|
+
}
|
|
138
|
+
}));
|
|
139
|
+
r.client("GET", "/devices/:deviceId", procedure({
|
|
140
|
+
auth: "user",
|
|
141
|
+
handler: async (ctx) => {
|
|
142
|
+
const s = ctx.session;
|
|
143
|
+
const row = await ctx.hs.db.findOne("devices", [{
|
|
144
|
+
field: "user_id",
|
|
145
|
+
value: s.userId
|
|
146
|
+
}, {
|
|
147
|
+
field: "device_id",
|
|
148
|
+
value: ctx.params.deviceId
|
|
149
|
+
}]);
|
|
150
|
+
if (!row) throw errors.notFound("Device not found");
|
|
151
|
+
return formatDevice(row);
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
154
|
+
r.client("PUT", "/devices/:deviceId", procedure({
|
|
155
|
+
auth: "user",
|
|
156
|
+
handler: async (ctx) => {
|
|
157
|
+
const s = ctx.session;
|
|
158
|
+
const body = await ctx.json();
|
|
159
|
+
const where = [{
|
|
160
|
+
field: "user_id",
|
|
161
|
+
value: s.userId
|
|
162
|
+
}, {
|
|
163
|
+
field: "device_id",
|
|
164
|
+
value: ctx.params.deviceId
|
|
165
|
+
}];
|
|
166
|
+
if (!await ctx.hs.db.findOne("devices", where)) throw errors.notFound("Device not found");
|
|
167
|
+
if (body.display_name !== void 0) {
|
|
168
|
+
if (typeof body.display_name !== "string") throw errors.badJson("display_name must be a string");
|
|
169
|
+
await ctx.hs.db.update("devices", where, { display_name: body.display_name });
|
|
170
|
+
await ctx.hs.keys.deviceChanged(s.userId);
|
|
171
|
+
}
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
r.client("DELETE", "/devices/:deviceId", procedure({
|
|
176
|
+
auth: "user",
|
|
177
|
+
handler: async (ctx) => {
|
|
178
|
+
const s = ctx.session;
|
|
179
|
+
const body = await ctx.json();
|
|
180
|
+
const where = [{
|
|
181
|
+
field: "user_id",
|
|
182
|
+
value: s.userId
|
|
183
|
+
}, {
|
|
184
|
+
field: "device_id",
|
|
185
|
+
value: ctx.params.deviceId
|
|
186
|
+
}];
|
|
187
|
+
if (!await ctx.hs.db.findOne("devices", where)) throw errors.notFound("Device not found");
|
|
188
|
+
await requireUia(ctx.hs, body, [[M.LOGIN.PASSWORD]], s.userId);
|
|
189
|
+
await ctx.hs.auth.deleteDevice(s.userId, ctx.params.deviceId);
|
|
190
|
+
return {};
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
193
|
+
r.client("POST", "/delete_devices", procedure({
|
|
194
|
+
auth: "user",
|
|
195
|
+
input: deleteDevicesBody,
|
|
196
|
+
handler: async (ctx) => {
|
|
197
|
+
const s = ctx.session;
|
|
198
|
+
const body = ctx.input;
|
|
199
|
+
await requireUia(ctx.hs, body, [[M.LOGIN.PASSWORD]], s.userId);
|
|
200
|
+
for (const d of body.devices) if (typeof d === "string") await ctx.hs.auth.deleteDevice(s.userId, d);
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
r.client("POST", "/keys/upload", procedure({
|
|
205
|
+
auth: "user",
|
|
206
|
+
input: body({}),
|
|
207
|
+
handler: async (ctx) => {
|
|
208
|
+
const s = ctx.session;
|
|
209
|
+
return { one_time_key_counts: await ctx.hs.keys.upload(s.userId, s.deviceId, ctx.input) };
|
|
210
|
+
}
|
|
211
|
+
}));
|
|
212
|
+
r.client("POST", "/keys/query", procedure({
|
|
213
|
+
auth: "user",
|
|
214
|
+
rateLimit: "keyQuery",
|
|
215
|
+
input: keysQueryBody,
|
|
216
|
+
handler: async (ctx) => {
|
|
217
|
+
const s = ctx.session;
|
|
218
|
+
const body = ctx.input;
|
|
219
|
+
return ctx.hs.keys.query(s.userId, body.device_keys, Math.min(Number(body.timeout ?? 1e4) || 1e4, 3e4));
|
|
220
|
+
}
|
|
221
|
+
}));
|
|
222
|
+
r.client("POST", "/keys/claim", procedure({
|
|
223
|
+
auth: "user",
|
|
224
|
+
input: keysClaimBody,
|
|
225
|
+
handler: async (ctx) => {
|
|
226
|
+
const body = ctx.input;
|
|
227
|
+
const local = {};
|
|
228
|
+
const remote = {};
|
|
229
|
+
for (const [userId, devices] of Object.entries(body.one_time_keys)) (ctx.hs.isLocalUser(userId) ? local : remote)[userId] = devices;
|
|
230
|
+
const claimed = await ctx.hs.keys.claim(local);
|
|
231
|
+
const fed = Object.keys(remote).length ? await ctx.hs.federation.claimKeys(remote) : {
|
|
232
|
+
results: {},
|
|
233
|
+
failures: {}
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
one_time_keys: {
|
|
237
|
+
...claimed,
|
|
238
|
+
...fed.results
|
|
239
|
+
},
|
|
240
|
+
failures: fed.failures
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
r.client("GET", "/keys/changes", procedure({
|
|
245
|
+
auth: "user",
|
|
246
|
+
handler: async (ctx) => {
|
|
247
|
+
const s = ctx.session;
|
|
248
|
+
const parse = (v) => {
|
|
249
|
+
const m = /^s?(\d+)/.exec(v ?? "");
|
|
250
|
+
if (!m) throw errors.invalidParam("Invalid token");
|
|
251
|
+
return Number(m[1]);
|
|
252
|
+
};
|
|
253
|
+
return ctx.hs.keys.changesSince(s.userId, parse(ctx.query.get("from")), parse(ctx.query.get("to")));
|
|
254
|
+
}
|
|
255
|
+
}));
|
|
256
|
+
r.client("POST", "/keys/device_signing/upload", procedure({
|
|
257
|
+
auth: "user",
|
|
258
|
+
input: body({}),
|
|
259
|
+
handler: async (ctx) => {
|
|
260
|
+
const s = ctx.session;
|
|
261
|
+
const body = ctx.input;
|
|
262
|
+
if (await ctx.hs.db.findOne("cross_signing_keys", [{
|
|
263
|
+
field: "user_id",
|
|
264
|
+
value: s.userId
|
|
265
|
+
}, {
|
|
266
|
+
field: "key_type",
|
|
267
|
+
value: "master"
|
|
268
|
+
}])) {
|
|
269
|
+
const stored = new Map((await ctx.hs.db.findMany("cross_signing_keys", { where: [{
|
|
270
|
+
field: "user_id",
|
|
271
|
+
value: s.userId
|
|
272
|
+
}] })).map((row) => [row.key_type, canonicalJson(row.key)]));
|
|
273
|
+
if ([
|
|
274
|
+
"master",
|
|
275
|
+
"self_signing",
|
|
276
|
+
"user_signing"
|
|
277
|
+
].filter((type) => body[`${type}_key`] !== void 0).every((type) => stored.get(type) === canonicalJson(body[`${type}_key`]))) return {};
|
|
278
|
+
await requireUia(ctx.hs, body, [[M.LOGIN.PASSWORD]], s.userId);
|
|
279
|
+
}
|
|
280
|
+
await ctx.hs.keys.uploadCrossSigning(s.userId, body);
|
|
281
|
+
return {};
|
|
282
|
+
}
|
|
283
|
+
}));
|
|
284
|
+
r.client("POST", "/keys/signatures/upload", procedure({
|
|
285
|
+
auth: "user",
|
|
286
|
+
input: body({}),
|
|
287
|
+
handler: async (ctx) => {
|
|
288
|
+
const s = ctx.session;
|
|
289
|
+
return ctx.hs.keys.uploadSignatures(s.userId, ctx.input);
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
292
|
+
r.client("PUT", "/sendToDevice/:eventType/:txnId", procedure({
|
|
293
|
+
auth: "user",
|
|
294
|
+
handler: async (ctx) => {
|
|
295
|
+
const s = ctx.session;
|
|
296
|
+
const hs = ctx.hs;
|
|
297
|
+
const body = await ctx.json();
|
|
298
|
+
if (!isObject(body.messages)) throw errors.badJson("messages must be an object");
|
|
299
|
+
const txnKey = `${s.userId}|${s.deviceId}|${ctx.params.txnId}`;
|
|
300
|
+
if (!await hs.cluster.setIfAbsent(`todevice-txn:${txnKey}`, true, 864e5)) return {};
|
|
301
|
+
const recipients = /* @__PURE__ */ new Set();
|
|
302
|
+
for (const [userId, devices] of Object.entries(body.messages)) {
|
|
303
|
+
if (!isObject(devices) || !hs.isLocalUser(userId)) continue;
|
|
304
|
+
let targets = Object.keys(devices);
|
|
305
|
+
if (targets.includes("*")) targets = (await hs.db.findMany("devices", { where: [{
|
|
306
|
+
field: "user_id",
|
|
307
|
+
value: userId
|
|
308
|
+
}] })).map((d) => d.device_id);
|
|
309
|
+
for (const deviceId of targets) {
|
|
310
|
+
const content = devices[deviceId] ?? devices["*"];
|
|
311
|
+
await hs.streams.write((stream) => hs.db.create("to_device", {
|
|
312
|
+
stream,
|
|
313
|
+
user_id: userId,
|
|
314
|
+
device_id: deviceId,
|
|
315
|
+
sender: s.userId,
|
|
316
|
+
type: ctx.params.eventType,
|
|
317
|
+
content
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
recipients.add(userId);
|
|
321
|
+
}
|
|
322
|
+
hs.notifier.notify(recipients);
|
|
323
|
+
await hs.federation.sendToDevice(s.userId, ctx.params.eventType, body.messages, `${s.deviceId}-${ctx.params.txnId}`);
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
export { registerUserRoutes };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Row } from "../storage/types.js";
|
|
2
|
+
import { Homeserver } from "../homeserver.js";
|
|
3
|
+
//#region src/core/admin.d.ts
|
|
4
|
+
export interface ServerNoticesOptions {
|
|
5
|
+
/** Localpart of the user that sends server notices. Default: "server". */
|
|
6
|
+
localpart?: string;
|
|
7
|
+
displayName?: string;
|
|
8
|
+
roomName?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Programmatic administration, for embedding applications and the admin HTTP API. */
|
|
11
|
+
export declare class AdminService {
|
|
12
|
+
private hs;
|
|
13
|
+
constructor(hs: Homeserver);
|
|
14
|
+
createUser(localpart: string, opts?: {
|
|
15
|
+
password?: string;
|
|
16
|
+
admin?: boolean;
|
|
17
|
+
displayname?: string;
|
|
18
|
+
}): Promise<string>;
|
|
19
|
+
getUser(userId: string): Promise<Record<string, any> | null>;
|
|
20
|
+
listUsers(opts?: {
|
|
21
|
+
from?: number;
|
|
22
|
+
limit?: number;
|
|
23
|
+
search?: string;
|
|
24
|
+
includeDeactivated?: boolean;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
users: {
|
|
27
|
+
[x: string]: any;
|
|
28
|
+
}[];
|
|
29
|
+
total: number;
|
|
30
|
+
next: number | null;
|
|
31
|
+
}>;
|
|
32
|
+
setAdmin(userId: string, admin: boolean): Promise<void>;
|
|
33
|
+
/** Lock or unlock an account (reversible; sessions are kept but unusable while locked). */
|
|
34
|
+
setLocked(userId: string, locked: boolean, actor?: string): Promise<void>;
|
|
35
|
+
/** Suspend or unsuspend an account (reversible; the user can still read, leave and log out). */
|
|
36
|
+
setSuspended(userId: string, suspended: boolean, actor?: string): Promise<void>;
|
|
37
|
+
resetPassword(userId: string, password: string, logoutDevices?: boolean): Promise<void>;
|
|
38
|
+
/** Deactivate an account: leave all rooms, log out everywhere and (optionally) erase the profile. */
|
|
39
|
+
deactivateUser(userId: string, opts?: {
|
|
40
|
+
erase?: boolean;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
createRegistrationToken(opts?: {
|
|
43
|
+
token?: string;
|
|
44
|
+
usesAllowed?: number | null;
|
|
45
|
+
expiryTime?: number | null;
|
|
46
|
+
length?: number;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
token: string;
|
|
49
|
+
uses_allowed: number | null;
|
|
50
|
+
pending: number;
|
|
51
|
+
completed: number;
|
|
52
|
+
expiry_time: number | null;
|
|
53
|
+
}>;
|
|
54
|
+
listRegistrationTokens(): Promise<Row[]>;
|
|
55
|
+
deleteRegistrationToken(token: string): Promise<void>;
|
|
56
|
+
listRooms(opts?: {
|
|
57
|
+
from?: number;
|
|
58
|
+
limit?: number;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
rooms: Record<string, any>[];
|
|
61
|
+
total: number;
|
|
62
|
+
}>;
|
|
63
|
+
/** Remove everyone from a room, delist it and drop its aliases. With `purge`, also delete its history. */
|
|
64
|
+
deleteRoom(roomId: string, opts?: {
|
|
65
|
+
actor?: string;
|
|
66
|
+
reason?: string;
|
|
67
|
+
purge?: boolean;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
kicked: string[];
|
|
70
|
+
}>;
|
|
71
|
+
deleteMedia(mediaId: string): Promise<void>;
|
|
72
|
+
/** Delete local media uploaded before `beforeTs`. Returns the deleted media IDs. */
|
|
73
|
+
purgeMediaBefore(beforeTs: number): Promise<string[]>;
|
|
74
|
+
get noticeUserId(): string;
|
|
75
|
+
/** Whether `roomId` is a server notices room (members may not reject or leave its invites). */
|
|
76
|
+
isNoticeRoom(roomId: string): Promise<boolean>;
|
|
77
|
+
/** Send a message to a user in their server notices room, creating or re-inviting as needed. */
|
|
78
|
+
sendServerNotice(userId: string, content: Record<string, any>, opts?: {
|
|
79
|
+
type?: string;
|
|
80
|
+
txnId?: string;
|
|
81
|
+
}): Promise<string>;
|
|
82
|
+
private noticeRoomFor;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|