matrix-nodejs-homeserver 0.2.0 → 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 (78) hide show
  1. package/README.md +29 -7
  2. package/dist/api/account.js +385 -351
  3. package/dist/api/admin.js +76 -60
  4. package/dist/api/appservice.js +37 -25
  5. package/dist/api/backup.js +73 -62
  6. package/dist/api/federation.js +172 -138
  7. package/dist/api/filters.js +4 -3
  8. package/dist/api/legacy.js +91 -78
  9. package/dist/api/media.js +70 -54
  10. package/dist/api/misc.js +46 -37
  11. package/dist/api/preview.js +4 -3
  12. package/dist/api/profile.js +108 -97
  13. package/dist/api/push.js +114 -99
  14. package/dist/api/relationships.js +64 -58
  15. package/dist/api/rooms.js +703 -580
  16. package/dist/api/rtc.d.ts +0 -1
  17. package/dist/api/rtc.js +48 -34
  18. package/dist/api/search.js +133 -127
  19. package/dist/api/slidingsync.js +41 -31
  20. package/dist/api/spaces.js +82 -77
  21. package/dist/api/sync.js +59 -50
  22. package/dist/api/threadsubs.js +72 -61
  23. package/dist/api/threepid.js +99 -66
  24. package/dist/api/uia.js +17 -16
  25. package/dist/api/user.js +221 -178
  26. package/dist/core/admin.d.ts +0 -1
  27. package/dist/core/admin.js +6 -5
  28. package/dist/core/adminroom.js +9 -8
  29. package/dist/core/appservices.d.ts +2 -1
  30. package/dist/core/appservices.js +5 -4
  31. package/dist/core/audit.d.ts +0 -1
  32. package/dist/core/authrules.js +26 -25
  33. package/dist/core/delayed.js +2 -1
  34. package/dist/core/events.js +11 -10
  35. package/dist/core/invitefilter.js +2 -1
  36. package/dist/core/keys.js +2 -1
  37. package/dist/core/m.d.ts +209 -0
  38. package/dist/core/m.js +182 -0
  39. package/dist/core/partialstate.js +2 -1
  40. package/dist/core/policy.js +4 -3
  41. package/dist/core/push.d.ts +0 -1
  42. package/dist/core/push.js +12 -11
  43. package/dist/core/pushrules.js +36 -35
  44. package/dist/core/rooms.js +76 -74
  45. package/dist/core/stateres.js +7 -6
  46. package/dist/federation/client.js +2 -1
  47. package/dist/federation/index.d.ts +1 -1
  48. package/dist/federation/index.js +30 -29
  49. package/dist/homeserver.d.ts +2 -1
  50. package/dist/homeserver.js +1 -0
  51. package/dist/http/handler.js +2 -23
  52. package/dist/http/input.js +2 -2
  53. package/dist/http/procedure.js +25 -0
  54. package/dist/http/router.d.ts +9 -26
  55. package/dist/http/router.js +10 -30
  56. package/dist/index.d.ts +2 -1
  57. package/dist/index.js +2 -1
  58. package/dist/vendor/zod/LICENSE +21 -0
  59. package/dist/vendor/zod/v4/classic/errors.js +54 -0
  60. package/dist/vendor/zod/v4/classic/parse.js +17 -0
  61. package/dist/vendor/zod/v4/classic/schemas.js +865 -0
  62. package/dist/vendor/zod/v4/core/api.js +512 -0
  63. package/dist/vendor/zod/v4/core/checks.js +300 -0
  64. package/dist/vendor/zod/v4/core/core.js +109 -0
  65. package/dist/vendor/zod/v4/core/doc.js +35 -0
  66. package/dist/vendor/zod/v4/core/errors.js +118 -0
  67. package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
  68. package/dist/vendor/zod/v4/core/memoizer.js +255 -0
  69. package/dist/vendor/zod/v4/core/parse.js +206 -0
  70. package/dist/vendor/zod/v4/core/regexes.js +73 -0
  71. package/dist/vendor/zod/v4/core/registries.js +48 -0
  72. package/dist/vendor/zod/v4/core/schemas.js +1378 -0
  73. package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
  74. package/dist/vendor/zod/v4/core/util.js +537 -0
  75. package/dist/vendor/zod/v4/core/versions.js +8 -0
  76. package/dist/vendor/zod/v4/locales/en.js +110 -0
  77. package/package.json +3 -5
  78. package/dist/core/pushrules.d.ts +0 -1
package/dist/api/admin.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { errors } from "../core/errors.js";
2
+ import { boolean, looseObject, number, string } from "../vendor/zod/v4/classic/schemas.js";
2
3
  import { body, fail, lenient } from "../http/input.js";
3
- import { z } from "zod";
4
+ import { procedure } from "../http/procedure.js";
4
5
  //#region src/api/admin.ts
5
6
  const required = fail("M_BAD_JSON", "user_id and content are required");
6
7
  const noticeBody = body({
7
- user_id: z.string(required),
8
- content: z.looseObject({}, required),
9
- type: lenient(z.string())
8
+ user_id: string(required),
9
+ content: looseObject({}, required),
10
+ type: lenient(string())
10
11
  });
11
- const optionalInt = (name) => z.number(fail("M_INVALID_PARAM", `${name} must be an integer`)).int(`M_INVALID_PARAM: ${name} must be an integer`);
12
+ const optionalInt = (name) => number(fail("M_INVALID_PARAM", `${name} must be an integer`)).int(`M_INVALID_PARAM: ${name} must be an integer`);
12
13
  const registrationTokenBody = body({
13
- token: z.string(fail("M_INVALID_PARAM", "Invalid token")).optional(),
14
+ token: string(fail("M_INVALID_PARAM", "Invalid token")).optional(),
14
15
  uses_allowed: optionalInt("uses_allowed").nullable().optional(),
15
16
  expiry_time: optionalInt("expiry_time").nullable().optional(),
16
17
  length: optionalInt("length").optional()
@@ -65,13 +66,19 @@ function registerAdminRoutes(r) {
65
66
  };
66
67
  };
67
68
  const lock = moderation("locked"), suspend = moderation("suspended");
68
- r.get("/_matrix/client/v1/admin/lock/:userId", lock.get);
69
- r.put("/_matrix/client/v1/admin/lock/:userId", lock.put);
70
- r.get("/_matrix/client/v1/admin/suspend/:userId", suspend.get);
71
- r.put("/_matrix/client/v1/admin/suspend/:userId", suspend.put);
72
- r.post("/_synapse/admin/v1/send_server_notice", { input: noticeBody }, notice);
73
- r.put("/_synapse/admin/v1/send_server_notice/:txnId", { input: noticeBody }, notice);
74
- r.get("/_synapse/admin/v2/users", async (ctx) => {
69
+ r.get("/_matrix/client/v1/admin/lock/:userId", procedure({ handler: lock.get }));
70
+ r.put("/_matrix/client/v1/admin/lock/:userId", procedure({ handler: lock.put }));
71
+ r.get("/_matrix/client/v1/admin/suspend/:userId", procedure({ handler: suspend.get }));
72
+ r.put("/_matrix/client/v1/admin/suspend/:userId", procedure({ handler: suspend.put }));
73
+ r.post("/_synapse/admin/v1/send_server_notice", procedure({
74
+ input: noticeBody,
75
+ handler: notice
76
+ }));
77
+ r.put("/_synapse/admin/v1/send_server_notice/:txnId", procedure({
78
+ input: noticeBody,
79
+ handler: notice
80
+ }));
81
+ r.get("/_synapse/admin/v2/users", procedure({ handler: async (ctx) => {
75
82
  await requireAdmin(ctx);
76
83
  const from = Number(ctx.query.get("from") ?? 0) || 0;
77
84
  const res = await ctx.hs.admin.listUsers({
@@ -85,14 +92,14 @@ function registerAdminRoutes(r) {
85
92
  total: res.total,
86
93
  ...res.next !== null ? { next_token: String(res.next) } : {}
87
94
  };
88
- });
89
- r.get("/_synapse/admin/v2/users/:userId", async (ctx) => {
95
+ } }));
96
+ r.get("/_synapse/admin/v2/users/:userId", procedure({ handler: async (ctx) => {
90
97
  await requireAdmin(ctx);
91
98
  const user = await ctx.hs.admin.getUser(ctx.params.userId);
92
99
  if (!user) throw errors.notFound("Unknown user");
93
100
  return user;
94
- });
95
- r.put("/_synapse/admin/v2/users/:userId", async (ctx) => {
101
+ } }));
102
+ r.put("/_synapse/admin/v2/users/:userId", procedure({ handler: async (ctx) => {
96
103
  await requireAdmin(ctx);
97
104
  const body = await ctx.json();
98
105
  const userId = ctx.params.userId;
@@ -119,43 +126,52 @@ function registerAdminRoutes(r) {
119
126
  }
120
127
  if (body.deactivated === true) await ctx.hs.admin.deactivateUser(userId);
121
128
  return ctx.hs.admin.getUser(userId);
122
- });
123
- r.post("/_synapse/admin/v1/deactivate/:userId", { input: body({ erase: lenient(z.boolean()) }) }, async (ctx) => {
124
- await requireAdmin(ctx);
125
- const body = ctx.input;
126
- await ctx.hs.admin.deactivateUser(ctx.params.userId, { erase: body.erase === true });
127
- return { id_server_unbind_result: "no-support" };
128
- });
129
+ } }));
130
+ r.post("/_synapse/admin/v1/deactivate/:userId", procedure({
131
+ input: body({ erase: lenient(boolean()) }),
132
+ handler: async (ctx) => {
133
+ await requireAdmin(ctx);
134
+ const body = ctx.input;
135
+ await ctx.hs.admin.deactivateUser(ctx.params.userId, { erase: body.erase === true });
136
+ return { id_server_unbind_result: "no-support" };
137
+ }
138
+ }));
129
139
  const resetPasswordBody = body({
130
- new_password: z.string(fail("M_MISSING_PARAM", "Missing new_password")),
131
- logout_devices: lenient(z.boolean())
140
+ new_password: string(fail("M_MISSING_PARAM", "Missing new_password")),
141
+ logout_devices: lenient(boolean())
132
142
  });
133
- r.post("/_synapse/admin/v1/reset_password/:userId", { input: resetPasswordBody }, async (ctx) => {
134
- await requireAdmin(ctx);
135
- const body = ctx.input;
136
- await ctx.hs.admin.resetPassword(ctx.params.userId, body.new_password, body.logout_devices !== false);
137
- return {};
138
- });
139
- r.get("/_synapse/admin/v1/registration_tokens", async (ctx) => {
143
+ r.post("/_synapse/admin/v1/reset_password/:userId", procedure({
144
+ input: resetPasswordBody,
145
+ handler: async (ctx) => {
146
+ await requireAdmin(ctx);
147
+ const body = ctx.input;
148
+ await ctx.hs.admin.resetPassword(ctx.params.userId, body.new_password, body.logout_devices !== false);
149
+ return {};
150
+ }
151
+ }));
152
+ r.get("/_synapse/admin/v1/registration_tokens", procedure({ handler: async (ctx) => {
140
153
  await requireAdmin(ctx);
141
154
  return { registration_tokens: await ctx.hs.admin.listRegistrationTokens() };
142
- });
143
- r.post("/_synapse/admin/v1/registration_tokens/new", { input: registrationTokenBody }, async (ctx) => {
144
- await requireAdmin(ctx);
145
- const body = ctx.input;
146
- return ctx.hs.admin.createRegistrationToken({
147
- token: body.token,
148
- usesAllowed: body.uses_allowed,
149
- expiryTime: body.expiry_time,
150
- length: body.length
151
- });
152
- });
153
- r.delete("/_synapse/admin/v1/registration_tokens/:token", async (ctx) => {
155
+ } }));
156
+ r.post("/_synapse/admin/v1/registration_tokens/new", procedure({
157
+ input: registrationTokenBody,
158
+ handler: async (ctx) => {
159
+ await requireAdmin(ctx);
160
+ const body = ctx.input;
161
+ return ctx.hs.admin.createRegistrationToken({
162
+ token: body.token,
163
+ usesAllowed: body.uses_allowed,
164
+ expiryTime: body.expiry_time,
165
+ length: body.length
166
+ });
167
+ }
168
+ }));
169
+ r.delete("/_synapse/admin/v1/registration_tokens/:token", procedure({ handler: async (ctx) => {
154
170
  await requireAdmin(ctx);
155
171
  await ctx.hs.admin.deleteRegistrationToken(ctx.params.token);
156
172
  return {};
157
- });
158
- r.get("/_synapse/admin/v1/audit_log", async (ctx) => {
173
+ } }));
174
+ r.get("/_synapse/admin/v1/audit_log", procedure({ handler: async (ctx) => {
159
175
  await requireAdmin(ctx);
160
176
  if (!ctx.hs.audit.enabled) throw errors.unrecognized("The audit log is disabled");
161
177
  const rows = await ctx.hs.audit.query({
@@ -169,8 +185,8 @@ function registerAdminRoutes(r) {
169
185
  const out = { entries: rows };
170
186
  if (rows.length) out.next_token = String(rows[rows.length - 1].id);
171
187
  return out;
172
- });
173
- r.get("/_synapse/admin/v1/event_reports", async (ctx) => {
188
+ } }));
189
+ r.get("/_synapse/admin/v1/event_reports", procedure({ handler: async (ctx) => {
174
190
  await requireAdmin(ctx);
175
191
  const rows = await ctx.hs.db.findMany("event_reports", {
176
192
  sortBy: {
@@ -192,16 +208,16 @@ function registerAdminRoutes(r) {
192
208
  })),
193
209
  total: rows.length
194
210
  };
195
- });
196
- r.post("/_synapse/admin/v1/event_reports/:id/resolve", async (ctx) => {
211
+ } }));
212
+ r.post("/_synapse/admin/v1/event_reports/:id/resolve", procedure({ handler: async (ctx) => {
197
213
  await requireAdmin(ctx);
198
214
  if (!await ctx.hs.db.update("event_reports", [{
199
215
  field: "id",
200
216
  value: Number(ctx.params.id)
201
217
  }], { resolved: true })) throw errors.notFound("Unknown report");
202
218
  return {};
203
- });
204
- r.get("/_synapse/admin/v1/rooms", async (ctx) => {
219
+ } }));
220
+ r.get("/_synapse/admin/v1/rooms", procedure({ handler: async (ctx) => {
205
221
  await requireAdmin(ctx);
206
222
  const res = await ctx.hs.admin.listRooms({
207
223
  from: Number(ctx.query.get("from") ?? 0) || 0,
@@ -211,8 +227,8 @@ function registerAdminRoutes(r) {
211
227
  rooms: res.rooms,
212
228
  total_rooms: res.total
213
229
  };
214
- });
215
- r.delete("/_synapse/admin/v1/rooms/:roomId", async (ctx) => {
230
+ } }));
231
+ r.delete("/_synapse/admin/v1/rooms/:roomId", procedure({ handler: async (ctx) => {
216
232
  const s = await requireAdmin(ctx);
217
233
  const body = await ctx.json();
218
234
  const { kicked } = await ctx.hs.admin.deleteRoom(ctx.params.roomId, {
@@ -226,8 +242,8 @@ function registerAdminRoutes(r) {
226
242
  local_aliases: [],
227
243
  new_room_id: null
228
244
  };
229
- });
230
- r.delete("/_synapse/admin/v1/media/:serverName/:mediaId", async (ctx) => {
245
+ } }));
246
+ r.delete("/_synapse/admin/v1/media/:serverName/:mediaId", procedure({ handler: async (ctx) => {
231
247
  await requireAdmin(ctx);
232
248
  if (ctx.params.serverName !== ctx.hs.serverName) throw errors.notFound("Remote media");
233
249
  await ctx.hs.admin.deleteMedia(ctx.params.mediaId);
@@ -235,8 +251,8 @@ function registerAdminRoutes(r) {
235
251
  deleted_media: [ctx.params.mediaId],
236
252
  total: 1
237
253
  };
238
- });
239
- r.post("/_synapse/admin/v1/media/delete", async (ctx) => {
254
+ } }));
255
+ r.post("/_synapse/admin/v1/media/delete", procedure({ handler: async (ctx) => {
240
256
  await requireAdmin(ctx);
241
257
  const before = Number(ctx.query.get("before_ts"));
242
258
  if (!Number.isFinite(before)) throw errors.missingParam("Missing before_ts");
@@ -245,7 +261,7 @@ function registerAdminRoutes(r) {
245
261
  deleted_media: deleted,
246
262
  total: deleted.length
247
263
  };
248
- });
264
+ } }));
249
265
  }
250
266
  //#endregion
251
267
  export { registerAdminRoutes };
@@ -1,32 +1,44 @@
1
1
  import { errors } from "../core/errors.js";
2
+ import { procedure } from "../http/procedure.js";
2
3
  //#region src/api/appservice.ts
3
4
  function registerAppServiceRoutes(r) {
4
- r.client("POST", "/appservice/:appserviceId/ping", async (ctx) => {
5
- const s = await ctx.auth();
6
- if (!s.appservice) throw errors.forbidden("An application service token is required");
7
- if (s.appservice.id !== ctx.params.appserviceId) throw errors.forbidden("Token does not belong to this application service");
8
- const body = await ctx.json();
9
- return { duration_ms: await ctx.hs.appservices.ping(s.appservice, typeof body.transaction_id === "string" ? body.transaction_id : void 0) };
10
- }, ["v1"]);
11
- r.client("GET", "/thirdparty/protocols", async (ctx) => {
12
- await ctx.auth();
13
- return ctx.hs.appservices.protocols();
14
- });
15
- r.client("GET", "/thirdparty/protocol/:protocol", async (ctx) => {
16
- await ctx.auth();
17
- const p = (await ctx.hs.appservices.protocols())[ctx.params.protocol];
18
- if (!p) throw errors.notFound("Unknown protocol");
19
- return p;
20
- });
5
+ r.client("POST", "/appservice/:appserviceId/ping", procedure({
6
+ auth: "user",
7
+ handler: async (ctx) => {
8
+ const s = ctx.session;
9
+ if (!s.appservice) throw errors.forbidden("An application service token is required");
10
+ if (s.appservice.id !== ctx.params.appserviceId) throw errors.forbidden("Token does not belong to this application service");
11
+ const body = await ctx.json();
12
+ return { duration_ms: await ctx.hs.appservices.ping(s.appservice, typeof body.transaction_id === "string" ? body.transaction_id : void 0) };
13
+ }
14
+ }), ["v1"]);
15
+ r.client("GET", "/thirdparty/protocols", procedure({
16
+ auth: "user",
17
+ handler: async (ctx) => {
18
+ return ctx.hs.appservices.protocols();
19
+ }
20
+ }));
21
+ r.client("GET", "/thirdparty/protocol/:protocol", procedure({
22
+ auth: "user",
23
+ handler: async (ctx) => {
24
+ const p = (await ctx.hs.appservices.protocols())[ctx.params.protocol];
25
+ if (!p) throw errors.notFound("Unknown protocol");
26
+ return p;
27
+ }
28
+ }));
21
29
  for (const kind of ["location", "user"]) {
22
- r.client("GET", `/thirdparty/${kind}/:protocol`, async (ctx) => {
23
- await ctx.auth();
24
- return ctx.hs.appservices.thirdParty(`${kind}/${encodeURIComponent(ctx.params.protocol)}`, ctx.query);
25
- });
26
- r.client("GET", `/thirdparty/${kind}`, async (ctx) => {
27
- await ctx.auth();
28
- return ctx.hs.appservices.thirdParty(kind, ctx.query);
29
- });
30
+ r.client("GET", `/thirdparty/${kind}/:protocol`, procedure({
31
+ auth: "user",
32
+ handler: async (ctx) => {
33
+ return ctx.hs.appservices.thirdParty(`${kind}/${encodeURIComponent(ctx.params.protocol)}`, ctx.query);
34
+ }
35
+ }));
36
+ r.client("GET", `/thirdparty/${kind}`, procedure({
37
+ auth: "user",
38
+ handler: async (ctx) => {
39
+ return ctx.hs.appservices.thirdParty(kind, ctx.query);
40
+ }
41
+ }));
30
42
  }
31
43
  }
32
44
  //#endregion
@@ -1,12 +1,13 @@
1
1
  import { isObject } from "../core/json.js";
2
2
  import { MatrixError, errors } from "../core/errors.js";
3
+ import { looseObject, string } from "../vendor/zod/v4/classic/schemas.js";
3
4
  import { body, fail } from "../http/input.js";
4
- import { z } from "zod";
5
+ import { procedure } from "../http/procedure.js";
5
6
  //#region src/api/backup.ts
6
7
  const required = fail("M_BAD_JSON", "algorithm and auth_data are required");
7
8
  const createVersionBody = body({
8
- algorithm: z.string(required),
9
- auth_data: z.looseObject({}, required)
9
+ algorithm: string(required),
10
+ auth_data: looseObject({}, required)
10
11
  });
11
12
  /** Server-side room key backups (`/room_keys`). */
12
13
  async function latestVersion(hs, userId) {
@@ -74,68 +75,78 @@ async function requireCurrentVersion(ctx, userId) {
74
75
  };
75
76
  }
76
77
  function registerBackupRoutes(r) {
77
- r.client("POST", "/room_keys/version", { input: createVersionBody }, async (ctx) => {
78
- const s = await ctx.auth();
79
- const body = ctx.input;
80
- const max = (await ctx.hs.db.findMany("backup_versions", { where: [{
81
- field: "user_id",
82
- value: s.userId
83
- }] })).reduce((m, v) => Math.max(m, v.version), 0);
84
- await ctx.hs.db.create("backup_versions", {
85
- user_id: s.userId,
86
- version: max + 1,
87
- algorithm: body.algorithm,
88
- auth_data: body.auth_data,
89
- etag: 0,
90
- deleted: false
91
- });
92
- return { version: String(max + 1) };
93
- });
78
+ r.client("POST", "/room_keys/version", procedure({
79
+ auth: "user",
80
+ input: createVersionBody,
81
+ handler: async (ctx) => {
82
+ const s = ctx.session;
83
+ const body = ctx.input;
84
+ const max = (await ctx.hs.db.findMany("backup_versions", { where: [{
85
+ field: "user_id",
86
+ value: s.userId
87
+ }] })).reduce((m, v) => Math.max(m, v.version), 0);
88
+ await ctx.hs.db.create("backup_versions", {
89
+ user_id: s.userId,
90
+ version: max + 1,
91
+ algorithm: body.algorithm,
92
+ auth_data: body.auth_data,
93
+ etag: 0,
94
+ deleted: false
95
+ });
96
+ return { version: String(max + 1) };
97
+ }
98
+ }));
94
99
  const getVersionHandler = async (ctx) => {
95
100
  const s = await ctx.auth();
96
101
  const row = await getVersion(ctx.hs, s.userId, ctx.params.version ?? null);
97
102
  if (!row) throw errors.notFound("No backup found");
98
103
  return describe(ctx.hs, row);
99
104
  };
100
- r.client("GET", "/room_keys/version", getVersionHandler);
101
- r.client("GET", "/room_keys/version/:version", getVersionHandler);
102
- r.client("PUT", "/room_keys/version/:version", async (ctx) => {
103
- const s = await ctx.auth();
104
- const body = await ctx.json();
105
- const row = await getVersion(ctx.hs, s.userId, ctx.params.version);
106
- if (!row) throw errors.notFound("Unknown backup version");
107
- if (body.version !== void 0 && String(body.version) !== ctx.params.version) throw errors.invalidParam("Version mismatch");
108
- if (body.algorithm !== row.algorithm) throw errors.invalidParam("Algorithm cannot be changed");
109
- if (!isObject(body.auth_data)) throw errors.badJson("auth_data is required");
110
- await ctx.hs.db.update("backup_versions", [{
111
- field: "user_id",
112
- value: s.userId
113
- }, {
114
- field: "version",
115
- value: row.version
116
- }], { auth_data: body.auth_data });
117
- return {};
118
- });
119
- r.client("DELETE", "/room_keys/version/:version", async (ctx) => {
120
- const s = await ctx.auth();
121
- const row = await getVersion(ctx.hs, s.userId, ctx.params.version);
122
- if (!row) throw errors.notFound("Unknown backup version");
123
- await ctx.hs.db.update("backup_versions", [{
124
- field: "user_id",
125
- value: s.userId
126
- }, {
127
- field: "version",
128
- value: row.version
129
- }], { deleted: true });
130
- await ctx.hs.db.delete("backup_keys", [{
131
- field: "user_id",
132
- value: s.userId
133
- }, {
134
- field: "version",
135
- value: row.version
136
- }]);
137
- return {};
138
- });
105
+ r.client("GET", "/room_keys/version", procedure({ handler: getVersionHandler }));
106
+ r.client("GET", "/room_keys/version/:version", procedure({ handler: getVersionHandler }));
107
+ r.client("PUT", "/room_keys/version/:version", procedure({
108
+ auth: "user",
109
+ handler: async (ctx) => {
110
+ const s = ctx.session;
111
+ const body = await ctx.json();
112
+ const row = await getVersion(ctx.hs, s.userId, ctx.params.version);
113
+ if (!row) throw errors.notFound("Unknown backup version");
114
+ if (body.version !== void 0 && String(body.version) !== ctx.params.version) throw errors.invalidParam("Version mismatch");
115
+ if (body.algorithm !== row.algorithm) throw errors.invalidParam("Algorithm cannot be changed");
116
+ if (!isObject(body.auth_data)) throw errors.badJson("auth_data is required");
117
+ await ctx.hs.db.update("backup_versions", [{
118
+ field: "user_id",
119
+ value: s.userId
120
+ }, {
121
+ field: "version",
122
+ value: row.version
123
+ }], { auth_data: body.auth_data });
124
+ return {};
125
+ }
126
+ }));
127
+ r.client("DELETE", "/room_keys/version/:version", procedure({
128
+ auth: "user",
129
+ handler: async (ctx) => {
130
+ const s = ctx.session;
131
+ const row = await getVersion(ctx.hs, s.userId, ctx.params.version);
132
+ if (!row) throw errors.notFound("Unknown backup version");
133
+ await ctx.hs.db.update("backup_versions", [{
134
+ field: "user_id",
135
+ value: s.userId
136
+ }, {
137
+ field: "version",
138
+ value: row.version
139
+ }], { deleted: true });
140
+ await ctx.hs.db.delete("backup_keys", [{
141
+ field: "user_id",
142
+ value: s.userId
143
+ }, {
144
+ field: "version",
145
+ value: row.version
146
+ }]);
147
+ return {};
148
+ }
149
+ }));
139
150
  const putKeys = async (ctx) => {
140
151
  const s = await ctx.auth();
141
152
  const hs = ctx.hs;
@@ -260,9 +271,9 @@ function registerBackupRoutes(r) {
260
271
  "/room_keys/keys/:roomId",
261
272
  "/room_keys/keys/:roomId/:sessionId"
262
273
  ]) {
263
- r.client("PUT", path, putKeys);
264
- r.client("GET", path, getKeys);
265
- r.client("DELETE", path, deleteKeys);
274
+ r.client("PUT", path, procedure({ handler: putKeys }));
275
+ r.client("GET", path, procedure({ handler: getKeys }));
276
+ r.client("DELETE", path, procedure({ handler: deleteKeys }));
266
277
  }
267
278
  }
268
279
  //#endregion