matrix-nodejs-homeserver 0.2.0 → 0.3.2
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/README.md +89 -11
- package/dist/adapters/cluster.d.ts +1 -26
- package/dist/adapters/cluster.js +50 -103
- package/dist/api/account.js +407 -354
- package/dist/api/admin.js +82 -61
- package/dist/api/appservice.js +37 -25
- package/dist/api/backup.js +73 -62
- package/dist/api/federation.js +172 -138
- package/dist/api/filters.js +4 -3
- package/dist/api/legacy.js +91 -78
- package/dist/api/media.js +71 -55
- package/dist/api/misc.js +46 -37
- package/dist/api/preview.js +37 -73
- package/dist/api/profile.js +108 -97
- package/dist/api/push.js +114 -99
- package/dist/api/relationships.js +64 -58
- package/dist/api/rooms.js +703 -580
- package/dist/api/rtc.d.ts +0 -1
- package/dist/api/rtc.js +48 -34
- package/dist/api/search.js +133 -127
- package/dist/api/slidingsync.js +41 -31
- package/dist/api/spaces.js +82 -77
- package/dist/api/sync.js +325 -135
- package/dist/api/threadsubs.js +72 -61
- package/dist/api/threepid.js +99 -66
- package/dist/api/uia.js +17 -16
- package/dist/api/user.js +221 -178
- package/dist/core/admin.d.ts +0 -1
- package/dist/core/admin.js +6 -5
- package/dist/core/adminroom.js +9 -8
- package/dist/core/appservices.d.ts +2 -1
- package/dist/core/appservices.js +5 -4
- package/dist/core/audit.d.ts +0 -1
- package/dist/core/authrules.js +26 -25
- package/dist/core/cluster.d.ts +12 -5
- package/dist/core/cluster.js +46 -29
- package/dist/core/delayed.js +2 -1
- package/dist/core/events.js +11 -10
- package/dist/core/html.js +13 -0
- package/dist/core/identity.js +1 -1
- package/dist/core/invitefilter.js +2 -1
- package/dist/core/json.js +10 -1
- package/dist/core/keys.d.ts +10 -1
- package/dist/core/keys.js +62 -42
- package/dist/core/m.d.ts +209 -0
- package/dist/core/m.js +182 -0
- package/dist/core/outbound.d.ts +16 -0
- package/dist/core/outbound.js +172 -0
- package/dist/core/partialstate.js +2 -1
- package/dist/core/policy.js +4 -3
- package/dist/core/push.d.ts +0 -1
- package/dist/core/push.js +41 -28
- package/dist/core/pushrules.js +36 -35
- package/dist/core/ratelimit.d.ts +1 -1
- package/dist/core/rooms.d.ts +10 -1
- package/dist/core/rooms.js +101 -74
- package/dist/core/semaphore.js +23 -0
- package/dist/core/sso.d.ts +15 -1
- package/dist/core/sso.js +38 -2
- package/dist/core/stateres.js +7 -6
- package/dist/core/threepid.js +1 -9
- package/dist/federation/client.js +9 -1
- package/dist/federation/index.d.ts +2 -2
- package/dist/federation/index.js +43 -35
- package/dist/homeserver.d.ts +23 -4
- package/dist/homeserver.js +12 -3
- package/dist/http/handler.js +2 -23
- package/dist/http/input.js +2 -2
- package/dist/http/procedure.js +25 -0
- package/dist/http/router.d.ts +9 -26
- package/dist/http/router.js +10 -30
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/storage/batch.js +11 -0
- package/dist/storage/memory.js +1 -0
- package/dist/storage/schema.js +1 -1
- package/dist/storage/sql.js +20 -1
- package/dist/storage/types.d.ts +16 -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 +8 -6
- package/dist/core/pushrules.d.ts +0 -1
package/dist/api/account.js
CHANGED
|
@@ -1,51 +1,55 @@
|
|
|
1
1
|
import { isObject } from "../core/json.js";
|
|
2
2
|
import { randomString } from "../core/crypto.js";
|
|
3
|
+
import { M, UNSTABLE } from "../core/m.js";
|
|
3
4
|
import { MatrixError, errors } from "../core/errors.js";
|
|
4
5
|
import { LOCALPART_RE } from "../core/ids.js";
|
|
5
6
|
import { toMsisdn } from "../core/phone.js";
|
|
7
|
+
import { escapeHtml } from "../core/html.js";
|
|
6
8
|
import { canonicalAddress, isMedium } from "../core/threepid.js";
|
|
7
|
-
import {
|
|
9
|
+
import { boolean, looseObject, string } from "../vendor/zod/v4/classic/schemas.js";
|
|
8
10
|
import { body, fail, lenient } from "../http/input.js";
|
|
11
|
+
import { procedure } from "../http/procedure.js";
|
|
12
|
+
import { requireUia } from "./uia.js";
|
|
13
|
+
import { checkRedirectUrl } from "../core/sso.js";
|
|
9
14
|
import { createHmac, randomInt, timingSafeEqual } from "node:crypto";
|
|
10
|
-
import { z } from "zod";
|
|
11
15
|
//#region src/api/account.ts
|
|
12
16
|
const deviceFields = {
|
|
13
|
-
device_id: lenient(
|
|
14
|
-
initial_device_display_name: lenient(
|
|
15
|
-
refresh_token: lenient(
|
|
16
|
-
inhibit_login: lenient(
|
|
17
|
+
device_id: lenient(string()),
|
|
18
|
+
initial_device_display_name: lenient(string()),
|
|
19
|
+
refresh_token: lenient(boolean()),
|
|
20
|
+
inhibit_login: lenient(boolean())
|
|
17
21
|
};
|
|
18
22
|
const loginBody = body({
|
|
19
|
-
type: lenient(
|
|
20
|
-
token: lenient(
|
|
21
|
-
password: lenient(
|
|
23
|
+
type: lenient(string()),
|
|
24
|
+
token: lenient(string()),
|
|
25
|
+
password: lenient(string()),
|
|
22
26
|
...deviceFields
|
|
23
27
|
});
|
|
24
28
|
const registerBody = body({
|
|
25
|
-
type: lenient(
|
|
26
|
-
auth: lenient(
|
|
27
|
-
username:
|
|
28
|
-
password:
|
|
29
|
+
type: lenient(string()),
|
|
30
|
+
auth: lenient(looseObject({ type: lenient(string()) })),
|
|
31
|
+
username: string(fail("M_INVALID_PARAM", "username must be a string")).optional(),
|
|
32
|
+
password: string(fail("M_INVALID_PARAM", "password must be a string")).optional(),
|
|
29
33
|
...deviceFields
|
|
30
34
|
});
|
|
31
35
|
const passwordBody = body({
|
|
32
|
-
new_password: lenient(
|
|
33
|
-
logout_devices: lenient(
|
|
36
|
+
new_password: lenient(string()),
|
|
37
|
+
logout_devices: lenient(boolean())
|
|
34
38
|
});
|
|
35
39
|
const sharedSecretRegisterBody = body({
|
|
36
|
-
nonce: lenient(
|
|
37
|
-
username:
|
|
38
|
-
password:
|
|
39
|
-
admin: lenient(
|
|
40
|
-
mac: lenient(
|
|
41
|
-
displayname: lenient(
|
|
40
|
+
nonce: lenient(string()),
|
|
41
|
+
username: string(fail("M_INVALID_PARAM", "Invalid username/password")),
|
|
42
|
+
password: string(fail("M_INVALID_PARAM", "Invalid username/password")),
|
|
43
|
+
admin: lenient(boolean()),
|
|
44
|
+
mac: lenient(string()),
|
|
45
|
+
displayname: lenient(string())
|
|
42
46
|
});
|
|
43
47
|
function loginFlows(hs) {
|
|
44
48
|
if (hs.oauth) return [];
|
|
45
49
|
const flows = [];
|
|
46
|
-
if (hs.userAuth.passwordLoginEnabled) flows.push({ type:
|
|
50
|
+
if (hs.userAuth.passwordLoginEnabled) flows.push({ type: M.LOGIN.PASSWORD });
|
|
47
51
|
if (hs.sso) flows.push({
|
|
48
|
-
type:
|
|
52
|
+
type: M.LOGIN.SSO,
|
|
49
53
|
identity_providers: hs.sso.providers.map((p) => ({
|
|
50
54
|
id: p.id,
|
|
51
55
|
name: p.name,
|
|
@@ -54,7 +58,7 @@ function loginFlows(hs) {
|
|
|
54
58
|
}))
|
|
55
59
|
});
|
|
56
60
|
flows.push({
|
|
57
|
-
type:
|
|
61
|
+
type: M.LOGIN.TOKEN,
|
|
58
62
|
...hs.config.loginViaExistingSession ? { get_login_token: true } : {}
|
|
59
63
|
});
|
|
60
64
|
return flows;
|
|
@@ -105,11 +109,11 @@ async function resolveLoginUser(hs, body) {
|
|
|
105
109
|
let user;
|
|
106
110
|
const id = isObject(body.identifier) ? body.identifier : null;
|
|
107
111
|
let threepid = null;
|
|
108
|
-
if (id?.type ===
|
|
112
|
+
if (id?.type === M.ID.THIRDPARTY && typeof id.medium === "string" && typeof id.address === "string") threepid = {
|
|
109
113
|
medium: id.medium,
|
|
110
114
|
address: isMedium(id.medium) ? canonicalAddress(id.medium, id.address) : null
|
|
111
115
|
};
|
|
112
|
-
else if (id?.type ===
|
|
116
|
+
else if (id?.type === M.ID.PHONE && typeof id.country === "string" && typeof id.phone === "string") threepid = {
|
|
113
117
|
medium: "msisdn",
|
|
114
118
|
address: toMsisdn(id.country, id.phone)
|
|
115
119
|
};
|
|
@@ -123,7 +127,7 @@ async function resolveLoginUser(hs, body) {
|
|
|
123
127
|
return owner;
|
|
124
128
|
}
|
|
125
129
|
if (id) {
|
|
126
|
-
if (id.type !==
|
|
130
|
+
if (id.type !== M.ID.USER) throw errors.unknown("Unsupported identifier type");
|
|
127
131
|
user = id.user;
|
|
128
132
|
} else user = body.user;
|
|
129
133
|
if (typeof user !== "string" || !user) throw errors.missingParam("Missing user identifier");
|
|
@@ -135,77 +139,152 @@ async function resolveLoginUser(hs, body) {
|
|
|
135
139
|
return `@${user.toLowerCase()}:${hs.serverName}`;
|
|
136
140
|
}
|
|
137
141
|
function registerAccountRoutes(r) {
|
|
138
|
-
r.client("GET", "/login", (ctx) => ({ flows: loginFlows(ctx.hs) }));
|
|
139
|
-
r.client("GET", "/auth_metadata", async (ctx) => {
|
|
142
|
+
r.client("GET", "/login", procedure({ handler: (ctx) => ({ flows: loginFlows(ctx.hs) }) }));
|
|
143
|
+
r.client("GET", "/auth_metadata", procedure({ handler: async (ctx) => {
|
|
140
144
|
if (!ctx.hs.oauth) throw errors.unrecognized("OAuth is not enabled");
|
|
141
145
|
return ctx.hs.oauth.serverMetadata();
|
|
142
|
-
}, ["v1"]);
|
|
143
|
-
r.client("GET", "/auth_issuer", (ctx) => {
|
|
146
|
+
} }), ["v1"]);
|
|
147
|
+
r.client("GET", "/auth_issuer", procedure({ handler: (ctx) => {
|
|
144
148
|
if (!ctx.hs.oauth) throw errors.unrecognized("OAuth is not enabled");
|
|
145
149
|
return { issuer: ctx.hs.oauth.options.issuer };
|
|
146
|
-
}, ["v1"]);
|
|
150
|
+
} }), ["v1"]);
|
|
151
|
+
const ssoConfirmPage = (serverName, result) => {
|
|
152
|
+
const client = escapeHtml(result.client);
|
|
153
|
+
const html = `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Continue to ${client}</title>
|
|
154
|
+
<style>body{font:16px system-ui,sans-serif;max-width:32rem;margin:3rem auto;padding:0 1rem}a.button{display:inline-block;padding:.6rem 1.2rem;background:#0b7a55;color:#fff;border-radius:6px;text-decoration:none}</style>
|
|
155
|
+
<h1>Continue to ${client}?</h1>
|
|
156
|
+
<p>You signed in to <b>${escapeHtml(serverName)}</b>. <b>${client}</b> will get access to your account.</p>
|
|
157
|
+
<p>If you did not just sign in with this app, close this page.</p>
|
|
158
|
+
<p><a class="button" href="${escapeHtml(result.url)}">Continue to ${client}</a></p>`;
|
|
159
|
+
return new Response(html, { headers: {
|
|
160
|
+
"content-type": "text/html; charset=utf-8",
|
|
161
|
+
"cache-control": "no-store",
|
|
162
|
+
"referrer-policy": "no-referrer",
|
|
163
|
+
"x-frame-options": "DENY",
|
|
164
|
+
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'"
|
|
165
|
+
} });
|
|
166
|
+
};
|
|
147
167
|
const ssoRedirect = async (ctx) => {
|
|
148
168
|
if (!ctx.hs.sso) throw errors.unrecognized("SSO is not enabled");
|
|
149
|
-
const redirectUrl = ctx.query.get("redirectUrl");
|
|
150
|
-
if (!redirectUrl
|
|
169
|
+
const redirectUrl = checkRedirectUrl(ctx.query.get("redirectUrl"));
|
|
170
|
+
if (!redirectUrl) throw errors.missingParam("Missing or invalid redirectUrl");
|
|
151
171
|
return Response.redirect(await ctx.hs.sso.startLogin(ctx.params.idpId ?? null, redirectUrl), 302);
|
|
152
172
|
};
|
|
153
|
-
r.client("POST", "/login/get_token",
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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");
|
|
173
|
+
r.client("POST", "/login/get_token", procedure({
|
|
174
|
+
auth: "user",
|
|
175
|
+
handler: async (ctx) => {
|
|
176
|
+
const s = ctx.session;
|
|
177
|
+
if (!ctx.hs.config.loginViaExistingSession || ctx.hs.oauth || s.isGuest) throw errors.unrecognized();
|
|
178
|
+
await ctx.rateLimit("login");
|
|
179
|
+
await requireUia(ctx.hs, await ctx.json(), [[M.LOGIN.PASSWORD]], s.userId);
|
|
180
|
+
const ttl = 12e4;
|
|
184
181
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
displayName: body.initial_device_display_name
|
|
188
|
-
}),
|
|
189
|
-
home_server: hs.serverName
|
|
182
|
+
login_token: await ctx.hs.auth.createLoginToken(s.userId, ttl),
|
|
183
|
+
expires_in_ms: ttl
|
|
190
184
|
};
|
|
191
185
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
186
|
+
}), ["v1"]);
|
|
187
|
+
r.client("GET", "/login/sso/redirect", procedure({ handler: ssoRedirect }));
|
|
188
|
+
r.client("GET", "/login/sso/redirect/:idpId", procedure({ handler: ssoRedirect }));
|
|
189
|
+
r.client("GET", "/login/sso/callback/:idpId", procedure({ handler: async (ctx) => {
|
|
190
|
+
if (!ctx.hs.sso) throw errors.unrecognized("SSO is not enabled");
|
|
191
|
+
const result = await ctx.hs.sso.finishLogin(ctx.params.idpId, ctx.query);
|
|
192
|
+
return result.confirm ? ssoConfirmPage(ctx.hs.serverName, result) : Response.redirect(result.url, 302);
|
|
193
|
+
} }));
|
|
194
|
+
r.client("POST", "/login", procedure({
|
|
195
|
+
rateLimit: "login",
|
|
196
|
+
input: loginBody,
|
|
197
|
+
handler: async (ctx) => {
|
|
198
|
+
const body = ctx.input;
|
|
199
|
+
const hs = ctx.hs;
|
|
200
|
+
if (hs.oauth) throw errors.unrecognized("Log in through the OAuth provider");
|
|
201
|
+
if (body.type === M.LOGIN.APPLICATION_SERVICE) {
|
|
202
|
+
const reg = ctx.accessToken() ? hs.appservices.byToken(ctx.accessToken()) : null;
|
|
203
|
+
if (!reg) throw errors.unknownToken("An application service token is required");
|
|
204
|
+
const userId = await resolveLoginUser(hs, body);
|
|
205
|
+
if (!hs.appservices.isInterestedUser(reg, userId)) throw errors.forbidden("User is not in the application service's namespace");
|
|
206
|
+
if (!await hs.db.findOne("users", [{
|
|
207
|
+
field: "user_id",
|
|
208
|
+
value: userId
|
|
209
|
+
}])) throw errors.forbidden("Unknown user");
|
|
210
|
+
return {
|
|
211
|
+
...await hs.auth.createSession(userId, {
|
|
212
|
+
deviceId: body.device_id,
|
|
213
|
+
displayName: body.initial_device_display_name
|
|
214
|
+
}),
|
|
215
|
+
home_server: hs.serverName
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (body.type === M.LOGIN.TOKEN) {
|
|
219
|
+
if (body.token === void 0) throw errors.missingParam("Missing token");
|
|
220
|
+
const userId = await hs.auth.consumeLoginToken(body.token).catch(async (err) => {
|
|
221
|
+
await hs.audit.record({
|
|
222
|
+
action: "login_failed",
|
|
223
|
+
actor: null,
|
|
224
|
+
ip: ctx.ip,
|
|
225
|
+
outcome: "failure",
|
|
226
|
+
details: { type: M.LOGIN.TOKEN }
|
|
227
|
+
});
|
|
228
|
+
throw err;
|
|
229
|
+
});
|
|
230
|
+
const user = await hs.db.findOne("users", [{
|
|
231
|
+
field: "user_id",
|
|
232
|
+
value: userId
|
|
233
|
+
}]);
|
|
234
|
+
if (!user || user.deactivated) throw errors.userDeactivated();
|
|
235
|
+
const session = await hs.auth.createSession(userId, {
|
|
236
|
+
deviceId: body.device_id,
|
|
237
|
+
displayName: body.initial_device_display_name,
|
|
238
|
+
refreshToken: body.refresh_token === true
|
|
239
|
+
});
|
|
195
240
|
await hs.audit.record({
|
|
196
|
-
action: "
|
|
197
|
-
actor:
|
|
241
|
+
action: "login",
|
|
242
|
+
actor: userId,
|
|
243
|
+
target: session.device_id,
|
|
198
244
|
ip: ctx.ip,
|
|
199
|
-
|
|
200
|
-
details: { type:
|
|
245
|
+
userAgent: ctx.request.headers.get("user-agent"),
|
|
246
|
+
details: { type: M.LOGIN.TOKEN }
|
|
201
247
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
248
|
+
return {
|
|
249
|
+
...session,
|
|
250
|
+
home_server: hs.serverName,
|
|
251
|
+
well_known: wellKnown(hs) ?? void 0
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (body.type !== M.LOGIN.PASSWORD || !hs.userAuth.passwordLoginEnabled) throw errors.unknown("Unsupported login type");
|
|
255
|
+
const userId = await resolveLoginUser(hs, body);
|
|
256
|
+
if (body.password === void 0) throw errors.missingParam("Missing password");
|
|
257
|
+
let user = await hs.db.findOne("users", [{
|
|
205
258
|
field: "user_id",
|
|
206
259
|
value: userId
|
|
207
260
|
}]);
|
|
208
|
-
|
|
261
|
+
const ok = await hs.userAuth.checkPassword({
|
|
262
|
+
userId,
|
|
263
|
+
localpart: userId.slice(1).split(":")[0],
|
|
264
|
+
password: body.password,
|
|
265
|
+
storedHash: user?.password_hash ?? null
|
|
266
|
+
});
|
|
267
|
+
const ua = ctx.request.headers.get("user-agent");
|
|
268
|
+
if (!ok) {
|
|
269
|
+
await hs.audit.record({
|
|
270
|
+
action: "login_failed",
|
|
271
|
+
actor: userId,
|
|
272
|
+
ip: ctx.ip,
|
|
273
|
+
userAgent: ua,
|
|
274
|
+
outcome: "failure",
|
|
275
|
+
details: { type: M.LOGIN.PASSWORD }
|
|
276
|
+
});
|
|
277
|
+
throw errors.forbidden("Invalid username or password");
|
|
278
|
+
}
|
|
279
|
+
if (!user) {
|
|
280
|
+
if (!await hs.userAuth.autoProvision(userId)) throw errors.forbidden("Invalid username or password");
|
|
281
|
+
await createUser(hs, userId.slice(1).split(":")[0], {});
|
|
282
|
+
user = await hs.db.findOne("users", [{
|
|
283
|
+
field: "user_id",
|
|
284
|
+
value: userId
|
|
285
|
+
}]);
|
|
286
|
+
}
|
|
287
|
+
if (user.deactivated) throw errors.userDeactivated();
|
|
209
288
|
const session = await hs.auth.createSession(userId, {
|
|
210
289
|
deviceId: body.device_id,
|
|
211
290
|
displayName: body.initial_device_display_name,
|
|
@@ -216,8 +295,8 @@ function registerAccountRoutes(r) {
|
|
|
216
295
|
actor: userId,
|
|
217
296
|
target: session.device_id,
|
|
218
297
|
ip: ctx.ip,
|
|
219
|
-
userAgent:
|
|
220
|
-
details: { type:
|
|
298
|
+
userAgent: ua,
|
|
299
|
+
details: { type: M.LOGIN.PASSWORD }
|
|
221
300
|
});
|
|
222
301
|
return {
|
|
223
302
|
...session,
|
|
@@ -225,141 +304,143 @@ function registerAccountRoutes(r) {
|
|
|
225
304
|
well_known: wellKnown(hs) ?? void 0
|
|
226
305
|
};
|
|
227
306
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
307
|
+
}));
|
|
308
|
+
r.client("POST", "/refresh", procedure({
|
|
309
|
+
input: body({ refresh_token: lenient(string()) }),
|
|
310
|
+
handler: async (ctx) => {
|
|
311
|
+
const body = ctx.input;
|
|
312
|
+
if (body.refresh_token === void 0) throw errors.missingParam("Missing refresh_token");
|
|
313
|
+
const { access_token, refresh_token, expires_in_ms } = await ctx.hs.auth.refresh(body.refresh_token);
|
|
314
|
+
return {
|
|
315
|
+
access_token,
|
|
316
|
+
refresh_token,
|
|
317
|
+
expires_in_ms
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}));
|
|
321
|
+
r.client("POST", "/logout", procedure({
|
|
322
|
+
auth: "user",
|
|
323
|
+
handler: async (ctx) => {
|
|
324
|
+
const s = ctx.session;
|
|
325
|
+
await ctx.hs.auth.deleteDevice(s.userId, s.deviceId);
|
|
326
|
+
await ctx.hs.audit.record({
|
|
327
|
+
action: "logout",
|
|
328
|
+
actor: s.userId,
|
|
329
|
+
target: s.deviceId,
|
|
330
|
+
ip: ctx.ip
|
|
331
|
+
});
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
}));
|
|
335
|
+
r.client("POST", "/logout/all", procedure({
|
|
336
|
+
auth: "user",
|
|
337
|
+
handler: async (ctx) => {
|
|
338
|
+
const s = ctx.session;
|
|
339
|
+
await ctx.hs.auth.deleteAllDevices(s.userId);
|
|
340
|
+
await ctx.hs.audit.record({
|
|
341
|
+
action: "logout",
|
|
342
|
+
actor: s.userId,
|
|
246
343
|
ip: ctx.ip,
|
|
247
|
-
|
|
248
|
-
outcome: "failure",
|
|
249
|
-
details: { type: "m.login.password" }
|
|
344
|
+
details: { all: true }
|
|
250
345
|
});
|
|
251
|
-
|
|
346
|
+
return {};
|
|
252
347
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
348
|
+
}));
|
|
349
|
+
r.client("GET", "/account/whoami", procedure({
|
|
350
|
+
auth: "user",
|
|
351
|
+
handler: async (ctx) => {
|
|
352
|
+
const s = ctx.session;
|
|
353
|
+
return {
|
|
354
|
+
user_id: s.userId,
|
|
355
|
+
...s.deviceId ? { device_id: s.deviceId } : {},
|
|
356
|
+
is_guest: s.isGuest
|
|
357
|
+
};
|
|
260
358
|
}
|
|
261
|
-
|
|
262
|
-
|
|
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) => {
|
|
359
|
+
}));
|
|
360
|
+
r.client("GET", "/register/available", procedure({ handler: async (ctx) => {
|
|
322
361
|
const username = ctx.query.get("username");
|
|
323
362
|
if (!username) throw errors.missingParam("Missing username");
|
|
324
363
|
await validateNewUsername(ctx.hs, username);
|
|
325
364
|
return { available: true };
|
|
326
|
-
});
|
|
327
|
-
r.client("GET", "/register", async (ctx) => ({
|
|
365
|
+
} }));
|
|
366
|
+
r.client("GET", "/register", procedure({ handler: async (ctx) => ({
|
|
328
367
|
flows: (await registrationFlows(ctx.hs)).map((stages) => ({ stages })),
|
|
329
368
|
params: {}
|
|
330
|
-
}));
|
|
331
|
-
r.client("GET", "/register/m.login.registration_token/validity",
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
};
|
|
369
|
+
}) }));
|
|
370
|
+
r.client("GET", "/register/m.login.registration_token/validity", procedure({
|
|
371
|
+
rateLimit: "register",
|
|
372
|
+
handler: async (ctx) => {
|
|
373
|
+
const token = ctx.query.get("token");
|
|
374
|
+
if (!token) throw errors.missingParam("Missing token");
|
|
375
|
+
return { valid: await registrationTokenValid(ctx.hs, token) };
|
|
348
376
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
357
|
-
|
|
377
|
+
}), ["v1"]);
|
|
378
|
+
r.client("POST", "/register", procedure({
|
|
379
|
+
input: registerBody,
|
|
380
|
+
handler: async (ctx) => {
|
|
381
|
+
const hs = ctx.hs;
|
|
382
|
+
const body = ctx.input;
|
|
383
|
+
const kind = ctx.query.get("kind") ?? "user";
|
|
384
|
+
if (kind === "guest") {
|
|
385
|
+
if (!hs.userAuth.guestAccessEnabled) throw errors.forbidden("Guest access is disabled");
|
|
386
|
+
const userId = await createUser(hs, await generateLocalpart(hs), { guest: true });
|
|
387
|
+
return {
|
|
388
|
+
...await hs.auth.createSession(userId, { displayName: body.initial_device_display_name }),
|
|
389
|
+
home_server: hs.serverName
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (kind !== "user") throw errors.invalidParam("Unknown kind");
|
|
393
|
+
if (body.type === M.LOGIN.APPLICATION_SERVICE || body.auth?.type === M.LOGIN.APPLICATION_SERVICE) {
|
|
394
|
+
const s = await ctx.auth();
|
|
395
|
+
if (!s.appservice) throw errors.forbidden("An application service token is required");
|
|
396
|
+
if (body.username === void 0) throw errors.missingParam("Missing username");
|
|
397
|
+
const localpart = body.username.toLowerCase();
|
|
398
|
+
const userId = `@${localpart}:${hs.serverName}`;
|
|
399
|
+
if (!hs.appservices.isInterestedUser(s.appservice, userId)) throw errors.exclusive("User is not in the application service's namespace");
|
|
400
|
+
if (!LOCALPART_RE.test(localpart)) throw errors.invalidUsername();
|
|
401
|
+
if (await hs.db.findOne("users", [{
|
|
402
|
+
field: "user_id",
|
|
403
|
+
value: userId
|
|
404
|
+
}])) throw errors.userInUse();
|
|
405
|
+
await createUser(hs, localpart, {});
|
|
406
|
+
if (body.inhibit_login === true) return {
|
|
407
|
+
user_id: userId,
|
|
408
|
+
home_server: hs.serverName
|
|
409
|
+
};
|
|
410
|
+
return {
|
|
411
|
+
...await hs.auth.createSession(userId, {
|
|
412
|
+
deviceId: body.device_id,
|
|
413
|
+
displayName: body.initial_device_display_name
|
|
414
|
+
}),
|
|
415
|
+
home_server: hs.serverName
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (ctx.accessToken() && hs.appservices.byToken(ctx.accessToken())) throw errors.missingParam("Application services must use type m.login.application_service");
|
|
419
|
+
rejectIfDelegated(hs);
|
|
420
|
+
if (!await hs.registrationAllowed()) throw errors.forbidden("Registration is disabled");
|
|
421
|
+
await ctx.rateLimit("register");
|
|
422
|
+
const localpart = body.username !== void 0 ? await validateNewUsername(hs, body.username.toLowerCase()) : await generateLocalpart(hs);
|
|
423
|
+
if (hs.appservices.exclusiveUserOwner(`@${localpart}:${hs.serverName}`)) throw errors.exclusive("This user ID is reserved by an application service");
|
|
424
|
+
if (body.password !== void 0) {
|
|
425
|
+
const problem = await hs.userAuth.validatePassword(body.password);
|
|
426
|
+
if (problem) throw new MatrixError(400, "M_WEAK_PASSWORD", problem);
|
|
427
|
+
}
|
|
428
|
+
const flows = await registrationFlows(hs);
|
|
429
|
+
const { threepids } = await requireUia(hs, body, flows, null);
|
|
430
|
+
await consumeRegistrationToken(hs, body);
|
|
358
431
|
if (await hs.db.findOne("users", [{
|
|
359
|
-
field: "
|
|
360
|
-
value:
|
|
432
|
+
field: "localpart",
|
|
433
|
+
value: localpart
|
|
361
434
|
}])) throw errors.userInUse();
|
|
362
|
-
await createUser(hs, localpart, {});
|
|
435
|
+
const userId = await createUser(hs, localpart, { password: body.password });
|
|
436
|
+
for (const t of threepids) await hs.threepids.add(userId, t).catch((err) => hs.logger.warn("Could not attach", t.medium, "to", userId, err));
|
|
437
|
+
await hs.audit.record({
|
|
438
|
+
action: "register",
|
|
439
|
+
actor: userId,
|
|
440
|
+
ip: ctx.ip,
|
|
441
|
+
userAgent: ctx.request.headers.get("user-agent"),
|
|
442
|
+
details: { token: body.auth?.type === M.LOGIN.REGISTRATION_TOKEN }
|
|
443
|
+
});
|
|
363
444
|
if (body.inhibit_login === true) return {
|
|
364
445
|
user_id: userId,
|
|
365
446
|
home_server: hs.serverName
|
|
@@ -367,166 +448,138 @@ function registerAccountRoutes(r) {
|
|
|
367
448
|
return {
|
|
368
449
|
...await hs.auth.createSession(userId, {
|
|
369
450
|
deviceId: body.device_id,
|
|
370
|
-
displayName: body.initial_device_display_name
|
|
451
|
+
displayName: body.initial_device_display_name,
|
|
452
|
+
refreshToken: body.refresh_token === true
|
|
371
453
|
}),
|
|
372
454
|
home_server: hs.serverName
|
|
373
455
|
};
|
|
374
456
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
457
|
+
}));
|
|
458
|
+
r.client("POST", "/account/password", procedure({
|
|
459
|
+
input: passwordBody,
|
|
460
|
+
handler: async (ctx) => {
|
|
461
|
+
const hs = ctx.hs;
|
|
462
|
+
rejectIfDelegated(hs);
|
|
463
|
+
const body = ctx.input;
|
|
464
|
+
let s;
|
|
465
|
+
if (ctx.accessToken()) {
|
|
466
|
+
const session = await ctx.auth();
|
|
467
|
+
await requireUia(hs, body, [[M.LOGIN.PASSWORD]], session.userId);
|
|
468
|
+
s = {
|
|
469
|
+
userId: session.userId,
|
|
470
|
+
deviceId: session.deviceId
|
|
471
|
+
};
|
|
472
|
+
} else {
|
|
473
|
+
const { threepids } = await requireUia(hs, body, [[M.LOGIN.EMAIL.IDENTITY], [M.LOGIN.MSISDN]], null);
|
|
474
|
+
const owner = threepids.length ? await hs.threepids.userFor(threepids[0].medium, threepids[0].address) : null;
|
|
475
|
+
if (!owner) throw new MatrixError(400, "M_THREEPID_NOT_FOUND", "No account uses this address");
|
|
476
|
+
s = {
|
|
477
|
+
userId: owner,
|
|
478
|
+
deviceId: null
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (!body.new_password) throw errors.missingParam("Missing new_password");
|
|
482
|
+
const problem = await hs.userAuth.validatePassword(body.new_password);
|
|
383
483
|
if (problem) throw new MatrixError(400, "M_WEAK_PASSWORD", problem);
|
|
484
|
+
await hs.db.update("users", [{
|
|
485
|
+
field: "user_id",
|
|
486
|
+
value: s.userId
|
|
487
|
+
}], { password_hash: await hs.userAuth.hashPassword(body.new_password) });
|
|
488
|
+
await hs.audit.record({
|
|
489
|
+
action: "password_change",
|
|
490
|
+
actor: s.userId,
|
|
491
|
+
ip: ctx.ip,
|
|
492
|
+
details: { logout_devices: body.logout_devices !== false }
|
|
493
|
+
});
|
|
494
|
+
if (body.logout_devices !== false) await hs.auth.deleteAllDevices(s.userId, s.deviceId ?? void 0);
|
|
495
|
+
return {};
|
|
384
496
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
};
|
|
497
|
+
}));
|
|
498
|
+
r.client("POST", "/account/deactivate", procedure({
|
|
499
|
+
input: body({}),
|
|
500
|
+
handler: async (ctx) => {
|
|
501
|
+
const hs = ctx.hs;
|
|
502
|
+
rejectIfDelegated(hs);
|
|
503
|
+
const s = await ctx.auth();
|
|
504
|
+
await requireUia(hs, ctx.input, [[M.LOGIN.PASSWORD]], s.userId);
|
|
505
|
+
for (const membership of [
|
|
506
|
+
"join",
|
|
507
|
+
"invite",
|
|
508
|
+
"knock"
|
|
509
|
+
]) for (const roomId of await hs.rooms.roomsForUser(s.userId, membership)) try {
|
|
510
|
+
await hs.rooms.setMembership(roomId, s.userId, s.userId, "leave");
|
|
511
|
+
} catch {}
|
|
512
|
+
await hs.auth.deleteAllDevices(s.userId);
|
|
513
|
+
await hs.db.update("users", [{
|
|
514
|
+
field: "user_id",
|
|
515
|
+
value: s.userId
|
|
516
|
+
}], {
|
|
517
|
+
deactivated: true,
|
|
518
|
+
password_hash: null,
|
|
519
|
+
displayname: null,
|
|
520
|
+
avatar_url: null
|
|
521
|
+
});
|
|
522
|
+
await hs.db.delete("profile_fields", [{
|
|
523
|
+
field: "user_id",
|
|
524
|
+
value: s.userId
|
|
525
|
+
}]);
|
|
526
|
+
const unbind = await hs.threepids.removeAll(s.userId);
|
|
527
|
+
await hs.audit.record({
|
|
528
|
+
action: "deactivate",
|
|
529
|
+
actor: s.userId,
|
|
530
|
+
target: s.userId,
|
|
531
|
+
ip: ctx.ip
|
|
532
|
+
});
|
|
533
|
+
return { id_server_unbind_result: unbind };
|
|
434
534
|
}
|
|
435
|
-
|
|
436
|
-
|
|
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) => {
|
|
535
|
+
}));
|
|
536
|
+
r.get("/_synapse/admin/v1/register", procedure({ handler: async (ctx) => {
|
|
487
537
|
if (!ctx.hs.config.registrationSharedSecret) throw errors.unrecognized();
|
|
488
538
|
const nonce = randomString(32);
|
|
489
539
|
await ctx.hs.cluster.set(`regnonce:${nonce}`, true, 6e4);
|
|
490
540
|
return { nonce };
|
|
491
|
-
});
|
|
492
|
-
r.post("/_synapse/admin/v1/register",
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
541
|
+
} }));
|
|
542
|
+
r.post("/_synapse/admin/v1/register", procedure({
|
|
543
|
+
input: sharedSecretRegisterBody,
|
|
544
|
+
handler: async (ctx) => {
|
|
545
|
+
const hs = ctx.hs;
|
|
546
|
+
const secret = hs.config.registrationSharedSecret;
|
|
547
|
+
if (!secret) throw errors.unrecognized();
|
|
548
|
+
const body = ctx.input;
|
|
549
|
+
if (body.nonce === void 0 || !await hs.cluster.get(`regnonce:${body.nonce}`)) throw errors.unknown("Unrecognised nonce");
|
|
550
|
+
await hs.cluster.delete(`regnonce:${body.nonce}`);
|
|
551
|
+
const admin = body.admin === true;
|
|
552
|
+
const expected = createHmac("sha1", secret).update(`${body.nonce}\u0000${body.username}\u0000${body.password}\u0000${admin ? "admin" : "notadmin"}`).digest("hex");
|
|
553
|
+
const given = Buffer.from(body.mac ?? "", "utf8");
|
|
554
|
+
if (given.length !== expected.length || !timingSafeEqual(given, Buffer.from(expected, "utf8"))) throw errors.forbidden("HMAC incorrect");
|
|
555
|
+
const userId = await createUser(hs, await validateNewUsername(hs, body.username.toLowerCase()), {
|
|
556
|
+
password: body.password,
|
|
557
|
+
admin,
|
|
558
|
+
displayname: body.displayname
|
|
559
|
+
});
|
|
560
|
+
const session = await hs.auth.createSession(userId);
|
|
561
|
+
await hs.audit.record({
|
|
562
|
+
action: "register",
|
|
563
|
+
actor: "server",
|
|
564
|
+
target: userId,
|
|
565
|
+
ip: ctx.ip,
|
|
566
|
+
details: {
|
|
567
|
+
shared_secret: true,
|
|
568
|
+
admin
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
...session,
|
|
573
|
+
home_server: hs.serverName
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}));
|
|
524
577
|
}
|
|
525
578
|
async function registrationFlows(hs) {
|
|
526
|
-
if (hs.adminRoom?.options.firstUserCanRegister && await hs.adminRoom.noUsersYet()) return [[
|
|
527
|
-
const base = await hs.registrationMode() === "token" ? [
|
|
579
|
+
if (hs.adminRoom?.options.firstUserCanRegister && await hs.adminRoom.noUsersYet()) return [[M.LOGIN.DUMMY]];
|
|
580
|
+
const base = await hs.registrationMode() === "token" ? [M.LOGIN.REGISTRATION_TOKEN] : [M.LOGIN.DUMMY];
|
|
528
581
|
if (!hs.threepids.canValidate("email")) return [base];
|
|
529
|
-
const withEmail = [...base.filter((s) => s !==
|
|
582
|
+
const withEmail = [...base.filter((s) => s !== M.LOGIN.DUMMY), M.LOGIN.EMAIL.IDENTITY];
|
|
530
583
|
return hs.config.threepid.requireEmailForRegistration ? [withEmail] : [base, withEmail];
|
|
531
584
|
}
|
|
532
585
|
async function registrationTokenValid(hs, token) {
|
|
@@ -539,7 +592,7 @@ async function registrationTokenValid(hs, token) {
|
|
|
539
592
|
return row.uses_allowed === null || row.completed < row.uses_allowed;
|
|
540
593
|
}
|
|
541
594
|
async function consumeRegistrationToken(hs, body) {
|
|
542
|
-
if (body.auth?.type !==
|
|
595
|
+
if (body.auth?.type !== M.LOGIN.REGISTRATION_TOKEN) return;
|
|
543
596
|
const token = body.auth?.token;
|
|
544
597
|
const row = await hs.db.findOne("registration_tokens", [{
|
|
545
598
|
field: "token",
|
|
@@ -552,8 +605,8 @@ async function consumeRegistrationToken(hs, body) {
|
|
|
552
605
|
}
|
|
553
606
|
function wellKnown(hs) {
|
|
554
607
|
if (!hs.config.publicBaseUrl) return null;
|
|
555
|
-
const wk = {
|
|
556
|
-
if (hs.config.rtc.transports?.length) wk[
|
|
608
|
+
const wk = { [M.HOMESERVER]: { base_url: hs.config.publicBaseUrl } };
|
|
609
|
+
if (hs.config.rtc.transports?.length) wk[UNSTABLE.ORG.MATRIX.MSC4143.RTC_FOCI] = hs.config.rtc.transports;
|
|
557
610
|
return wk;
|
|
558
611
|
}
|
|
559
612
|
//#endregion
|