matrix-nodejs-homeserver 0.0.0-stage → 0.2.0

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 (120) hide show
  1. package/LICENSE +75 -0
  2. package/README.md +457 -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 +560 -0
  12. package/dist/api/admin.js +251 -0
  13. package/dist/api/appservice.js +33 -0
  14. package/dist/api/backup.js +269 -0
  15. package/dist/api/federation.js +792 -0
  16. package/dist/api/filters.js +87 -0
  17. package/dist/api/index.js +47 -0
  18. package/dist/api/legacy.js +170 -0
  19. package/dist/api/media.js +255 -0
  20. package/dist/api/misc.js +86 -0
  21. package/dist/api/preview.js +200 -0
  22. package/dist/api/profile.js +165 -0
  23. package/dist/api/push.js +187 -0
  24. package/dist/api/relationships.js +264 -0
  25. package/dist/api/rooms.js +1364 -0
  26. package/dist/api/rtc.d.ts +24 -0
  27. package/dist/api/rtc.js +52 -0
  28. package/dist/api/search.js +155 -0
  29. package/dist/api/slidingsync.js +464 -0
  30. package/dist/api/spaces.js +172 -0
  31. package/dist/api/sync.js +600 -0
  32. package/dist/api/threadsubs.js +129 -0
  33. package/dist/api/threepid.js +122 -0
  34. package/dist/api/uia.js +126 -0
  35. package/dist/api/user.js +286 -0
  36. package/dist/core/admin.d.ts +85 -0
  37. package/dist/core/admin.js +307 -0
  38. package/dist/core/adminroom.d.ts +49 -0
  39. package/dist/core/adminroom.js +482 -0
  40. package/dist/core/appservices.d.ts +73 -0
  41. package/dist/core/appservices.js +368 -0
  42. package/dist/core/audit.d.ts +51 -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 +347 -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 +159 -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 +185 -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 +35 -0
  63. package/dist/core/json.js +16 -0
  64. package/dist/core/keys.d.ts +36 -0
  65. package/dist/core/keys.js +449 -0
  66. package/dist/core/notifier.d.ts +17 -0
  67. package/dist/core/notifier.js +54 -0
  68. package/dist/core/oauth.d.ts +38 -0
  69. package/dist/core/oauth.js +108 -0
  70. package/dist/core/partialstate.d.ts +32 -0
  71. package/dist/core/partialstate.js +197 -0
  72. package/dist/core/phone.js +268 -0
  73. package/dist/core/policy.d.ts +29 -0
  74. package/dist/core/policy.js +96 -0
  75. package/dist/core/profile.js +39 -0
  76. package/dist/core/push.d.ts +47 -0
  77. package/dist/core/push.js +306 -0
  78. package/dist/core/pushrules.d.ts +1 -0
  79. package/dist/core/pushrules.js +228 -0
  80. package/dist/core/ratelimit.d.ts +23 -0
  81. package/dist/core/ratelimit.js +77 -0
  82. package/dist/core/rooms.d.ts +215 -0
  83. package/dist/core/rooms.js +1729 -0
  84. package/dist/core/settings.d.ts +11 -0
  85. package/dist/core/settings.js +29 -0
  86. package/dist/core/sso.d.ts +46 -0
  87. package/dist/core/sso.js +165 -0
  88. package/dist/core/stateres.d.ts +4 -0
  89. package/dist/core/stateres.js +208 -0
  90. package/dist/core/threepid.d.ts +80 -0
  91. package/dist/core/threepid.js +275 -0
  92. package/dist/core/typing.d.ts +16 -0
  93. package/dist/core/typing.js +60 -0
  94. package/dist/federation/client.d.ts +51 -0
  95. package/dist/federation/client.js +278 -0
  96. package/dist/federation/index.d.ts +153 -0
  97. package/dist/federation/index.js +1058 -0
  98. package/dist/federation/keyring.d.ts +26 -0
  99. package/dist/federation/keyring.js +122 -0
  100. package/dist/federation/sender.d.ts +26 -0
  101. package/dist/federation/sender.js +164 -0
  102. package/dist/homeserver.d.ts +223 -0
  103. package/dist/homeserver.js +301 -0
  104. package/dist/http/adapters.d.ts +40 -0
  105. package/dist/http/adapters.js +130 -0
  106. package/dist/http/handler.d.ts +8 -0
  107. package/dist/http/handler.js +202 -0
  108. package/dist/http/input.js +24 -0
  109. package/dist/http/router.d.ts +60 -0
  110. package/dist/http/router.js +76 -0
  111. package/dist/index.d.ts +25 -0
  112. package/dist/index.js +14 -0
  113. package/dist/storage/memory.d.ts +5 -0
  114. package/dist/storage/memory.js +122 -0
  115. package/dist/storage/schema.d.ts +4 -0
  116. package/dist/storage/schema.js +664 -0
  117. package/dist/storage/sql.d.ts +72 -0
  118. package/dist/storage/sql.js +274 -0
  119. package/dist/storage/types.d.ts +55 -0
  120. package/package.json +67 -4
@@ -0,0 +1,560 @@
1
+ import { isObject } from "../core/json.js";
2
+ import { randomString } from "../core/crypto.js";
3
+ import { MatrixError, errors } from "../core/errors.js";
4
+ import { LOCALPART_RE } from "../core/ids.js";
5
+ import { toMsisdn } from "../core/phone.js";
6
+ import { canonicalAddress, isMedium } from "../core/threepid.js";
7
+ import { requireUia } from "./uia.js";
8
+ import { body, fail, lenient } from "../http/input.js";
9
+ import { createHmac, randomInt, timingSafeEqual } from "node:crypto";
10
+ import { z } from "zod";
11
+ //#region src/api/account.ts
12
+ const deviceFields = {
13
+ device_id: lenient(z.string()),
14
+ initial_device_display_name: lenient(z.string()),
15
+ refresh_token: lenient(z.boolean()),
16
+ inhibit_login: lenient(z.boolean())
17
+ };
18
+ const loginBody = body({
19
+ type: lenient(z.string()),
20
+ token: lenient(z.string()),
21
+ password: lenient(z.string()),
22
+ ...deviceFields
23
+ });
24
+ const registerBody = body({
25
+ type: lenient(z.string()),
26
+ auth: lenient(z.looseObject({ type: lenient(z.string()) })),
27
+ username: z.string(fail("M_INVALID_PARAM", "username must be a string")).optional(),
28
+ password: z.string(fail("M_INVALID_PARAM", "password must be a string")).optional(),
29
+ ...deviceFields
30
+ });
31
+ const passwordBody = body({
32
+ new_password: lenient(z.string()),
33
+ logout_devices: lenient(z.boolean())
34
+ });
35
+ const sharedSecretRegisterBody = body({
36
+ nonce: lenient(z.string()),
37
+ username: z.string(fail("M_INVALID_PARAM", "Invalid username/password")),
38
+ password: z.string(fail("M_INVALID_PARAM", "Invalid username/password")),
39
+ admin: lenient(z.boolean()),
40
+ mac: lenient(z.string()),
41
+ displayname: lenient(z.string())
42
+ });
43
+ function loginFlows(hs) {
44
+ if (hs.oauth) return [];
45
+ const flows = [];
46
+ if (hs.userAuth.passwordLoginEnabled) flows.push({ type: "m.login.password" });
47
+ if (hs.sso) flows.push({
48
+ type: "m.login.sso",
49
+ identity_providers: hs.sso.providers.map((p) => ({
50
+ id: p.id,
51
+ name: p.name,
52
+ icon: p.icon,
53
+ brand: p.brand
54
+ }))
55
+ });
56
+ flows.push({
57
+ type: "m.login.token",
58
+ ...hs.config.loginViaExistingSession ? { get_login_token: true } : {}
59
+ });
60
+ return flows;
61
+ }
62
+ /** Endpoints that manage passwords are unavailable when an OAuth provider owns the accounts. */
63
+ function rejectIfDelegated(hs) {
64
+ if (hs.oauth) throw new MatrixError(403, "M_FORBIDDEN", "Accounts are managed by the OAuth provider", hs.oauth.options.accountManagementUrl ? { account_management_uri: hs.oauth.options.accountManagementUrl } : {});
65
+ }
66
+ async function validateNewUsername(hs, raw) {
67
+ if (typeof raw !== "string" || raw.length === 0) throw errors.invalidUsername("Username must be a non-empty string");
68
+ const localpart = raw.startsWith("@") ? raw.slice(1).split(":")[0] : raw;
69
+ if (!LOCALPART_RE.test(localpart)) throw errors.invalidUsername("Username contains invalid characters");
70
+ if (`@${localpart}:${hs.serverName}`.length > 255) throw errors.invalidUsername("Username too long");
71
+ if (!await hs.userAuth.validateUsername(localpart)) throw errors.invalidUsername();
72
+ if (await hs.db.findOne("users", [{
73
+ field: "user_id",
74
+ value: `@${localpart}:${hs.serverName}`
75
+ }])) throw errors.userInUse();
76
+ return localpart;
77
+ }
78
+ async function generateLocalpart(hs) {
79
+ for (;;) {
80
+ const candidate = String(randomInt(1e9));
81
+ if (!await hs.db.findOne("users", [{
82
+ field: "localpart",
83
+ value: candidate
84
+ }])) return candidate;
85
+ }
86
+ }
87
+ async function createUser(hs, localpart, opts) {
88
+ const userId = `@${localpart}:${hs.serverName}`;
89
+ await hs.db.create("users", {
90
+ user_id: userId,
91
+ localpart,
92
+ password_hash: opts.password ? await hs.userAuth.hashPassword(opts.password) : null,
93
+ displayname: opts.displayname ?? localpart,
94
+ avatar_url: null,
95
+ is_guest: !!opts.guest,
96
+ is_admin: !!opts.admin,
97
+ deactivated: false,
98
+ created_ts: Date.now()
99
+ });
100
+ await hs.userAuth.onUserRegistered(userId);
101
+ await hs.adminRoom?.onUserCreated(userId).catch((err) => hs.logger.error("Admin room setup failed", err));
102
+ return userId;
103
+ }
104
+ async function resolveLoginUser(hs, body) {
105
+ let user;
106
+ const id = isObject(body.identifier) ? body.identifier : null;
107
+ let threepid = null;
108
+ if (id?.type === "m.id.thirdparty" && typeof id.medium === "string" && typeof id.address === "string") threepid = {
109
+ medium: id.medium,
110
+ address: isMedium(id.medium) ? canonicalAddress(id.medium, id.address) : null
111
+ };
112
+ else if (id?.type === "m.id.phone" && typeof id.country === "string" && typeof id.phone === "string") threepid = {
113
+ medium: "msisdn",
114
+ address: toMsisdn(id.country, id.phone)
115
+ };
116
+ else if (!id && typeof body.medium === "string" && typeof body.address === "string") threepid = {
117
+ medium: body.medium,
118
+ address: isMedium(body.medium) ? canonicalAddress(body.medium, body.address) : null
119
+ };
120
+ if (threepid) {
121
+ const owner = threepid.address && isMedium(threepid.medium) ? await hs.threepids.userFor(threepid.medium, threepid.address) : null;
122
+ if (!owner) throw errors.forbidden("Invalid username or password");
123
+ return owner;
124
+ }
125
+ if (id) {
126
+ if (id.type !== "m.id.user") throw errors.unknown("Unsupported identifier type");
127
+ user = id.user;
128
+ } else user = body.user;
129
+ if (typeof user !== "string" || !user) throw errors.missingParam("Missing user identifier");
130
+ if (user.startsWith("@")) {
131
+ const [lp, ...server] = user.slice(1).split(":");
132
+ if (server.join(":") !== hs.serverName) throw errors.forbidden("Invalid username or password");
133
+ return `@${lp.toLowerCase()}:${hs.serverName}`;
134
+ }
135
+ return `@${user.toLowerCase()}:${hs.serverName}`;
136
+ }
137
+ function registerAccountRoutes(r) {
138
+ r.client("GET", "/login", (ctx) => ({ flows: loginFlows(ctx.hs) }));
139
+ r.client("GET", "/auth_metadata", async (ctx) => {
140
+ if (!ctx.hs.oauth) throw errors.unrecognized("OAuth is not enabled");
141
+ return ctx.hs.oauth.serverMetadata();
142
+ }, ["v1"]);
143
+ r.client("GET", "/auth_issuer", (ctx) => {
144
+ if (!ctx.hs.oauth) throw errors.unrecognized("OAuth is not enabled");
145
+ return { issuer: ctx.hs.oauth.options.issuer };
146
+ }, ["v1"]);
147
+ const ssoRedirect = async (ctx) => {
148
+ if (!ctx.hs.sso) throw errors.unrecognized("SSO is not enabled");
149
+ const redirectUrl = ctx.query.get("redirectUrl");
150
+ if (!redirectUrl || !/^https?:\/\/|^[a-z][a-z0-9+.-]*:/i.test(redirectUrl)) throw errors.missingParam("Missing or invalid redirectUrl");
151
+ return Response.redirect(await ctx.hs.sso.startLogin(ctx.params.idpId ?? null, redirectUrl), 302);
152
+ };
153
+ r.client("POST", "/login/get_token", async (ctx) => {
154
+ const s = await ctx.auth();
155
+ if (!ctx.hs.config.loginViaExistingSession || ctx.hs.oauth || s.isGuest) throw errors.unrecognized();
156
+ await ctx.rateLimit("login");
157
+ await requireUia(ctx.hs, await ctx.json(), [["m.login.password"]], s.userId);
158
+ const ttl = 12e4;
159
+ return {
160
+ login_token: await ctx.hs.auth.createLoginToken(s.userId, ttl),
161
+ expires_in_ms: ttl
162
+ };
163
+ }, ["v1"]);
164
+ r.client("GET", "/login/sso/redirect", ssoRedirect);
165
+ r.client("GET", "/login/sso/redirect/:idpId", ssoRedirect);
166
+ r.client("GET", "/login/sso/callback/:idpId", async (ctx) => {
167
+ if (!ctx.hs.sso) throw errors.unrecognized("SSO is not enabled");
168
+ return Response.redirect(await ctx.hs.sso.finishLogin(ctx.params.idpId, ctx.query), 302);
169
+ });
170
+ r.client("POST", "/login", { input: loginBody }, async (ctx) => {
171
+ await ctx.rateLimit("login");
172
+ const body = ctx.input;
173
+ const hs = ctx.hs;
174
+ if (hs.oauth) throw errors.unrecognized("Log in through the OAuth provider");
175
+ if (body.type === "m.login.application_service") {
176
+ const reg = ctx.accessToken() ? hs.appservices.byToken(ctx.accessToken()) : null;
177
+ if (!reg) throw errors.unknownToken("An application service token is required");
178
+ const userId = await resolveLoginUser(hs, body);
179
+ if (!hs.appservices.isInterestedUser(reg, userId)) throw errors.forbidden("User is not in the application service's namespace");
180
+ if (!await hs.db.findOne("users", [{
181
+ field: "user_id",
182
+ value: userId
183
+ }])) throw errors.forbidden("Unknown user");
184
+ return {
185
+ ...await hs.auth.createSession(userId, {
186
+ deviceId: body.device_id,
187
+ displayName: body.initial_device_display_name
188
+ }),
189
+ home_server: hs.serverName
190
+ };
191
+ }
192
+ if (body.type === "m.login.token") {
193
+ if (body.token === void 0) throw errors.missingParam("Missing token");
194
+ const userId = await hs.auth.consumeLoginToken(body.token).catch(async (err) => {
195
+ await hs.audit.record({
196
+ action: "login_failed",
197
+ actor: null,
198
+ ip: ctx.ip,
199
+ outcome: "failure",
200
+ details: { type: "m.login.token" }
201
+ });
202
+ throw err;
203
+ });
204
+ const user = await hs.db.findOne("users", [{
205
+ field: "user_id",
206
+ value: userId
207
+ }]);
208
+ if (!user || user.deactivated) throw errors.userDeactivated();
209
+ const session = await hs.auth.createSession(userId, {
210
+ deviceId: body.device_id,
211
+ displayName: body.initial_device_display_name,
212
+ refreshToken: body.refresh_token === true
213
+ });
214
+ await hs.audit.record({
215
+ action: "login",
216
+ actor: userId,
217
+ target: session.device_id,
218
+ ip: ctx.ip,
219
+ userAgent: ctx.request.headers.get("user-agent"),
220
+ details: { type: "m.login.token" }
221
+ });
222
+ return {
223
+ ...session,
224
+ home_server: hs.serverName,
225
+ well_known: wellKnown(hs) ?? void 0
226
+ };
227
+ }
228
+ if (body.type !== "m.login.password" || !hs.userAuth.passwordLoginEnabled) throw errors.unknown("Unsupported login type");
229
+ const userId = await resolveLoginUser(hs, body);
230
+ if (body.password === void 0) throw errors.missingParam("Missing password");
231
+ let user = await hs.db.findOne("users", [{
232
+ field: "user_id",
233
+ value: userId
234
+ }]);
235
+ const ok = await hs.userAuth.checkPassword({
236
+ userId,
237
+ localpart: userId.slice(1).split(":")[0],
238
+ password: body.password,
239
+ storedHash: user?.password_hash ?? null
240
+ });
241
+ const ua = ctx.request.headers.get("user-agent");
242
+ if (!ok) {
243
+ await hs.audit.record({
244
+ action: "login_failed",
245
+ actor: userId,
246
+ ip: ctx.ip,
247
+ userAgent: ua,
248
+ outcome: "failure",
249
+ details: { type: "m.login.password" }
250
+ });
251
+ throw errors.forbidden("Invalid username or password");
252
+ }
253
+ if (!user) {
254
+ if (!await hs.userAuth.autoProvision(userId)) throw errors.forbidden("Invalid username or password");
255
+ await createUser(hs, userId.slice(1).split(":")[0], {});
256
+ user = await hs.db.findOne("users", [{
257
+ field: "user_id",
258
+ value: userId
259
+ }]);
260
+ }
261
+ if (user.deactivated) throw errors.userDeactivated();
262
+ const session = await hs.auth.createSession(userId, {
263
+ deviceId: body.device_id,
264
+ displayName: body.initial_device_display_name,
265
+ refreshToken: body.refresh_token === true
266
+ });
267
+ await hs.audit.record({
268
+ action: "login",
269
+ actor: userId,
270
+ target: session.device_id,
271
+ ip: ctx.ip,
272
+ userAgent: ua,
273
+ details: { type: "m.login.password" }
274
+ });
275
+ return {
276
+ ...session,
277
+ home_server: hs.serverName,
278
+ well_known: wellKnown(hs) ?? void 0
279
+ };
280
+ });
281
+ r.client("POST", "/refresh", { input: body({ refresh_token: lenient(z.string()) }) }, async (ctx) => {
282
+ const body = ctx.input;
283
+ if (body.refresh_token === void 0) throw errors.missingParam("Missing refresh_token");
284
+ const { access_token, refresh_token, expires_in_ms } = await ctx.hs.auth.refresh(body.refresh_token);
285
+ return {
286
+ access_token,
287
+ refresh_token,
288
+ expires_in_ms
289
+ };
290
+ });
291
+ r.client("POST", "/logout", async (ctx) => {
292
+ const s = await ctx.auth();
293
+ await ctx.hs.auth.deleteDevice(s.userId, s.deviceId);
294
+ await ctx.hs.audit.record({
295
+ action: "logout",
296
+ actor: s.userId,
297
+ target: s.deviceId,
298
+ ip: ctx.ip
299
+ });
300
+ return {};
301
+ });
302
+ r.client("POST", "/logout/all", async (ctx) => {
303
+ const s = await ctx.auth();
304
+ await ctx.hs.auth.deleteAllDevices(s.userId);
305
+ await ctx.hs.audit.record({
306
+ action: "logout",
307
+ actor: s.userId,
308
+ ip: ctx.ip,
309
+ details: { all: true }
310
+ });
311
+ return {};
312
+ });
313
+ r.client("GET", "/account/whoami", async (ctx) => {
314
+ const s = await ctx.auth();
315
+ return {
316
+ user_id: s.userId,
317
+ ...s.deviceId ? { device_id: s.deviceId } : {},
318
+ is_guest: s.isGuest
319
+ };
320
+ });
321
+ r.client("GET", "/register/available", async (ctx) => {
322
+ const username = ctx.query.get("username");
323
+ if (!username) throw errors.missingParam("Missing username");
324
+ await validateNewUsername(ctx.hs, username);
325
+ return { available: true };
326
+ });
327
+ r.client("GET", "/register", async (ctx) => ({
328
+ flows: (await registrationFlows(ctx.hs)).map((stages) => ({ stages })),
329
+ params: {}
330
+ }));
331
+ r.client("GET", "/register/m.login.registration_token/validity", async (ctx) => {
332
+ await ctx.rateLimit("register");
333
+ const token = ctx.query.get("token");
334
+ if (!token) throw errors.missingParam("Missing token");
335
+ return { valid: await registrationTokenValid(ctx.hs, token) };
336
+ }, ["v1"]);
337
+ r.client("POST", "/register", { input: registerBody }, async (ctx) => {
338
+ const hs = ctx.hs;
339
+ const body = ctx.input;
340
+ const kind = ctx.query.get("kind") ?? "user";
341
+ if (kind === "guest") {
342
+ if (!hs.userAuth.guestAccessEnabled) throw errors.forbidden("Guest access is disabled");
343
+ const userId = await createUser(hs, await generateLocalpart(hs), { guest: true });
344
+ return {
345
+ ...await hs.auth.createSession(userId, { displayName: body.initial_device_display_name }),
346
+ home_server: hs.serverName
347
+ };
348
+ }
349
+ if (kind !== "user") throw errors.invalidParam("Unknown kind");
350
+ if (body.type === "m.login.application_service" || body.auth?.type === "m.login.application_service") {
351
+ const s = await ctx.auth();
352
+ if (!s.appservice) throw errors.forbidden("An application service token is required");
353
+ if (body.username === void 0) throw errors.missingParam("Missing username");
354
+ const localpart = body.username.toLowerCase();
355
+ const userId = `@${localpart}:${hs.serverName}`;
356
+ if (!hs.appservices.isInterestedUser(s.appservice, userId)) throw errors.exclusive("User is not in the application service's namespace");
357
+ if (!LOCALPART_RE.test(localpart)) throw errors.invalidUsername();
358
+ if (await hs.db.findOne("users", [{
359
+ field: "user_id",
360
+ value: userId
361
+ }])) throw errors.userInUse();
362
+ await createUser(hs, localpart, {});
363
+ if (body.inhibit_login === true) return {
364
+ user_id: userId,
365
+ home_server: hs.serverName
366
+ };
367
+ return {
368
+ ...await hs.auth.createSession(userId, {
369
+ deviceId: body.device_id,
370
+ displayName: body.initial_device_display_name
371
+ }),
372
+ home_server: hs.serverName
373
+ };
374
+ }
375
+ if (ctx.accessToken() && hs.appservices.byToken(ctx.accessToken())) throw errors.missingParam("Application services must use type m.login.application_service");
376
+ rejectIfDelegated(hs);
377
+ if (!await hs.registrationAllowed()) throw errors.forbidden("Registration is disabled");
378
+ await ctx.rateLimit("register");
379
+ const localpart = body.username !== void 0 ? await validateNewUsername(hs, body.username.toLowerCase()) : await generateLocalpart(hs);
380
+ if (hs.appservices.exclusiveUserOwner(`@${localpart}:${hs.serverName}`)) throw errors.exclusive("This user ID is reserved by an application service");
381
+ if (body.password !== void 0) {
382
+ const problem = await hs.userAuth.validatePassword(body.password);
383
+ if (problem) throw new MatrixError(400, "M_WEAK_PASSWORD", problem);
384
+ }
385
+ const flows = await registrationFlows(hs);
386
+ const { threepids } = await requireUia(hs, body, flows, null);
387
+ await consumeRegistrationToken(hs, body);
388
+ if (await hs.db.findOne("users", [{
389
+ field: "localpart",
390
+ value: localpart
391
+ }])) throw errors.userInUse();
392
+ const userId = await createUser(hs, localpart, { password: body.password });
393
+ for (const t of threepids) await hs.threepids.add(userId, t).catch((err) => hs.logger.warn("Could not attach", t.medium, "to", userId, err));
394
+ await hs.audit.record({
395
+ action: "register",
396
+ actor: userId,
397
+ ip: ctx.ip,
398
+ userAgent: ctx.request.headers.get("user-agent"),
399
+ details: { token: body.auth?.type === "m.login.registration_token" }
400
+ });
401
+ if (body.inhibit_login === true) return {
402
+ user_id: userId,
403
+ home_server: hs.serverName
404
+ };
405
+ return {
406
+ ...await hs.auth.createSession(userId, {
407
+ deviceId: body.device_id,
408
+ displayName: body.initial_device_display_name,
409
+ refreshToken: body.refresh_token === true
410
+ }),
411
+ home_server: hs.serverName
412
+ };
413
+ });
414
+ r.client("POST", "/account/password", { input: passwordBody }, async (ctx) => {
415
+ const hs = ctx.hs;
416
+ rejectIfDelegated(hs);
417
+ const body = ctx.input;
418
+ let s;
419
+ if (ctx.accessToken()) {
420
+ const session = await ctx.auth();
421
+ await requireUia(hs, body, [["m.login.password"]], session.userId);
422
+ s = {
423
+ userId: session.userId,
424
+ deviceId: session.deviceId
425
+ };
426
+ } else {
427
+ const { threepids } = await requireUia(hs, body, [["m.login.email.identity"], ["m.login.msisdn"]], null);
428
+ const owner = threepids.length ? await hs.threepids.userFor(threepids[0].medium, threepids[0].address) : null;
429
+ if (!owner) throw new MatrixError(400, "M_THREEPID_NOT_FOUND", "No account uses this address");
430
+ s = {
431
+ userId: owner,
432
+ deviceId: null
433
+ };
434
+ }
435
+ if (!body.new_password) throw errors.missingParam("Missing new_password");
436
+ const problem = await hs.userAuth.validatePassword(body.new_password);
437
+ if (problem) throw new MatrixError(400, "M_WEAK_PASSWORD", problem);
438
+ await hs.db.update("users", [{
439
+ field: "user_id",
440
+ value: s.userId
441
+ }], { password_hash: await hs.userAuth.hashPassword(body.new_password) });
442
+ await hs.audit.record({
443
+ action: "password_change",
444
+ actor: s.userId,
445
+ ip: ctx.ip,
446
+ details: { logout_devices: body.logout_devices !== false }
447
+ });
448
+ if (body.logout_devices !== false) await hs.auth.deleteAllDevices(s.userId, s.deviceId ?? void 0);
449
+ return {};
450
+ });
451
+ r.client("POST", "/account/deactivate", { input: body({}) }, async (ctx) => {
452
+ const hs = ctx.hs;
453
+ rejectIfDelegated(hs);
454
+ const s = await ctx.auth();
455
+ await requireUia(hs, ctx.input, [["m.login.password"]], s.userId);
456
+ for (const membership of [
457
+ "join",
458
+ "invite",
459
+ "knock"
460
+ ]) for (const roomId of await hs.rooms.roomsForUser(s.userId, membership)) try {
461
+ await hs.rooms.setMembership(roomId, s.userId, s.userId, "leave");
462
+ } catch {}
463
+ await hs.auth.deleteAllDevices(s.userId);
464
+ await hs.db.update("users", [{
465
+ field: "user_id",
466
+ value: s.userId
467
+ }], {
468
+ deactivated: true,
469
+ password_hash: null,
470
+ displayname: null,
471
+ avatar_url: null
472
+ });
473
+ await hs.db.delete("profile_fields", [{
474
+ field: "user_id",
475
+ value: s.userId
476
+ }]);
477
+ const unbind = await hs.threepids.removeAll(s.userId);
478
+ await hs.audit.record({
479
+ action: "deactivate",
480
+ actor: s.userId,
481
+ target: s.userId,
482
+ ip: ctx.ip
483
+ });
484
+ return { id_server_unbind_result: unbind };
485
+ });
486
+ r.get("/_synapse/admin/v1/register", async (ctx) => {
487
+ if (!ctx.hs.config.registrationSharedSecret) throw errors.unrecognized();
488
+ const nonce = randomString(32);
489
+ await ctx.hs.cluster.set(`regnonce:${nonce}`, true, 6e4);
490
+ return { nonce };
491
+ });
492
+ r.post("/_synapse/admin/v1/register", { input: sharedSecretRegisterBody }, async (ctx) => {
493
+ const hs = ctx.hs;
494
+ const secret = hs.config.registrationSharedSecret;
495
+ if (!secret) throw errors.unrecognized();
496
+ const body = ctx.input;
497
+ if (body.nonce === void 0 || !await hs.cluster.get(`regnonce:${body.nonce}`)) throw errors.unknown("Unrecognised nonce");
498
+ await hs.cluster.delete(`regnonce:${body.nonce}`);
499
+ const admin = body.admin === true;
500
+ const expected = createHmac("sha1", secret).update(`${body.nonce}\u0000${body.username}\u0000${body.password}\u0000${admin ? "admin" : "notadmin"}`).digest("hex");
501
+ const given = Buffer.from(body.mac ?? "", "utf8");
502
+ if (given.length !== expected.length || !timingSafeEqual(given, Buffer.from(expected, "utf8"))) throw errors.forbidden("HMAC incorrect");
503
+ const userId = await createUser(hs, await validateNewUsername(hs, body.username.toLowerCase()), {
504
+ password: body.password,
505
+ admin,
506
+ displayname: body.displayname
507
+ });
508
+ const session = await hs.auth.createSession(userId);
509
+ await hs.audit.record({
510
+ action: "register",
511
+ actor: "server",
512
+ target: userId,
513
+ ip: ctx.ip,
514
+ details: {
515
+ shared_secret: true,
516
+ admin
517
+ }
518
+ });
519
+ return {
520
+ ...session,
521
+ home_server: hs.serverName
522
+ };
523
+ });
524
+ }
525
+ async function registrationFlows(hs) {
526
+ if (hs.adminRoom?.options.firstUserCanRegister && await hs.adminRoom.noUsersYet()) return [["m.login.dummy"]];
527
+ const base = await hs.registrationMode() === "token" ? ["m.login.registration_token"] : ["m.login.dummy"];
528
+ if (!hs.threepids.canValidate("email")) return [base];
529
+ const withEmail = [...base.filter((s) => s !== "m.login.dummy"), "m.login.email.identity"];
530
+ return hs.config.threepid.requireEmailForRegistration ? [withEmail] : [base, withEmail];
531
+ }
532
+ async function registrationTokenValid(hs, token) {
533
+ const row = await hs.db.findOne("registration_tokens", [{
534
+ field: "token",
535
+ value: token
536
+ }]);
537
+ if (!row) return false;
538
+ if (row.expiry_time !== null && row.expiry_time < Date.now()) return false;
539
+ return row.uses_allowed === null || row.completed < row.uses_allowed;
540
+ }
541
+ async function consumeRegistrationToken(hs, body) {
542
+ if (body.auth?.type !== "m.login.registration_token") return;
543
+ const token = body.auth?.token;
544
+ const row = await hs.db.findOne("registration_tokens", [{
545
+ field: "token",
546
+ value: token
547
+ }]);
548
+ if (row) await hs.db.update("registration_tokens", [{
549
+ field: "token",
550
+ value: token
551
+ }], { completed: row.completed + 1 });
552
+ }
553
+ function wellKnown(hs) {
554
+ if (!hs.config.publicBaseUrl) return null;
555
+ const wk = { "m.homeserver": { base_url: hs.config.publicBaseUrl } };
556
+ if (hs.config.rtc.transports?.length) wk["org.matrix.msc4143.rtc_foci"] = hs.config.rtc.transports;
557
+ return wk;
558
+ }
559
+ //#endregion
560
+ export { createUser, registerAccountRoutes, registrationFlows, registrationTokenValid, wellKnown };