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,186 @@
1
+ import { canonicalJson, isObject } from "./json.js";
2
+ import { randomString, sha256, signJson, unpaddedBase64, urlSafeBase64 } from "./crypto.js";
3
+ import { M } from "./m.js";
4
+ //#region src/core/events.ts
5
+ const STABLE_ROOM_VERSIONS = [
6
+ "1",
7
+ "2",
8
+ "3",
9
+ "4",
10
+ "5",
11
+ "6",
12
+ "7",
13
+ "8",
14
+ "9",
15
+ "10",
16
+ "11",
17
+ "12"
18
+ ];
19
+ /** Experimental versions: an MSC's changes on top of a stable version. */
20
+ const UNSTABLE_ROOM_VERSIONS = [
21
+ "org.matrix.msc3757.10",
22
+ "org.matrix.msc3757.11",
23
+ "org.matrix.msc3389.10"
24
+ ];
25
+ const SUPPORTED_ROOM_VERSIONS = [...STABLE_ROOM_VERSIONS, ...UNSTABLE_ROOM_VERSIONS];
26
+ /** The stable version an (experimental) room version is based on, e.g. "org.matrix.msc3757.10" → 10. */
27
+ const versionNum = (v) => {
28
+ let start = v.length;
29
+ while (start > 0 && v.charCodeAt(start - 1) >= 48 && v.charCodeAt(start - 1) <= 57) start--;
30
+ return Number.parseInt(start < v.length ? v.slice(start) : v, 10);
31
+ };
32
+ const roomVersionFeatures = (version) => {
33
+ const n = versionNum(version);
34
+ return {
35
+ /** v1/v2 use server-generated event IDs and `[id, hashes]` tuples in prev/auth events. */
36
+ legacyEventIds: n <= 2,
37
+ urlSafeEventIds: n >= 4,
38
+ knock: n >= 7,
39
+ restricted: n >= 8,
40
+ knockRestricted: n >= 10,
41
+ integerPowerLevels: n >= 10,
42
+ /** v11: creator is the sender of the create event; `redacts` moves into content. */
43
+ v11: n >= 11,
44
+ /**
45
+ * v12: room ID is the create event's reference hash, the create event has no room_id and is
46
+ * omitted from auth_events, and room creators have unlimited power.
47
+ */
48
+ v12: n >= 12,
49
+ /** MSC3757: state keys "@user:server[_suffix]" are owned by that user. */
50
+ ownedState: version.startsWith("org.matrix.msc3757."),
51
+ /** MSC3389: redactions keep m.relates_to. */
52
+ redactionKeepsRelations: version.startsWith("org.matrix.msc3389.")
53
+ };
54
+ };
55
+ const KEEP_TOP_LEVEL = [
56
+ "event_id",
57
+ "type",
58
+ "room_id",
59
+ "sender",
60
+ "state_key",
61
+ "content",
62
+ "hashes",
63
+ "signatures",
64
+ "depth",
65
+ "prev_events",
66
+ "auth_events",
67
+ "origin_server_ts"
68
+ ];
69
+ const KEEP_TOP_LEVEL_LEGACY = [
70
+ "prev_state",
71
+ "origin",
72
+ "membership"
73
+ ];
74
+ /** Apply the redaction algorithm for the given room version. */
75
+ function redact(event, roomVersion) {
76
+ const n = versionNum(roomVersion);
77
+ const keep = n >= 11 ? KEEP_TOP_LEVEL : [...KEEP_TOP_LEVEL, ...KEEP_TOP_LEVEL_LEGACY];
78
+ const out = {};
79
+ for (const k of keep) if (k in event) out[k] = event[k];
80
+ const keepRelation = roomVersion.startsWith("org.matrix.msc3389.") && isObject(event.content?.[M.RELATES_TO]) ? event.content[M.RELATES_TO] : void 0;
81
+ const content = isObject(event.content) ? event.content : {};
82
+ const pick = (...keys) => {
83
+ const c = {};
84
+ for (const k of keys) if (k in content) c[k] = content[k];
85
+ return c;
86
+ };
87
+ switch (event.type) {
88
+ case M.ROOM.MEMBER: {
89
+ const c = pick("membership", ...n >= 9 ? ["join_authorised_via_users_server"] : []);
90
+ if (n >= 11 && isObject(content.third_party_invite) && "signed" in content.third_party_invite) c.third_party_invite = { signed: content.third_party_invite.signed };
91
+ out.content = c;
92
+ break;
93
+ }
94
+ case M.ROOM.CREATE:
95
+ out.content = n >= 11 ? { ...content } : pick("creator");
96
+ break;
97
+ case M.ROOM.JOIN_RULES:
98
+ out.content = pick("join_rule", ...n >= 8 ? ["allow"] : []);
99
+ break;
100
+ case M.ROOM.POWER_LEVELS:
101
+ out.content = pick("ban", "events", "events_default", "kick", "redact", "state_default", "users", "users_default", ...n >= 11 ? ["invite"] : []);
102
+ break;
103
+ case M.ROOM.HISTORY_VISIBILITY:
104
+ out.content = pick("history_visibility");
105
+ break;
106
+ case M.ROOM.ALIASES:
107
+ out.content = n <= 5 ? pick("aliases") : {};
108
+ break;
109
+ case M.ROOM.REDACTION:
110
+ out.content = n >= 11 ? pick("redacts") : {};
111
+ break;
112
+ default: out.content = {};
113
+ }
114
+ if (keepRelation) {
115
+ const rel = {};
116
+ if ("rel_type" in keepRelation) rel.rel_type = keepRelation.rel_type;
117
+ if ("event_id" in keepRelation) rel.event_id = keepRelation.event_id;
118
+ out.content = {
119
+ ...out.content,
120
+ [M.RELATES_TO]: rel
121
+ };
122
+ }
123
+ return out;
124
+ }
125
+ function computeContentHash(pdu) {
126
+ const { unsigned, signatures, hashes, ...rest } = pdu;
127
+ return unpaddedBase64(sha256(canonicalJson(rest)));
128
+ }
129
+ function computeReferenceHash(pdu, roomVersion) {
130
+ const { signatures, unsigned, event_id, ...rest } = redact(pdu, roomVersion);
131
+ return sha256(canonicalJson(rest));
132
+ }
133
+ /** Finalise a PDU: compute hashes, signatures and the event ID. */
134
+ function finalisePdu(pdu, roomVersion, serverName, key) {
135
+ const features = roomVersionFeatures(roomVersion);
136
+ if (features.legacyEventIds) {
137
+ pdu.event_id = `$${randomString(18)}:${serverName}`;
138
+ pdu.origin = serverName;
139
+ }
140
+ pdu.hashes = { sha256: computeContentHash(pdu) };
141
+ const redacted = redact(pdu, roomVersion);
142
+ signJson(redacted, serverName, key);
143
+ pdu.signatures = redacted.signatures;
144
+ if (!features.legacyEventIds) {
145
+ const hash = computeReferenceHash(pdu, roomVersion);
146
+ pdu.event_id = `$${features.urlSafeEventIds ? urlSafeBase64(hash) : unpaddedBase64(hash)}`;
147
+ }
148
+ return pdu;
149
+ }
150
+ function eventIdOf(ref) {
151
+ return Array.isArray(ref) ? ref[0] : ref;
152
+ }
153
+ /** Convert a stored PDU into the client-server event format. */
154
+ function toClientEvent(pdu, opts = {}) {
155
+ const ev = {
156
+ event_id: pdu.event_id,
157
+ sender: pdu.sender,
158
+ type: pdu.type,
159
+ content: pdu.content,
160
+ origin_server_ts: pdu.origin_server_ts
161
+ };
162
+ if (opts.includeRoomId !== false) ev.room_id = pdu.room_id;
163
+ if (pdu.state_key !== void 0) ev.state_key = pdu.state_key;
164
+ if (pdu.redacts !== void 0) ev.redacts = pdu.redacts;
165
+ else if (pdu.type === M.ROOM.REDACTION && typeof pdu.content?.redacts === "string") ev.redacts = pdu.content.redacts;
166
+ ev.unsigned = {
167
+ ...pdu.unsigned,
168
+ age: Math.max(0, Date.now() - pdu.origin_server_ts)
169
+ };
170
+ return ev;
171
+ }
172
+ function stripState(pdu) {
173
+ return {
174
+ type: pdu.type,
175
+ state_key: pdu.state_key,
176
+ sender: pdu.sender,
177
+ content: pdu.content
178
+ };
179
+ }
180
+ const stateKey = (type, key) => `${type}\u0000${key}`;
181
+ const splitStateKey = (k) => {
182
+ const i = k.indexOf("\0");
183
+ return [k.slice(0, i), k.slice(i + 1)];
184
+ };
185
+ //#endregion
186
+ export { STABLE_ROOM_VERSIONS, SUPPORTED_ROOM_VERSIONS, UNSTABLE_ROOM_VERSIONS, computeContentHash, computeReferenceHash, eventIdOf, finalisePdu, redact, roomVersionFeatures, splitStateKey, stateKey, stripState, toClientEvent, versionNum };
@@ -0,0 +1,66 @@
1
+ //#region src/core/glob.ts
2
+ /**
3
+ * Glob matching (`*` = any run of characters, `?` = one character) in linear time.
4
+ *
5
+ * Security fix (https://github.com/k18k/matrix-nodejs-homeserver/issues/2 — ReDoS, CWE-1333): globs from untrusted input (m.room.server_acl entries from
6
+ * room state, MSC4155 invite rules from account data, event filters from clients) used to be
7
+ * compiled to regular expressions with `.*`. A pattern such as `****************!` made the
8
+ * regex engine backtrack exponentially: 16 asterisks blocked the event loop for ~110 s, so a
9
+ * room ACL could hang the whole homeserver, again after every restart. This matcher never
10
+ * backtracks more than once per character (classic two-pointer wildcard matching).
11
+ */
12
+ function globMatch(pattern, value, opts = {}) {
13
+ const p = opts.caseInsensitive ? pattern.toLowerCase() : pattern;
14
+ const v = opts.caseInsensitive ? value.toLowerCase() : value;
15
+ const anyOne = opts.questionMark ?? true;
16
+ let pi = 0, vi = 0;
17
+ let star = -1, mark = 0;
18
+ while (vi < v.length) if (pi < p.length && (p[pi] === v[vi] || anyOne && p[pi] === "?")) {
19
+ pi++;
20
+ vi++;
21
+ } else if (pi < p.length && p[pi] === "*") {
22
+ star = ++pi;
23
+ mark = vi;
24
+ } else if (star !== -1) {
25
+ pi = star;
26
+ vi = ++mark;
27
+ } else return false;
28
+ while (pi < p.length && p[pi] === "*") pi++;
29
+ return pi === p.length;
30
+ }
31
+ const isWordChar = (c) => c !== void 0 && /[\p{L}\p{N}_]/u.test(c);
32
+ /**
33
+ * Whether `pattern` (a glob) matches some run of `value` that starts and ends on a word boundary,
34
+ * as push rules match `content.body`. Case-insensitive.
35
+ *
36
+ * Security fix (https://github.com/k18k/matrix-nodejs-homeserver/issues/2 — ReDoS, CWE-1333): push rule patterns are set by any user and were compiled to
37
+ * `(^|\W)…(\W|$)` regexes with `.*?`, then run against every message in the user's rooms, so one
38
+ * account could hang the server. This simulates the glob as an automaton instead: the cost is
39
+ * O(value length × pattern length) whatever the input, with no backtracking.
40
+ */
41
+ function globSearchWords(pattern, value) {
42
+ const p = pattern.toLowerCase();
43
+ const v = value.toLowerCase();
44
+ const m = p.length;
45
+ let active = new Uint8Array(m + 1);
46
+ const close = (set) => {
47
+ for (let k = 0; k < m; k++) if (set[k] && p[k] === "*") set[k + 1] = 1;
48
+ };
49
+ for (let i = 0; i <= v.length; i++) {
50
+ if (!isWordChar(v[i - 1])) active[0] = 1;
51
+ close(active);
52
+ if (active[m] && !isWordChar(v[i])) return true;
53
+ if (i === v.length) break;
54
+ const next = new Uint8Array(m + 1);
55
+ const c = v[i];
56
+ for (let k = 0; k < m; k++) {
57
+ if (!active[k]) continue;
58
+ if (p[k] === "*") next[k] = 1;
59
+ else if (p[k] === "?" || p[k] === c) next[k + 1] = 1;
60
+ }
61
+ active = next;
62
+ }
63
+ return false;
64
+ }
65
+ //#endregion
66
+ export { globMatch, globSearchWords };
@@ -0,0 +1,24 @@
1
+ import { Homeserver } from "../homeserver.js";
2
+ //#region src/core/identity.d.ts
3
+ export interface StoredInvite {
4
+ token: string;
5
+ public_keys: {
6
+ public_key: string;
7
+ key_validity_url: string;
8
+ }[];
9
+ display_name: string;
10
+ }
11
+ export declare class IdentityServers {
12
+ private readonly hs;
13
+ constructor(hs: Homeserver);
14
+ private base;
15
+ private call;
16
+ /** The Matrix user ID bound to a 3PID, if any. */
17
+ lookup(idServer: string, token: string, medium: string, address: string): Promise<string | null>;
18
+ storeInvite(idServer: string, token: string, invite: Record<string, unknown>): Promise<StoredInvite>;
19
+ /** Returns the identity server's record of the binding (medium, address, mxid...). */
20
+ bind(idServer: string, token: string, sid: string, clientSecret: string, mxid: string): Promise<Record<string, any>>;
21
+ /** Remove a binding; resolves false when the identity server does not support unbinding. */
22
+ unbind(idServer: string, mxid: string, medium: string, address: string): Promise<boolean>;
23
+ }
24
+ //#endregion
@@ -0,0 +1,96 @@
1
+ import { MatrixError } from "./errors.js";
2
+ import { createHash } from "node:crypto";
3
+ //#region src/core/identity.ts
4
+ const urlSafe = (b) => b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5
+ var IdentityServers = class {
6
+ hs;
7
+ constructor(hs) {
8
+ this.hs = hs;
9
+ }
10
+ base(idServer) {
11
+ if (!/^[A-Za-z0-9.-]+(:\d+)?$/.test(idServer)) throw new MatrixError(400, "M_INVALID_PARAM", "Invalid id_server");
12
+ return `${this.hs.config.threepid.identityServerProtocol}://${idServer}`;
13
+ }
14
+ async call(idServer, method, path, opts = {}) {
15
+ const headers = { accept: "application/json" };
16
+ if (opts.body !== void 0) headers["content-type"] = "application/json";
17
+ if (opts.token) headers.authorization = `Bearer ${opts.token}`;
18
+ if (opts.signed) headers.authorization = this.hs.federation.client.authHeader(idServer, method, path, opts.body);
19
+ let res;
20
+ try {
21
+ res = await this.hs.fetch(`${this.base(idServer)}${path}`, {
22
+ method,
23
+ headers,
24
+ body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body),
25
+ signal: AbortSignal.timeout(15e3)
26
+ });
27
+ } catch (err) {
28
+ throw new MatrixError(502, "M_UNKNOWN", `Identity server ${idServer} unreachable: ${err.message}`);
29
+ }
30
+ const json = await res.json().catch(() => ({}));
31
+ if (!res.ok) throw new MatrixError(res.status >= 500 ? 502 : res.status, typeof json.errcode === "string" ? json.errcode : "M_UNKNOWN", typeof json.error === "string" ? json.error : `Identity server returned ${res.status}`);
32
+ return json;
33
+ }
34
+ /** The Matrix user ID bound to a 3PID, if any. */
35
+ async lookup(idServer, token, medium, address) {
36
+ const details = await this.call(idServer, "GET", "/_matrix/identity/v2/hash_details", { token });
37
+ const algorithms = Array.isArray(details.algorithms) ? details.algorithms : [];
38
+ let query, algorithm;
39
+ if (algorithms.includes("sha256") && typeof details.lookup_pepper === "string") {
40
+ algorithm = "sha256";
41
+ query = urlSafe(createHash("sha256").update(`${address} ${medium} ${details.lookup_pepper}`).digest());
42
+ } else if (algorithms.includes("none")) {
43
+ algorithm = "none";
44
+ query = `${address} ${medium}`;
45
+ } else throw new MatrixError(400, "M_UNKNOWN", "The identity server supports no known lookup algorithm");
46
+ const mxid = (await this.call(idServer, "POST", "/_matrix/identity/v2/lookup", {
47
+ token,
48
+ body: {
49
+ addresses: [query],
50
+ algorithm,
51
+ pepper: details.lookup_pepper ?? ""
52
+ }
53
+ })).mappings?.[query];
54
+ return typeof mxid === "string" ? mxid : null;
55
+ }
56
+ async storeInvite(idServer, token, invite) {
57
+ const res = await this.call(idServer, "POST", "/_matrix/identity/v2/store-invite", {
58
+ token,
59
+ body: invite
60
+ });
61
+ if (typeof res.token !== "string" || !Array.isArray(res.public_keys)) throw new MatrixError(502, "M_UNKNOWN", "Invalid store-invite response");
62
+ return res;
63
+ }
64
+ /** Returns the identity server's record of the binding (medium, address, mxid...). */
65
+ async bind(idServer, token, sid, clientSecret, mxid) {
66
+ return this.call(idServer, "POST", "/_matrix/identity/v2/3pid/bind", {
67
+ token,
68
+ body: {
69
+ sid,
70
+ client_secret: clientSecret,
71
+ mxid
72
+ }
73
+ });
74
+ }
75
+ /** Remove a binding; resolves false when the identity server does not support unbinding. */
76
+ async unbind(idServer, mxid, medium, address) {
77
+ try {
78
+ await this.call(idServer, "POST", "/_matrix/identity/v2/3pid/unbind", {
79
+ signed: true,
80
+ body: {
81
+ mxid,
82
+ threepid: {
83
+ medium,
84
+ address
85
+ }
86
+ }
87
+ });
88
+ return true;
89
+ } catch (err) {
90
+ if (err instanceof MatrixError && (err.status === 404 || err.status === 501 || err.errcode === "M_UNRECOGNIZED")) return false;
91
+ throw err;
92
+ }
93
+ }
94
+ };
95
+ //#endregion
96
+ export { IdentityServers };
@@ -0,0 +1,36 @@
1
+ import { randomString } from "./crypto.js";
2
+ //#region src/core/ids.ts
3
+ const LOCALPART_RE = /^[a-z0-9._=\-/+]+$/;
4
+ function parseId(id, sigil) {
5
+ if (typeof id !== "string" || !id.startsWith(sigil)) return null;
6
+ const idx = id.indexOf(":");
7
+ if (idx < 2) return null;
8
+ return {
9
+ localpart: id.slice(1, idx),
10
+ server: id.slice(idx + 1)
11
+ };
12
+ }
13
+ /** Server name grammar: DNS name, IPv4 or bracketed IPv6, with an optional port. */
14
+ const SERVER_NAME_RE = /^(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9.-]+)(:\d{1,5})?$/;
15
+ function isValidServerName(name) {
16
+ return name.length > 0 && name.length <= 255 && SERVER_NAME_RE.test(name);
17
+ }
18
+ function isValidUserId(id) {
19
+ if (typeof id !== "string" || id.length > 255) return false;
20
+ const parsed = parseId(id, "@");
21
+ return !!parsed && isValidServerName(parsed.server);
22
+ }
23
+ function isValidRoomId(id) {
24
+ return typeof id === "string" && /^![^\s:]+(:[^\s]+)?$/.test(id);
25
+ }
26
+ function isValidAlias(id) {
27
+ return typeof id === "string" && id.length <= 255 && !!parseId(id, "#");
28
+ }
29
+ function generateRoomId(serverName) {
30
+ return `!${randomString(18)}:${serverName}`;
31
+ }
32
+ function generateDeviceId() {
33
+ return randomString(10).toUpperCase();
34
+ }
35
+ //#endregion
36
+ export { LOCALPART_RE, generateDeviceId, generateRoomId, isValidAlias, isValidRoomId, isValidServerName, isValidUserId, parseId };
@@ -0,0 +1,36 @@
1
+ import { isObject } from "./json.js";
2
+ import { UNSTABLE } from "./m.js";
3
+ import { globMatch } from "./glob.js";
4
+ //#region src/core/invitefilter.ts
5
+ const INVITE_CONFIG_TYPE = UNSTABLE.ORG.MATRIX.MSC4155.INVITE_PERMISSION_CONFIG;
6
+ /**
7
+ * MSC4155 invite filtering: "allow", "ignore" (accept but hide) or "block" (refuse).
8
+ * User rules take precedence over server rules; within each, allow > ignore > block.
9
+ */
10
+ async function inviteDecision(hs, target, inviter) {
11
+ const config = (await hs.db.findOne("account_data", [
12
+ {
13
+ field: "user_id",
14
+ value: target
15
+ },
16
+ {
17
+ field: "room_id",
18
+ value: ""
19
+ },
20
+ {
21
+ field: "type",
22
+ value: INVITE_CONFIG_TYPE
23
+ }
24
+ ]))?.content;
25
+ if (!isObject(config)) return "allow";
26
+ const list = (k) => Array.isArray(config[k]) ? config[k].filter((x) => typeof x === "string") : [];
27
+ const server = inviter.slice(inviter.indexOf(":") + 1);
28
+ for (const [level, value] of [["users", inviter], ["servers", server]]) {
29
+ if (list(`allowed_${level}`).some((p) => globMatch(p, value))) return "allow";
30
+ if (list(`ignored_${level}`).some((p) => globMatch(p, value))) return "ignore";
31
+ if (list(`blocked_${level}`).some((p) => globMatch(p, value))) return "block";
32
+ }
33
+ return "allow";
34
+ }
35
+ //#endregion
36
+ export { INVITE_CONFIG_TYPE, inviteDecision };
@@ -0,0 +1,16 @@
1
+ //#region src/core/json.ts
2
+ /** Matrix canonical JSON: sorted keys, no whitespace, UTF-8, integers only. */
3
+ function canonicalJson(value) {
4
+ if (value === null || typeof value !== "object") {
5
+ if (typeof value === "number" && !Number.isInteger(value)) throw new Error("Canonical JSON does not allow non-integer numbers");
6
+ return JSON.stringify(value ?? null);
7
+ }
8
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
9
+ const obj = value;
10
+ return `{${Object.keys(obj).filter((k) => obj[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
11
+ }
12
+ function isObject(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ //#endregion
16
+ export { canonicalJson, isObject };
@@ -0,0 +1,36 @@
1
+ import { Homeserver } from "../homeserver.js";
2
+ //#region src/core/keys.d.ts
3
+ /** End-to-end encryption key storage (device keys, one-time keys, cross-signing). */
4
+ export declare class KeyService {
5
+ private hs;
6
+ constructor(hs: Homeserver);
7
+ /** A local user's devices or device keys changed: update local clients and remote servers. */
8
+ deviceChanged(userId: string, deletedDeviceId?: string): Promise<void>;
9
+ markDeviceListChanged(userId: string, notify?: boolean): Promise<void>;
10
+ /**
11
+ * A partial-state room got its full state (MSC3902): device list updates received meanwhile for
12
+ * users we could not place count as changes now, if those users turned out to be members.
13
+ */
14
+ onPartialStateComplete(roomId: string, pendingUsers: string[]): Promise<void>;
15
+ upload(userId: string, deviceId: string, body: Record<string, any>): Promise<Record<string, number>>;
16
+ oneTimeKeyCounts(userId: string, deviceId: string): Promise<Record<string, number>>;
17
+ unusedFallbackKeyTypes(userId: string, deviceId: string): Promise<string[]>;
18
+ query(requester: string, deviceKeys: Record<string, string[]>, timeoutMs?: number): Promise<Record<string, any>>;
19
+ /** Whether a remote user shares a room with a local user (by known membership). */
20
+ isTracked(userId: string): Promise<boolean>;
21
+ dropRemoteCache(userId: string): Promise<void>;
22
+ /** A tracked user's device list changed: refetch it on the next query. */
23
+ markRemoteStale(userId: string): Promise<void>;
24
+ private storeRemoteCache;
25
+ private fromRemoteCache;
26
+ private withSignatures;
27
+ claim(request: Record<string, Record<string, string>>): Promise<Record<string, any>>;
28
+ uploadCrossSigning(userId: string, body: Record<string, any>): Promise<void>;
29
+ uploadSignatures(signer: string, body: Record<string, any>): Promise<Record<string, any>>;
30
+ /** Users (sharing a room with `userId`) whose device lists changed after `since`. */
31
+ changesSince(userId: string, since: number, upTo: number): Promise<{
32
+ changed: string[];
33
+ left: string[];
34
+ }>;
35
+ }
36
+ //#endregion