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
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { isObject } from "./json.js";
|
|
2
|
+
import { UNSTABLE } from "./m.js";
|
|
3
|
+
import { errors } from "./errors.js";
|
|
4
|
+
//#region src/core/keys.ts
|
|
5
|
+
/** End-to-end encryption key storage (device keys, one-time keys, cross-signing). */
|
|
6
|
+
var KeyService = class {
|
|
7
|
+
hs;
|
|
8
|
+
constructor(hs) {
|
|
9
|
+
this.hs = hs;
|
|
10
|
+
}
|
|
11
|
+
/** A local user's devices or device keys changed: update local clients and remote servers. */
|
|
12
|
+
async deviceChanged(userId, deletedDeviceId) {
|
|
13
|
+
await this.markDeviceListChanged(userId);
|
|
14
|
+
await this.hs.federation.onDeviceListChange(userId, deletedDeviceId).catch((err) => this.hs.logger.warn("Device list EDU failed", err));
|
|
15
|
+
}
|
|
16
|
+
async markDeviceListChanged(userId, notify = true) {
|
|
17
|
+
await this.hs.streams.write((stream) => this.hs.db.upsert("device_list_changes", {
|
|
18
|
+
user_id: userId,
|
|
19
|
+
stream
|
|
20
|
+
}));
|
|
21
|
+
if (notify) this.hs.notifier.notify(await this.hs.rooms.usersSharingRooms(userId));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A partial-state room got its full state (MSC3902): device list updates received meanwhile for
|
|
25
|
+
* users we could not place count as changes now, if those users turned out to be members.
|
|
26
|
+
*/
|
|
27
|
+
async onPartialStateComplete(roomId, pendingUsers) {
|
|
28
|
+
const members = new Set(await this.hs.rooms.joinedMembers(roomId));
|
|
29
|
+
for (const userId of pendingUsers) if (members.has(userId)) await this.markDeviceListChanged(userId, false);
|
|
30
|
+
this.hs.notifier.notify([...members].filter((u) => this.hs.isLocalUser(u)));
|
|
31
|
+
}
|
|
32
|
+
async upload(userId, deviceId, body) {
|
|
33
|
+
if (body.device_keys !== void 0) {
|
|
34
|
+
const dk = body.device_keys;
|
|
35
|
+
if (!isObject(dk) || dk.user_id !== userId || dk.device_id !== deviceId) throw errors.invalidParam("device_keys do not match this device");
|
|
36
|
+
if (!Array.isArray(dk.algorithms) || !isObject(dk.keys) || !isObject(dk.signatures)) throw errors.badJson("device_keys must contain algorithms, keys and signatures");
|
|
37
|
+
const existing = await this.hs.db.findOne("device_keys", [{
|
|
38
|
+
field: "user_id",
|
|
39
|
+
value: userId
|
|
40
|
+
}, {
|
|
41
|
+
field: "device_id",
|
|
42
|
+
value: deviceId
|
|
43
|
+
}]);
|
|
44
|
+
if (!existing || JSON.stringify(existing.keys) !== JSON.stringify(dk)) {
|
|
45
|
+
await this.hs.streams.write((stream) => this.hs.db.upsert("device_keys", {
|
|
46
|
+
user_id: userId,
|
|
47
|
+
device_id: deviceId,
|
|
48
|
+
keys: dk,
|
|
49
|
+
stream
|
|
50
|
+
}));
|
|
51
|
+
await this.deviceChanged(userId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const device = [{
|
|
55
|
+
field: "user_id",
|
|
56
|
+
value: userId
|
|
57
|
+
}, {
|
|
58
|
+
field: "device_id",
|
|
59
|
+
value: deviceId
|
|
60
|
+
}];
|
|
61
|
+
if (isObject(body.one_time_keys)) for (const [fullId, key] of Object.entries(body.one_time_keys)) {
|
|
62
|
+
const [algorithm] = fullId.split(":");
|
|
63
|
+
const existing = await this.hs.db.findOne("one_time_keys", [...device, {
|
|
64
|
+
field: "key_id",
|
|
65
|
+
value: fullId
|
|
66
|
+
}]);
|
|
67
|
+
if (existing) {
|
|
68
|
+
if (JSON.stringify(existing.key) !== JSON.stringify(key)) throw errors.invalidParam(`One time key ${fullId} already exists`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
await this.hs.streams.write((seq) => this.hs.db.create("one_time_keys", {
|
|
72
|
+
user_id: userId,
|
|
73
|
+
device_id: deviceId,
|
|
74
|
+
key_id: fullId,
|
|
75
|
+
algorithm,
|
|
76
|
+
key,
|
|
77
|
+
fallback: false,
|
|
78
|
+
used: false,
|
|
79
|
+
seq
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
const fallbackKeys = body.fallback_keys ?? body[UNSTABLE.ORG.MATRIX.MSC2732.FALLBACK_KEYS];
|
|
83
|
+
if (isObject(fallbackKeys)) for (const [fullId, key] of Object.entries(fallbackKeys)) {
|
|
84
|
+
const [algorithm] = fullId.split(":");
|
|
85
|
+
const existing = await this.hs.db.findOne("fallback_keys", [...device, {
|
|
86
|
+
field: "algorithm",
|
|
87
|
+
value: algorithm
|
|
88
|
+
}]);
|
|
89
|
+
if (existing && existing.key_id === fullId && JSON.stringify(existing.key) === JSON.stringify(key)) continue;
|
|
90
|
+
await this.hs.db.upsert("fallback_keys", {
|
|
91
|
+
user_id: userId,
|
|
92
|
+
device_id: deviceId,
|
|
93
|
+
algorithm,
|
|
94
|
+
key_id: fullId,
|
|
95
|
+
key,
|
|
96
|
+
used: false
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return this.oneTimeKeyCounts(userId, deviceId);
|
|
100
|
+
}
|
|
101
|
+
async oneTimeKeyCounts(userId, deviceId) {
|
|
102
|
+
const rows = await this.hs.db.findMany("one_time_keys", { where: [
|
|
103
|
+
{
|
|
104
|
+
field: "user_id",
|
|
105
|
+
value: userId
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
field: "device_id",
|
|
109
|
+
value: deviceId
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
field: "fallback",
|
|
113
|
+
value: false
|
|
114
|
+
}
|
|
115
|
+
] });
|
|
116
|
+
const counts = { signed_curve25519: 0 };
|
|
117
|
+
for (const r of rows) counts[r.algorithm] = (counts[r.algorithm] ?? 0) + 1;
|
|
118
|
+
return counts;
|
|
119
|
+
}
|
|
120
|
+
async unusedFallbackKeyTypes(userId, deviceId) {
|
|
121
|
+
const rows = await this.hs.db.findMany("fallback_keys", { where: [
|
|
122
|
+
{
|
|
123
|
+
field: "user_id",
|
|
124
|
+
value: userId
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
field: "device_id",
|
|
128
|
+
value: deviceId
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
field: "used",
|
|
132
|
+
value: false
|
|
133
|
+
}
|
|
134
|
+
] });
|
|
135
|
+
return [...new Set(rows.map((r) => r.algorithm))];
|
|
136
|
+
}
|
|
137
|
+
async query(requester, deviceKeys, timeoutMs = 1e4) {
|
|
138
|
+
const result = {
|
|
139
|
+
device_keys: {},
|
|
140
|
+
failures: {},
|
|
141
|
+
master_keys: {},
|
|
142
|
+
self_signing_keys: {},
|
|
143
|
+
user_signing_keys: {}
|
|
144
|
+
};
|
|
145
|
+
const remote = {};
|
|
146
|
+
for (const [userId, devices] of Object.entries(deviceKeys ?? {})) {
|
|
147
|
+
if (!this.hs.isLocalUser(userId)) {
|
|
148
|
+
remote[userId] = devices;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const rows = await this.hs.db.findMany("device_keys", { where: [{
|
|
152
|
+
field: "user_id",
|
|
153
|
+
value: userId
|
|
154
|
+
}] });
|
|
155
|
+
const deviceRows = await this.hs.db.findMany("devices", { where: [{
|
|
156
|
+
field: "user_id",
|
|
157
|
+
value: userId
|
|
158
|
+
}] });
|
|
159
|
+
const names = new Map(deviceRows.map((d) => [d.device_id, d.display_name]));
|
|
160
|
+
const wanted = Array.isArray(devices) && devices.length ? new Set(devices) : null;
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const row of rows) {
|
|
163
|
+
if (wanted && !wanted.has(row.device_id)) continue;
|
|
164
|
+
if (!names.has(row.device_id)) continue;
|
|
165
|
+
const keys = await this.withSignatures(userId, `${row.device_id}`, row.keys);
|
|
166
|
+
const name = names.get(row.device_id);
|
|
167
|
+
keys.unsigned = {
|
|
168
|
+
...keys.unsigned,
|
|
169
|
+
...name ? { device_display_name: name } : {}
|
|
170
|
+
};
|
|
171
|
+
out[row.device_id] = keys;
|
|
172
|
+
}
|
|
173
|
+
result.device_keys[userId] = out;
|
|
174
|
+
const csRows = await this.hs.db.findMany("cross_signing_keys", { where: [{
|
|
175
|
+
field: "user_id",
|
|
176
|
+
value: userId
|
|
177
|
+
}] });
|
|
178
|
+
for (const cs of csRows) {
|
|
179
|
+
if (cs.key_type === "user_signing" && userId !== requester) continue;
|
|
180
|
+
const pub = Object.values(cs.key.keys ?? {})[0];
|
|
181
|
+
result[`${cs.key_type}_keys`][userId] = await this.withSignatures(userId, pub, cs.key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(remote).length && requester) {
|
|
185
|
+
const fetch = {};
|
|
186
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
187
|
+
for (const [userId, devices] of Object.entries(remote)) {
|
|
188
|
+
if (!await this.isTracked(userId)) {
|
|
189
|
+
await this.dropRemoteCache(userId);
|
|
190
|
+
fetch[userId] = devices;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
tracked.add(userId);
|
|
194
|
+
const list = await this.hs.db.findOne("remote_device_lists", [{
|
|
195
|
+
field: "user_id",
|
|
196
|
+
value: userId
|
|
197
|
+
}]);
|
|
198
|
+
if (!list || list.stale) {
|
|
199
|
+
fetch[userId] = [];
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
await this.fromRemoteCache(userId, devices, result);
|
|
203
|
+
}
|
|
204
|
+
if (Object.keys(fetch).length) {
|
|
205
|
+
const { results, failures } = await this.hs.federation.queryKeys(fetch, timeoutMs);
|
|
206
|
+
Object.assign(result.failures, failures);
|
|
207
|
+
const fetched = {
|
|
208
|
+
device_keys: {},
|
|
209
|
+
master_keys: {},
|
|
210
|
+
self_signing_keys: {}
|
|
211
|
+
};
|
|
212
|
+
for (const res of results) for (const kind of [
|
|
213
|
+
"device_keys",
|
|
214
|
+
"master_keys",
|
|
215
|
+
"self_signing_keys"
|
|
216
|
+
]) for (const [userId, value] of Object.entries(isObject(res[kind]) ? res[kind] : {})) if (fetch[userId] !== void 0) fetched[kind][userId] = value;
|
|
217
|
+
for (const userId of Object.keys(fetch)) {
|
|
218
|
+
if (tracked.has(userId) && isObject(fetched.device_keys[userId])) {
|
|
219
|
+
await this.storeRemoteCache(userId, fetched.device_keys[userId], fetched.master_keys[userId], fetched.self_signing_keys[userId]);
|
|
220
|
+
await this.fromRemoteCache(userId, remote[userId], result);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
for (const kind of [
|
|
224
|
+
"device_keys",
|
|
225
|
+
"master_keys",
|
|
226
|
+
"self_signing_keys"
|
|
227
|
+
]) if (fetched[kind][userId] !== void 0) result[kind][userId] = fetched[kind][userId];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
/** Whether a remote user shares a room with a local user (by known membership). */
|
|
234
|
+
async isTracked(userId) {
|
|
235
|
+
const rows = await this.hs.db.findMany("memberships", { where: [{
|
|
236
|
+
field: "user_id",
|
|
237
|
+
value: userId
|
|
238
|
+
}, {
|
|
239
|
+
field: "membership",
|
|
240
|
+
value: "join"
|
|
241
|
+
}] });
|
|
242
|
+
for (const row of rows) if ((await this.hs.rooms.joinedMembers(row.room_id)).some((u) => this.hs.isLocalUser(u))) return true;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
async dropRemoteCache(userId) {
|
|
246
|
+
await this.hs.db.delete("remote_devices", [{
|
|
247
|
+
field: "user_id",
|
|
248
|
+
value: userId
|
|
249
|
+
}]);
|
|
250
|
+
await this.hs.db.delete("remote_device_lists", [{
|
|
251
|
+
field: "user_id",
|
|
252
|
+
value: userId
|
|
253
|
+
}]);
|
|
254
|
+
}
|
|
255
|
+
/** A tracked user's device list changed: refetch it on the next query. */
|
|
256
|
+
async markRemoteStale(userId) {
|
|
257
|
+
if (await this.isTracked(userId)) await this.hs.db.upsert("remote_device_lists", {
|
|
258
|
+
user_id: userId,
|
|
259
|
+
stream_id: 0,
|
|
260
|
+
stale: true
|
|
261
|
+
});
|
|
262
|
+
else await this.dropRemoteCache(userId);
|
|
263
|
+
}
|
|
264
|
+
async storeRemoteCache(userId, devices, master, selfSigning) {
|
|
265
|
+
await this.hs.db.delete("remote_devices", [{
|
|
266
|
+
field: "user_id",
|
|
267
|
+
value: userId
|
|
268
|
+
}]);
|
|
269
|
+
for (const [deviceId, keys] of Object.entries(devices)) {
|
|
270
|
+
if (!isObject(keys)) continue;
|
|
271
|
+
const name = keys.unsigned?.device_display_name;
|
|
272
|
+
await this.hs.db.upsert("remote_devices", {
|
|
273
|
+
user_id: userId,
|
|
274
|
+
device_id: deviceId,
|
|
275
|
+
keys,
|
|
276
|
+
display_name: typeof name === "string" ? name : null,
|
|
277
|
+
stream_id: 0
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
for (const [type, key] of [["master", master], ["self_signing", selfSigning]]) if (isObject(key)) await this.hs.streams.write((stream) => this.hs.db.upsert("cross_signing_keys", {
|
|
281
|
+
user_id: userId,
|
|
282
|
+
key_type: type,
|
|
283
|
+
key,
|
|
284
|
+
stream
|
|
285
|
+
}));
|
|
286
|
+
await this.hs.db.upsert("remote_device_lists", {
|
|
287
|
+
user_id: userId,
|
|
288
|
+
stream_id: 0,
|
|
289
|
+
stale: false
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async fromRemoteCache(userId, devices, result) {
|
|
293
|
+
const wanted = Array.isArray(devices) && devices.length ? new Set(devices) : null;
|
|
294
|
+
const out = {};
|
|
295
|
+
for (const row of await this.hs.db.findMany("remote_devices", { where: [{
|
|
296
|
+
field: "user_id",
|
|
297
|
+
value: userId
|
|
298
|
+
}] })) if (!wanted || wanted.has(row.device_id)) out[row.device_id] = row.keys;
|
|
299
|
+
result.device_keys[userId] = out;
|
|
300
|
+
for (const cs of await this.hs.db.findMany("cross_signing_keys", { where: [{
|
|
301
|
+
field: "user_id",
|
|
302
|
+
value: userId
|
|
303
|
+
}] })) if (cs.key_type === "master" || cs.key_type === "self_signing") result[`${cs.key_type}_keys`][userId] = cs.key;
|
|
304
|
+
}
|
|
305
|
+
async withSignatures(userId, keyId, key) {
|
|
306
|
+
const sigs = await this.hs.db.findMany("key_signatures", { where: [{
|
|
307
|
+
field: "user_id",
|
|
308
|
+
value: userId
|
|
309
|
+
}, {
|
|
310
|
+
field: "key_id",
|
|
311
|
+
value: keyId
|
|
312
|
+
}] });
|
|
313
|
+
if (sigs.length === 0) return structuredClone(key);
|
|
314
|
+
const out = structuredClone(key);
|
|
315
|
+
out.signatures ??= {};
|
|
316
|
+
for (const s of sigs) out.signatures[s.signer] = {
|
|
317
|
+
...out.signatures[s.signer],
|
|
318
|
+
[s.signer_key_id]: s.signature
|
|
319
|
+
};
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
async claim(request) {
|
|
323
|
+
const result = {};
|
|
324
|
+
for (const [userId, devices] of Object.entries(request ?? {})) for (const [deviceId, algorithm] of Object.entries(devices ?? {})) {
|
|
325
|
+
const base = [
|
|
326
|
+
{
|
|
327
|
+
field: "user_id",
|
|
328
|
+
value: userId
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
field: "device_id",
|
|
332
|
+
value: deviceId
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
field: "algorithm",
|
|
336
|
+
value: algorithm
|
|
337
|
+
}
|
|
338
|
+
];
|
|
339
|
+
let key = (await this.hs.db.findMany("one_time_keys", {
|
|
340
|
+
where: [...base, {
|
|
341
|
+
field: "fallback",
|
|
342
|
+
value: false
|
|
343
|
+
}],
|
|
344
|
+
sortBy: {
|
|
345
|
+
field: "seq",
|
|
346
|
+
direction: "asc"
|
|
347
|
+
},
|
|
348
|
+
limit: 1
|
|
349
|
+
}))[0] ?? null;
|
|
350
|
+
if (key) await this.hs.db.delete("one_time_keys", [...base, {
|
|
351
|
+
field: "key_id",
|
|
352
|
+
value: key.key_id
|
|
353
|
+
}]);
|
|
354
|
+
else {
|
|
355
|
+
key = await this.hs.db.findOne("fallback_keys", base);
|
|
356
|
+
if (key && !key.used) await this.hs.db.update("fallback_keys", base, { used: true });
|
|
357
|
+
}
|
|
358
|
+
if (!key) continue;
|
|
359
|
+
result[userId] ??= {};
|
|
360
|
+
result[userId][deviceId] = { [key.key_id]: key.key };
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
async uploadCrossSigning(userId, body) {
|
|
365
|
+
for (const type of [
|
|
366
|
+
"master",
|
|
367
|
+
"self_signing",
|
|
368
|
+
"user_signing"
|
|
369
|
+
]) {
|
|
370
|
+
const key = body[`${type}_key`];
|
|
371
|
+
if (key === void 0) continue;
|
|
372
|
+
if (!isObject(key) || key.user_id !== userId || !isObject(key.keys)) throw errors.invalidParam(`Invalid ${type}_key`);
|
|
373
|
+
await this.hs.streams.write((stream) => this.hs.db.upsert("cross_signing_keys", {
|
|
374
|
+
user_id: userId,
|
|
375
|
+
key_type: type,
|
|
376
|
+
key,
|
|
377
|
+
stream
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
await this.markDeviceListChanged(userId);
|
|
381
|
+
await this.hs.federation.onSigningKeyUpdate(userId).catch(() => {});
|
|
382
|
+
}
|
|
383
|
+
async uploadSignatures(signer, body) {
|
|
384
|
+
const failures = {};
|
|
385
|
+
const changed = /* @__PURE__ */ new Set();
|
|
386
|
+
for (const [userId, keys] of Object.entries(body ?? {})) {
|
|
387
|
+
if (!isObject(keys)) continue;
|
|
388
|
+
for (const [keyId, signed] of Object.entries(keys)) {
|
|
389
|
+
const sigs = signed?.signatures?.[signer];
|
|
390
|
+
if (!isObject(sigs)) {
|
|
391
|
+
failures[userId] = {
|
|
392
|
+
...failures[userId],
|
|
393
|
+
[keyId]: {
|
|
394
|
+
errcode: "M_INVALID_SIGNATURE",
|
|
395
|
+
error: "No signature from signer"
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
for (const [signerKeyId, signature] of Object.entries(sigs)) await this.hs.db.upsert("key_signatures", {
|
|
401
|
+
user_id: userId,
|
|
402
|
+
key_id: keyId,
|
|
403
|
+
signer,
|
|
404
|
+
signer_key_id: signerKeyId,
|
|
405
|
+
signature: String(signature)
|
|
406
|
+
});
|
|
407
|
+
changed.add(userId);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const u of changed) if (this.hs.isLocalUser(u)) await this.deviceChanged(u);
|
|
411
|
+
else await this.markDeviceListChanged(u);
|
|
412
|
+
if (changed.has(signer)) await this.hs.federation.onSigningKeyUpdate(signer).catch(() => {});
|
|
413
|
+
return { failures };
|
|
414
|
+
}
|
|
415
|
+
/** Users (sharing a room with `userId`) whose device lists changed after `since`. */
|
|
416
|
+
async changesSince(userId, since, upTo) {
|
|
417
|
+
const sharing = await this.hs.rooms.usersSharingRooms(userId);
|
|
418
|
+
const rows = await this.hs.db.findMany("device_list_changes", { where: [{
|
|
419
|
+
field: "stream",
|
|
420
|
+
operator: "gt",
|
|
421
|
+
value: since
|
|
422
|
+
}] });
|
|
423
|
+
const changed = /* @__PURE__ */ new Set();
|
|
424
|
+
for (const r of rows) if (r.stream <= upTo && sharing.has(r.user_id)) changed.add(r.user_id);
|
|
425
|
+
const left = /* @__PURE__ */ new Set();
|
|
426
|
+
const memberRows = await this.hs.db.findMany("memberships", { where: [{
|
|
427
|
+
field: "stream",
|
|
428
|
+
operator: "gt",
|
|
429
|
+
value: since
|
|
430
|
+
}] });
|
|
431
|
+
const myRooms = /* @__PURE__ */ new Set([...await this.hs.rooms.roomsForUser(userId), ...memberRows.filter((m) => m.user_id === userId).map((m) => m.room_id)]);
|
|
432
|
+
for (const m of memberRows) {
|
|
433
|
+
if (m.stream > upTo || !myRooms.has(m.room_id)) continue;
|
|
434
|
+
if (m.user_id === userId) {
|
|
435
|
+
const members = await this.hs.rooms.joinedMembers(m.room_id);
|
|
436
|
+
if (m.membership === "join") {
|
|
437
|
+
for (const u of members) if (u !== userId) changed.add(u);
|
|
438
|
+
} else for (const u of members) if (!sharing.has(u)) left.add(u);
|
|
439
|
+
} else if (m.membership === "join") changed.add(m.user_id);
|
|
440
|
+
else if (!sharing.has(m.user_id)) left.add(m.user_id);
|
|
441
|
+
}
|
|
442
|
+
for (const u of left) changed.delete(u);
|
|
443
|
+
return {
|
|
444
|
+
changed: [...changed],
|
|
445
|
+
left: [...left]
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
//#endregion
|
|
450
|
+
export { KeyService };
|
package/dist/core/m.d.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
//#region src/core/m.d.ts
|
|
2
|
+
export declare const M: {
|
|
3
|
+
readonly BEACON_INFO: "m.beacon_info";
|
|
4
|
+
readonly CALL: {
|
|
5
|
+
readonly INVITE: "m.call.invite";
|
|
6
|
+
};
|
|
7
|
+
readonly CHANGE_PASSWORD: "m.change_password";
|
|
8
|
+
readonly DEVICE_LIST_UPDATE: "m.device_list_update";
|
|
9
|
+
readonly DIRECT: "m.direct";
|
|
10
|
+
readonly DIRECT_TO_DEVICE: "m.direct_to_device";
|
|
11
|
+
readonly FEDERATE: "m.federate";
|
|
12
|
+
readonly FULLY_READ: "m.fully_read";
|
|
13
|
+
readonly GET_LOGIN_TOKEN: "m.get_login_token";
|
|
14
|
+
readonly HEROES: "m.heroes";
|
|
15
|
+
readonly HOMESERVER: "m.homeserver";
|
|
16
|
+
readonly ID: {
|
|
17
|
+
readonly PHONE: "m.id.phone";
|
|
18
|
+
readonly THIRDPARTY: "m.id.thirdparty";
|
|
19
|
+
readonly USER: "m.id.user";
|
|
20
|
+
};
|
|
21
|
+
readonly IGNORED_USER_LIST: "m.ignored_user_list";
|
|
22
|
+
readonly IN_REPLY_TO: "m.in_reply_to";
|
|
23
|
+
readonly INVITED_MEMBER_COUNT: "m.invited_member_count";
|
|
24
|
+
readonly JOINED_MEMBER_COUNT: "m.joined_member_count";
|
|
25
|
+
readonly LOGIN: {
|
|
26
|
+
readonly APPLICATION_SERVICE: "m.login.application_service";
|
|
27
|
+
readonly DUMMY: "m.login.dummy";
|
|
28
|
+
readonly EMAIL: {
|
|
29
|
+
readonly IDENTITY: "m.login.email.identity";
|
|
30
|
+
};
|
|
31
|
+
readonly MSISDN: "m.login.msisdn";
|
|
32
|
+
readonly PASSWORD: "m.login.password";
|
|
33
|
+
readonly REGISTRATION_TOKEN: "m.login.registration_token";
|
|
34
|
+
readonly SSO: "m.login.sso";
|
|
35
|
+
readonly TOKEN: "m.login.token";
|
|
36
|
+
};
|
|
37
|
+
readonly NOTICE: "m.notice";
|
|
38
|
+
readonly OAUTH: "m.oauth";
|
|
39
|
+
readonly POLL: {
|
|
40
|
+
readonly START: "m.poll.start";
|
|
41
|
+
};
|
|
42
|
+
readonly PRESENCE: "m.presence";
|
|
43
|
+
readonly PROFILE_FIELDS: "m.profile_fields";
|
|
44
|
+
readonly PUSH_RULES: "m.push_rules";
|
|
45
|
+
readonly REACTION: "m.reaction";
|
|
46
|
+
readonly READ: {
|
|
47
|
+
readonly _: "m.read";
|
|
48
|
+
readonly PRIVATE: "m.read.private";
|
|
49
|
+
};
|
|
50
|
+
readonly RECEIPT: "m.receipt";
|
|
51
|
+
readonly RELATES_TO: "m.relates_to";
|
|
52
|
+
readonly RELATIONS: "m.relations";
|
|
53
|
+
readonly RELATIONSHIP: "m.relationship";
|
|
54
|
+
readonly REPLACE: "m.replace";
|
|
55
|
+
readonly ROLE: {
|
|
56
|
+
readonly ADMIN: "m.role.admin";
|
|
57
|
+
readonly SECURITY: "m.role.security";
|
|
58
|
+
};
|
|
59
|
+
readonly ROOM: {
|
|
60
|
+
readonly ALIASES: "m.room.aliases";
|
|
61
|
+
readonly AVATAR: "m.room.avatar";
|
|
62
|
+
readonly CANONICAL_ALIAS: "m.room.canonical_alias";
|
|
63
|
+
readonly CREATE: "m.room.create";
|
|
64
|
+
readonly ENCRYPTED: "m.room.encrypted";
|
|
65
|
+
readonly ENCRYPTION: "m.room.encryption";
|
|
66
|
+
readonly GUEST_ACCESS: "m.room.guest_access";
|
|
67
|
+
readonly HISTORY_VISIBILITY: "m.room.history_visibility";
|
|
68
|
+
readonly JOIN_RULES: "m.room.join_rules";
|
|
69
|
+
readonly MEMBER: "m.room.member";
|
|
70
|
+
readonly MESSAGE: "m.room.message";
|
|
71
|
+
readonly NAME: "m.room.name";
|
|
72
|
+
readonly POLICY: "m.room.policy";
|
|
73
|
+
readonly POWER_LEVELS: "m.room.power_levels";
|
|
74
|
+
readonly REDACTION: "m.room.redaction";
|
|
75
|
+
readonly SERVER_ACL: "m.room.server_acl";
|
|
76
|
+
readonly THIRD_PARTY_INVITE: "m.room.third_party_invite";
|
|
77
|
+
readonly TOMBSTONE: "m.room.tombstone";
|
|
78
|
+
readonly TOPIC: "m.room.topic";
|
|
79
|
+
};
|
|
80
|
+
readonly ROOM_MEMBERSHIP: "m.room_membership";
|
|
81
|
+
readonly ROOM_VERSIONS: "m.room_versions";
|
|
82
|
+
readonly RULE: {
|
|
83
|
+
readonly CALL: ".m.rule.call";
|
|
84
|
+
readonly CONTAINS_DISPLAY_NAME: ".m.rule.contains_display_name";
|
|
85
|
+
readonly CONTAINS_USER_NAME: ".m.rule.contains_user_name";
|
|
86
|
+
readonly ENCRYPTED: ".m.rule.encrypted";
|
|
87
|
+
readonly ENCRYPTED_ROOM_ONE_TO_ONE: ".m.rule.encrypted_room_one_to_one";
|
|
88
|
+
readonly INVITE_FOR_ME: ".m.rule.invite_for_me";
|
|
89
|
+
readonly IS_ROOM_MENTION: ".m.rule.is_room_mention";
|
|
90
|
+
readonly IS_USER_MENTION: ".m.rule.is_user_mention";
|
|
91
|
+
readonly MASTER: ".m.rule.master";
|
|
92
|
+
readonly MEMBER_EVENT: ".m.rule.member_event";
|
|
93
|
+
readonly MESSAGE: ".m.rule.message";
|
|
94
|
+
readonly REACTION: ".m.rule.reaction";
|
|
95
|
+
readonly ROOM: {
|
|
96
|
+
readonly SERVER_ACL: ".m.rule.room.server_acl";
|
|
97
|
+
};
|
|
98
|
+
readonly ROOM_ONE_TO_ONE: ".m.rule.room_one_to_one";
|
|
99
|
+
readonly ROOMNOTIF: ".m.rule.roomnotif";
|
|
100
|
+
readonly SUPPRESS_EDITS: ".m.rule.suppress_edits";
|
|
101
|
+
readonly SUPPRESS_NOTICES: ".m.rule.suppress_notices";
|
|
102
|
+
readonly TOMBSTONE: ".m.rule.tombstone";
|
|
103
|
+
};
|
|
104
|
+
readonly SERVER: "m.server";
|
|
105
|
+
readonly SERVER_NOTICE: "m.server_notice";
|
|
106
|
+
readonly SET_AVATAR_URL: "m.set_avatar_url";
|
|
107
|
+
readonly SET_DISPLAYNAME: "m.set_displayname";
|
|
108
|
+
readonly SIGNING_KEY_UPDATE: "m.signing_key_update";
|
|
109
|
+
readonly SPACE: {
|
|
110
|
+
readonly _: "m.space";
|
|
111
|
+
readonly CHILD: "m.space.child";
|
|
112
|
+
};
|
|
113
|
+
readonly STICKER: "m.sticker";
|
|
114
|
+
readonly TAG: "m.tag";
|
|
115
|
+
readonly TEXT: "m.text";
|
|
116
|
+
readonly THREAD: "m.thread";
|
|
117
|
+
readonly THREEPID_CHANGES: "m.3pid_changes";
|
|
118
|
+
readonly TOPIC: "m.topic";
|
|
119
|
+
readonly TYPING: "m.typing";
|
|
120
|
+
readonly UPLOAD: {
|
|
121
|
+
readonly SIZE: "m.upload.size";
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
export declare const UNSTABLE: {
|
|
125
|
+
readonly DE: {
|
|
126
|
+
readonly SORUNOME: {
|
|
127
|
+
readonly MSC2409: {
|
|
128
|
+
readonly EPHEMERAL: "de.sorunome.msc2409.ephemeral";
|
|
129
|
+
readonly PUSH_EPHEMERAL: "de.sorunome.msc2409.push_ephemeral";
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
readonly IO: {
|
|
134
|
+
readonly ELEMENT: {
|
|
135
|
+
readonly MSC4306: {
|
|
136
|
+
readonly RULE: {
|
|
137
|
+
readonly SUBSCRIBED_THREAD: ".io.element.msc4306.rule.subscribed_thread";
|
|
138
|
+
readonly UNSUBSCRIBED_THREAD: ".io.element.msc4306.rule.unsubscribed_thread";
|
|
139
|
+
};
|
|
140
|
+
readonly THREAD_SUBSCRIPTION: "io.element.msc4306.thread_subscription";
|
|
141
|
+
};
|
|
142
|
+
readonly MSC4308: {
|
|
143
|
+
readonly THREAD_SUBSCRIPTIONS: "io.element.msc4308.thread_subscriptions";
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
readonly ORG: {
|
|
148
|
+
readonly MATRIX: {
|
|
149
|
+
readonly CROSS_SIGNING_RESET: "org.matrix.cross_signing_reset";
|
|
150
|
+
readonly CUSTOM: {
|
|
151
|
+
readonly HTML: "org.matrix.custom.html";
|
|
152
|
+
};
|
|
153
|
+
readonly MSC2732: {
|
|
154
|
+
readonly FALLBACK_KEYS: "org.matrix.msc2732.fallback_keys";
|
|
155
|
+
};
|
|
156
|
+
readonly MSC3202: {
|
|
157
|
+
readonly DEVICE_ID: "org.matrix.msc3202.device_id";
|
|
158
|
+
};
|
|
159
|
+
readonly MSC3381: {
|
|
160
|
+
readonly POLL: {
|
|
161
|
+
readonly END: "org.matrix.msc3381.poll.end";
|
|
162
|
+
readonly RESPONSE: "org.matrix.msc3381.poll.response";
|
|
163
|
+
readonly START: "org.matrix.msc3381.poll.start";
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
readonly MSC3874: {
|
|
167
|
+
readonly NOT_REL_TYPES: "org.matrix.msc3874.not_rel_types";
|
|
168
|
+
readonly REL_TYPES: "org.matrix.msc3874.rel_types";
|
|
169
|
+
};
|
|
170
|
+
readonly MSC3916: {
|
|
171
|
+
readonly STABLE: "org.matrix.msc3916.stable";
|
|
172
|
+
};
|
|
173
|
+
readonly MSC3930: {
|
|
174
|
+
readonly RULE: {
|
|
175
|
+
readonly POLL_END: ".org.matrix.msc3930.rule.poll_end";
|
|
176
|
+
readonly POLL_END_ONE_TO_ONE: ".org.matrix.msc3930.rule.poll_end_one_to_one";
|
|
177
|
+
readonly POLL_RESPONSE: ".org.matrix.msc3930.rule.poll_response";
|
|
178
|
+
readonly POLL_START: ".org.matrix.msc3930.rule.poll_start";
|
|
179
|
+
readonly POLL_START_ONE_TO_ONE: ".org.matrix.msc3930.rule.poll_start_one_to_one";
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
readonly MSC4140: {
|
|
183
|
+
readonly _: "org.matrix.msc4140";
|
|
184
|
+
readonly DELAY: "org.matrix.msc4140.delay";
|
|
185
|
+
readonly MAX_DELAY: "org.matrix.msc4140.max_delay";
|
|
186
|
+
};
|
|
187
|
+
readonly MSC4143: {
|
|
188
|
+
readonly _: "org.matrix.msc4143";
|
|
189
|
+
readonly RTC_FOCI: "org.matrix.msc4143.rtc_foci";
|
|
190
|
+
};
|
|
191
|
+
readonly MSC4155: {
|
|
192
|
+
readonly ERRCODE: "org.matrix.msc4155.errcode";
|
|
193
|
+
readonly INVITE_PERMISSION_CONFIG: "org.matrix.msc4155.invite_permission_config";
|
|
194
|
+
};
|
|
195
|
+
readonly MSC4222: {
|
|
196
|
+
readonly _: "org.matrix.msc4222";
|
|
197
|
+
readonly STATE_AFTER: "org.matrix.msc4222.state_after";
|
|
198
|
+
readonly USE_STATE_AFTER: "org.matrix.msc4222.use_state_after";
|
|
199
|
+
};
|
|
200
|
+
readonly MSC4306: "org.matrix.msc4306";
|
|
201
|
+
readonly MSC4429: {
|
|
202
|
+
readonly PROFILE_FIELDS: "org.matrix.msc4429.profile_fields";
|
|
203
|
+
readonly USERS: "org.matrix.msc4429.users";
|
|
204
|
+
};
|
|
205
|
+
readonly SIMPLIFIED_MSC3575: "org.matrix.simplified_msc3575";
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|