@vex-chat/spire 0.7.4 → 1.0.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 (202) hide show
  1. package/README.md +82 -26
  2. package/dist/ClientManager.d.ts +24 -25
  3. package/dist/ClientManager.js +232 -509
  4. package/dist/ClientManager.js.map +1 -0
  5. package/dist/Database.d.ts +49 -41
  6. package/dist/Database.js +698 -716
  7. package/dist/Database.js.map +1 -0
  8. package/dist/Spire.d.ts +23 -15
  9. package/dist/Spire.js +518 -218
  10. package/dist/Spire.js.map +1 -0
  11. package/dist/__tests__/Database.spec.js +113 -73
  12. package/dist/__tests__/Database.spec.js.map +1 -0
  13. package/dist/db/schema.d.ts +134 -0
  14. package/dist/db/schema.js +2 -0
  15. package/dist/db/schema.js.map +1 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +3 -5
  18. package/dist/index.js.map +1 -0
  19. package/dist/middleware/validate.d.ts +12 -0
  20. package/dist/middleware/validate.js +35 -0
  21. package/dist/middleware/validate.js.map +1 -0
  22. package/dist/migrations/2026-04-06_initial-schema.d.ts +3 -0
  23. package/dist/migrations/2026-04-06_initial-schema.js +192 -0
  24. package/dist/migrations/2026-04-06_initial-schema.js.map +1 -0
  25. package/dist/run.js +26 -21
  26. package/dist/run.js.map +1 -0
  27. package/dist/server/avatar.d.ts +3 -4
  28. package/dist/server/avatar.js +85 -67
  29. package/dist/server/avatar.js.map +1 -0
  30. package/dist/server/errors.d.ts +59 -0
  31. package/dist/server/errors.js +94 -0
  32. package/dist/server/errors.js.map +1 -0
  33. package/dist/server/file.d.ts +3 -4
  34. package/dist/server/file.js +101 -61
  35. package/dist/server/file.js.map +1 -0
  36. package/dist/server/index.d.ts +9 -6
  37. package/dist/server/index.js +595 -70
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/invite.d.ts +4 -5
  40. package/dist/server/invite.js +21 -103
  41. package/dist/server/invite.js.map +1 -0
  42. package/dist/server/openapi.d.ts +2 -0
  43. package/dist/server/openapi.js +40 -0
  44. package/dist/server/openapi.js.map +1 -0
  45. package/dist/server/permissions.d.ts +16 -0
  46. package/dist/server/permissions.js +22 -0
  47. package/dist/server/permissions.js.map +1 -0
  48. package/dist/server/rateLimit.d.ts +28 -0
  49. package/dist/server/rateLimit.js +58 -0
  50. package/dist/server/rateLimit.js.map +1 -0
  51. package/dist/server/user.d.ts +4 -7
  52. package/dist/server/user.js +66 -76
  53. package/dist/server/user.js.map +1 -0
  54. package/dist/server/utils.d.ts +35 -7
  55. package/dist/server/utils.js +50 -6
  56. package/dist/server/utils.js.map +1 -0
  57. package/dist/types/express.d.ts +20 -0
  58. package/dist/types/express.js +2 -0
  59. package/dist/types/express.js.map +1 -0
  60. package/dist/utils/createLogger.js +13 -19
  61. package/dist/utils/createLogger.js.map +1 -0
  62. package/dist/utils/createUint8UUID.js +6 -10
  63. package/dist/utils/createUint8UUID.js.map +1 -0
  64. package/dist/utils/jwtSecret.d.ts +7 -0
  65. package/dist/utils/jwtSecret.js +15 -0
  66. package/dist/utils/jwtSecret.js.map +1 -0
  67. package/dist/utils/loadEnv.js +7 -22
  68. package/dist/utils/loadEnv.js.map +1 -0
  69. package/dist/utils/msgpack.d.ts +2 -0
  70. package/dist/utils/msgpack.js +4 -0
  71. package/dist/utils/msgpack.js.map +1 -0
  72. package/package.json +91 -63
  73. package/src/ClientManager.ts +434 -0
  74. package/src/Database.ts +925 -0
  75. package/src/Spire.ts +878 -0
  76. package/src/__tests__/Database.spec.ts +167 -0
  77. package/src/ambient-modules.d.ts +1 -0
  78. package/src/db/schema.ts +165 -0
  79. package/src/index.ts +3 -0
  80. package/src/middleware/validate.ts +38 -0
  81. package/src/migrations/2026-04-06_initial-schema.ts +218 -0
  82. package/src/run.ts +37 -0
  83. package/src/server/avatar.ts +141 -0
  84. package/src/server/errors.ts +133 -0
  85. package/src/server/file.ts +172 -0
  86. package/src/server/index.ts +855 -0
  87. package/src/server/invite.ts +65 -0
  88. package/src/server/openapi.ts +51 -0
  89. package/src/server/permissions.ts +40 -0
  90. package/src/server/rateLimit.ts +86 -0
  91. package/src/server/user.ts +125 -0
  92. package/src/server/utils.ts +59 -0
  93. package/src/types/express.ts +23 -0
  94. package/src/utils/createLogger.ts +47 -0
  95. package/src/utils/createUint8UUID.ts +9 -0
  96. package/src/utils/jwtSecret.ts +16 -0
  97. package/src/utils/loadEnv.ts +15 -0
  98. package/src/utils/msgpack.ts +4 -0
  99. package/avatars/169d76cb-6e7c-4e24-8224-017673eed8ff +0 -0
  100. package/avatars/1cae8d0b-0c6b-4c73-b25c-2d2349a57122 +0 -0
  101. package/avatars/1d87bc2b-71fc-4818-8004-40d04093e5fc +0 -0
  102. package/avatars/1f2c3d62-8b4d-465a-9895-51a24caef00d +0 -0
  103. package/avatars/245ee7fc-1004-41ab-adab-a04c9ceb9d7a +0 -0
  104. package/avatars/2465c28c-bdaf-4fa2-b42a-d054f04dc39b +0 -0
  105. package/avatars/3900a674-a2dd-4996-a61d-8c29b3270f41 +0 -0
  106. package/avatars/3c3b9c77-ea50-45e7-bb25-65d6f3d2a219 +0 -0
  107. package/avatars/414a2ff4-ad2f-4a7d-aa27-3b09ad3522b2 +0 -0
  108. package/avatars/522fe504-f0ad-4ed4-9dc6-e5dc2338e531 +0 -0
  109. package/avatars/53e5eb29-e7d1-4d58-add9-d44f39f0cfb7 +0 -0
  110. package/avatars/54d3f757-1038-41c8-bfb9-efd37b6e8ebe +0 -0
  111. package/avatars/623e86d7-c49c-46f6-9b76-ca70c9dbc43b +0 -0
  112. package/avatars/66e2abae-60f5-4dd1-b9fb-297a4bedfeb0 +0 -0
  113. package/avatars/6f37980e-f6fa-4d6d-9206-24050b403f45 +0 -0
  114. package/avatars/80138ece-eb5c-4b20-817b-903d6b0f54ae +0 -0
  115. package/avatars/841c77d3-37c4-431b-be22-672888062874 +0 -0
  116. package/avatars/88051a61-2bda-4750-95a3-5fb0b4918149 +0 -0
  117. package/avatars/89540973-c421-4bf1-8b89-9f1eaa51c1b5 +0 -0
  118. package/avatars/8a802fad-8c99-4942-8f80-47cec600149c +0 -0
  119. package/avatars/90531d8a-907a-4a1a-ac45-c85e4acb0df9 +0 -0
  120. package/avatars/9b7d0da9-b8d6-4801-b128-9993f79f464a +0 -0
  121. package/avatars/9bc456f1-c4c4-48a1-b9e6-fd44dd744a72 +0 -0
  122. package/avatars/9cf878bf-7430-49ec-a47a-78f3c93793dd +0 -0
  123. package/avatars/9ee82847-6ad3-45e5-92b9-f474a6c54d96 +0 -0
  124. package/avatars/ab44c857-d81d-4c88-85db-32f9532e5376 +0 -0
  125. package/avatars/b396a8d2-dc14-48d2-aac4-3755dc637051 +0 -0
  126. package/avatars/b6ac11c5-a8b2-4e0a-995a-9c87f1e58787 +0 -0
  127. package/avatars/b79d6855-b738-434c-be32-809637e62b9b +0 -0
  128. package/avatars/bbcd0188-d6a5-48ae-90fb-be5ff30599ab +0 -0
  129. package/avatars/bc7a9e0e-4720-4a6e-a90d-c11fec94d380 +0 -0
  130. package/avatars/c1c4889f-8383-4041-8bdd-9fded4046f37 +0 -0
  131. package/avatars/c4c7203c-d93a-4749-ade2-17053acf1d2a +0 -0
  132. package/avatars/ca974a70-0a23-4668-8b80-c4304dc7f793 +0 -0
  133. package/avatars/cf119a0d-eb3f-4bed-905b-f14a876c3535 +0 -0
  134. package/avatars/d464b03d-30c2-49e3-a666-80aefa8a1b35 +0 -0
  135. package/avatars/da0eee89-82f0-4d45-ab48-7d2786b634c5 +0 -0
  136. package/avatars/de4c77a5-68e9-4bb2-b40d-d964bf377d61 +0 -0
  137. package/avatars/dea95395-7d0b-42aa-a9ed-40c7d4fb4c48 +0 -0
  138. package/avatars/edb30749-59ba-4aa2-9c52-0fb22048f4cf +0 -0
  139. package/avatars/f17c245a-af7d-445b-9365-49f7f54b1eeb +0 -0
  140. package/avatars/f1ee6a35-b262-4dbf-99f5-3d011e3b98ec +0 -0
  141. package/avatars/f802bdd0-345d-41f6-9184-0f30e1258fb3 +0 -0
  142. package/dist/migrations/20210103192527_users.d.ts +0 -3
  143. package/dist/migrations/20210103192527_users.js +0 -30
  144. package/dist/migrations/20210103193502_mail.d.ts +0 -3
  145. package/dist/migrations/20210103193502_mail.js +0 -35
  146. package/dist/migrations/20210103193525_preKeys.d.ts +0 -3
  147. package/dist/migrations/20210103193525_preKeys.js +0 -30
  148. package/dist/migrations/20210103193553_oneTimeKeys.d.ts +0 -3
  149. package/dist/migrations/20210103193553_oneTimeKeys.js +0 -30
  150. package/dist/migrations/20210103193615_servers.d.ts +0 -3
  151. package/dist/migrations/20210103193615_servers.js +0 -28
  152. package/dist/migrations/20210103193729_channels.d.ts +0 -3
  153. package/dist/migrations/20210103193729_channels.js +0 -28
  154. package/dist/migrations/20210103193749_permissions.d.ts +0 -3
  155. package/dist/migrations/20210103193749_permissions.js +0 -30
  156. package/dist/migrations/20210103193801_files.d.ts +0 -3
  157. package/dist/migrations/20210103193801_files.js +0 -28
  158. package/files/00ea7368-45a7-4f6a-a199-974da14be1a0 +0 -0
  159. package/files/039503d2-a170-4962-b921-c97994ba64ff +0 -0
  160. package/files/14b6fa02-4cbb-40df-be4f-a07187cb619e +0 -0
  161. package/files/15c04cb1-dc6a-4f19-aa6f-4a3b92d05bf7 +0 -0
  162. package/files/2a8d411c-8b92-4532-b84d-d64c638d6293 +0 -0
  163. package/files/37de2cd3-08a8-4044-9c37-e13386765f3d +0 -0
  164. package/files/42452029-284e-4c81-9f18-feb6ce309eed +0 -0
  165. package/files/43d09f2f-29c8-415f-8c4a-23f2a32eb79e +0 -0
  166. package/files/52992923-33ab-44a1-8118-605e9b4856a7 +0 -0
  167. package/files/53180681-36e2-49c0-8382-94dca0da09bf +0 -0
  168. package/files/5a56cd7b-1d04-4619-b60f-1e5515b9164a +0 -0
  169. package/files/5ced7676-20c4-4219-a4f2-70a25eb7eea8 +0 -0
  170. package/files/60e787ff-8ec5-444d-b963-0aaf5313b53e +0 -0
  171. package/files/67a17729-fcb7-4339-9499-1fc08fea72ca +0 -0
  172. package/files/68d09565-908d-4a67-8f09-e183f8708eb4 +0 -0
  173. package/files/70e587c5-56e8-47f0-bd36-691efcb0cc2e +0 -4
  174. package/files/7a227619-b715-4e2c-a79d-b2158d56a799 +0 -0
  175. package/files/7e3cc3ea-b706-4835-994d-65d33acaf369 +0 -0
  176. package/files/816f72a0-65dc-40c1-8862-1d22065cca3c +0 -0
  177. package/files/8bf84972-5086-4631-a752-093d7b1a098b +0 -0
  178. package/files/8c46e3bc-3f2e-441d-b8cc-17428fc3d219 +0 -0
  179. package/files/8eb83364-8826-4eee-895a-ba5cd3ab85e9 +0 -0
  180. package/files/8faefaea-14e3-49e4-ac74-9710622bfae9 +0 -0
  181. package/files/91af08c2-9dca-41f4-b6c6-b7bf33505df9 +0 -0
  182. package/files/9349dffa-35dd-49a0-a651-10b438038f95 +0 -0
  183. package/files/b0a12a41-6283-4f27-b2c8-66774991d96c +0 -0
  184. package/files/b28afb08-d18b-48a8-93c3-6a59c66d8fee +0 -0
  185. package/files/be60fe01-1578-4363-a908-41500092b77d +0 -0
  186. package/files/cf7b90e3-734a-4453-9ff3-9a331404b5ef +0 -0
  187. package/files/d1be30aa-8ff4-41c1-b360-f65922029047 +0 -0
  188. package/files/d2d31a57-a413-443c-9b97-fa9ca394ef0b +0 -0
  189. package/files/d357f223-b786-478d-a113-b53cb62acdab +0 -0
  190. package/files/d4a69ee0-05c4-4ff0-8753-624cdd0f24e9 +0 -0
  191. package/files/d57f411b-1874-4f00-a6b0-2f86de6b229d +0 -0
  192. package/files/d85aaa33-7f70-4a70-b3d2-cad667beb38c +0 -0
  193. package/files/db2fc236-dbe0-4d24-bfaf-884829fd090a +0 -0
  194. package/files/df2e20ec-6dcc-4402-94ee-7d1163f84197 +0 -0
  195. package/files/e0880ab5-6d49-4dc0-a5ec-0d3793a8a7b8 +0 -0
  196. package/files/e59ee9c6-5aea-48ea-b3d9-94a324dc2260 +0 -0
  197. package/files/e6d7aad6-4220-4ce7-85f8-89d0b1f0cc89 +0 -0
  198. package/files/f0bfc98c-3534-459d-931a-7c6e77bde153 +0 -0
  199. package/files/f8443740-050c-4714-9bf6-334783ae6ffd +0 -0
  200. package/files/fc405ae7-f9fb-455d-ac8c-0ca0d88d4115 +0 -0
  201. package/jest.config.js +0 -13
  202. package/spire.sqlite +0 -0
@@ -1,510 +1,115 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.ClientManager = exports.POWER_LEVELS = void 0;
16
- const sleep_1 = require("@extrahash/sleep");
17
- const crypto_1 = require("@vex-chat/crypto");
18
- const chalk_1 = __importDefault(require("chalk"));
19
- const events_1 = require("events");
20
- const msgpack_lite_1 = __importDefault(require("msgpack-lite"));
21
- const tweetnacl_1 = __importDefault(require("tweetnacl"));
22
- const uuid_1 = require("uuid");
23
- const utils_1 = require("./server/utils");
24
- const Spire_1 = require("./Spire");
25
- const createLogger_1 = require("./utils/createLogger");
26
- const createUint8UUID_1 = require("./utils/createUint8UUID");
27
- exports.POWER_LEVELS = {
28
- INVITE: 25,
1
+ import { EventEmitter } from "events";
2
+ import { setTimeout as sleep } from "node:timers/promises";
3
+ import { xConcat, XUtils } from "@vex-chat/crypto";
4
+ import { xSignOpen } from "@vex-chat/crypto";
5
+ import { MailWSSchema, SocketAuthErrors } from "@vex-chat/types";
6
+ import pc from "picocolors";
7
+ import { parse as uuidParse, validate as uuidValidate } from "uuid";
8
+ import { TOKEN_EXPIRY } from "./Spire.js";
9
+ import { createLogger } from "./utils/createLogger.js";
10
+ import { createUint8UUID } from "./utils/createUint8UUID.js";
11
+ import { msgpack } from "./utils/msgpack.js";
12
+ export const POWER_LEVELS = {
29
13
  CREATE: 50,
30
14
  DELETE: 50,
15
+ EMOJI: 25,
16
+ INVITE: 25,
31
17
  };
32
18
  function emptyHeader() {
33
19
  return new Uint8Array(32);
34
20
  }
35
21
  const MAX_MSG_SIZE = 2048;
36
- function unpackMessage(msg) {
37
- const msgp = Uint8Array.from(msg);
38
- const msgh = msgp.slice(0, 32);
39
- const msgb = msgpack_lite_1.default.decode(msgp.slice(32));
40
- return [msgh, msgb];
41
- }
42
- function packMessage(msg, header) {
43
- const msgb = Uint8Array.from(msgpack_lite_1.default.encode(msg));
44
- const msgh = header || emptyHeader();
45
- return crypto_1.xConcat(msgh, msgb);
46
- }
47
- class ClientManager extends events_1.EventEmitter {
48
- constructor(ws, db, notify, jwtDetails, options) {
22
+ export class ClientManager extends EventEmitter {
23
+ alive = true;
24
+ authed = false;
25
+ challengeID = createUint8UUID();
26
+ conn;
27
+ db;
28
+ device;
29
+ failed = false;
30
+ log;
31
+ notify;
32
+ user;
33
+ userDetails;
34
+ constructor(ws, db, notify, userDetails, options) {
49
35
  super();
50
- this.authed = false;
51
- this.alive = true;
52
- this.challengeID = createUint8UUID_1.createUint8UUID();
53
- this.failed = false;
54
36
  this.conn = ws;
55
37
  this.db = db;
56
38
  this.user = null;
57
- this.jwtDetails = jwtDetails;
39
+ this.userDetails = userDetails;
58
40
  this.device = null;
59
41
  this.notify = notify;
60
- this.log = createLogger_1.createLogger("client-manager", (options === null || options === void 0 ? void 0 : options.logLevel) || "error");
42
+ this.log = createLogger("client-manager", options?.logLevel || "error");
61
43
  this.initListeners();
62
44
  this.challenge();
63
45
  }
64
- toString() {
65
- if (!this.user || !this.device) {
66
- return "Unauthorized#0000";
46
+ getDevice() {
47
+ if (!this.device) {
48
+ throw new Error("No device set on this client.");
67
49
  }
68
- return this.user.username + "<" + this.getDevice().deviceID + ">";
50
+ return this.device;
69
51
  }
70
52
  getUser() {
71
- if (!this.authed) {
53
+ if (!this.authed || !this.user) {
72
54
  throw new Error("You must be authed before getting user info.");
73
55
  }
74
56
  return this.user;
75
57
  }
76
58
  send(msg, header) {
77
- return __awaiter(this, void 0, void 0, function* () {
78
- if (header) {
79
- this.log.debug(chalk_1.default.red.bold("OUTH"), header.toString());
80
- }
81
- else {
82
- this.log.debug(chalk_1.default.red.bold("OUTH"), emptyHeader.toString());
83
- }
84
- const packedMessage = packMessage(msg, header);
85
- this.log.info(chalk_1.default.bold("⟶ ") +
86
- responseColor(msg.type.toUpperCase()) +
87
- " " +
88
- this.toString() +
89
- " " +
90
- chalk_1.default.yellow(Buffer.byteLength(packedMessage)));
91
- this.log.debug(chalk_1.default.red.bold("OUT"), msg);
92
- try {
93
- this.conn.send(packedMessage);
94
- }
95
- catch (err) {
96
- this.log.warn(err.toString());
97
- this.fail();
98
- }
99
- });
59
+ if (header) {
60
+ this.log.debug(pc.bold(pc.red("OUTH")), header.toString());
61
+ }
62
+ else {
63
+ this.log.debug(pc.bold(pc.red("OUTH")), emptyHeader.toString());
64
+ }
65
+ const packedMessage = packMessage(msg, header);
66
+ this.log.info(pc.bold("⟶ ") +
67
+ responseColor(msg.type.toUpperCase()) +
68
+ " " +
69
+ this.toString() +
70
+ " " +
71
+ pc.yellow(Buffer.byteLength(packedMessage)));
72
+ this.log.debug(pc.bold(pc.red("OUT")), msg);
73
+ try {
74
+ this.conn.send(packedMessage);
75
+ }
76
+ catch (err) {
77
+ this.log.warn(String(err));
78
+ this.fail();
79
+ }
100
80
  }
101
- getDevice() {
102
- return this.device;
81
+ toString() {
82
+ if (!this.user || !this.device) {
83
+ return "Unauthorized#0000";
84
+ }
85
+ return this.user.username + "<" + this.getDevice().deviceID + ">";
103
86
  }
104
87
  authorize(transmissionID) {
105
88
  this.authed = true;
106
89
  this.sendAuthedMessage(transmissionID);
90
+ void this.db.markDeviceLogin(this.getDevice());
107
91
  this.emit("authed");
108
92
  }
109
- // notifies all users when a given resourceID changes
110
- notifyServerChange(serverID, transmissionID) {
111
- return __awaiter(this, void 0, void 0, function* () {
112
- const affectedUsers = yield this.db.retrieveAffectedUsers(serverID);
113
- // tell everyone about server change
114
- for (const user of affectedUsers) {
115
- this.notify(user.userID, "serverChange", transmissionID, serverID);
116
- }
117
- });
93
+ challenge() {
94
+ this.challengeID = new Uint8Array(uuidParse(crypto.randomUUID()));
95
+ const challenge = {
96
+ challenge: this.challengeID,
97
+ transmissionID: crypto.randomUUID(),
98
+ type: "challenge",
99
+ };
100
+ this.send(challenge);
118
101
  }
119
102
  fail() {
120
103
  if (this.failed) {
121
104
  return;
122
105
  }
123
106
  this.log.warn("Connection closed.");
124
- if (this.conn) {
125
- this.conn.close();
126
- }
107
+ this.conn.close();
127
108
  this.failed = true;
128
109
  this.emit("fail");
129
110
  }
130
- setAlive(status) {
131
- this.alive = status;
132
- }
133
- pingLoop() {
134
- return __awaiter(this, void 0, void 0, function* () {
135
- while (true) {
136
- this.ping();
137
- yield sleep_1.sleep(5000);
138
- }
139
- });
140
- }
141
- ping() {
142
- if (!this.alive) {
143
- this.fail();
144
- return;
145
- }
146
- this.setAlive(false);
147
- const p = { transmissionID: uuid_1.v4(), type: "ping" };
148
- this.send(p);
149
- }
150
- pong(transmissionID) {
151
- // ping is allowed before auth
152
- if (this.user) {
153
- this.db.markUserSeen(this.user);
154
- }
155
- const p = { transmissionID, type: "pong" };
156
- this.send(p);
157
- }
158
- verifyResponse(msg) {
159
- return __awaiter(this, void 0, void 0, function* () {
160
- const user = yield this.db.retrieveUser(this.jwtDetails.userID);
161
- if (user) {
162
- const devices = yield this.db.retrieveUserDeviceList([user.userID]);
163
- let message = null;
164
- for (const device of devices) {
165
- const verified = tweetnacl_1.default.sign.open(msg.signed, crypto_1.XUtils.decodeHex(device.signKey));
166
- if (verified) {
167
- message = verified;
168
- this.device = device;
169
- }
170
- }
171
- if (!message) {
172
- console.warn("Bad response from client.");
173
- this.fail();
174
- return;
175
- }
176
- if (message) {
177
- if (crypto_1.XUtils.bytesEqual(this.challengeID, message)) {
178
- this.user = user;
179
- this.authorize(msg.transmissionID);
180
- }
181
- }
182
- else {
183
- this.log.info("Signature verification failed!");
184
- this.fail();
185
- }
186
- }
187
- else {
188
- this.log.info("User is not registered.");
189
- this.fail();
190
- }
191
- });
192
- }
193
- challenge() {
194
- this.challengeID = new Uint8Array(uuid_1.parse(uuid_1.v4()));
195
- const challenge = {
196
- transmissionID: uuid_1.v4(),
197
- type: "challenge",
198
- challenge: this.challengeID,
199
- };
200
- this.send(challenge);
201
- }
202
- sendErr(transmissionID, message, data) {
203
- const error = {
204
- transmissionID,
205
- type: "error",
206
- error: message,
207
- data,
208
- };
209
- this.send(error);
210
- }
211
- sendAuthedMessage(transmissionID) {
212
- this.send({ type: "authorized", transmissionID });
213
- }
214
- sendSuccess(transmissionID, data, header) {
215
- const msg = {
216
- transmissionID,
217
- type: "success",
218
- data,
219
- };
220
- this.send(msg, header);
221
- }
222
- parseResourceMsg(msg, header) {
223
- return __awaiter(this, void 0, void 0, function* () {
224
- switch (msg.resourceType) {
225
- case "permissions":
226
- if (msg.action === "RETRIEVE") {
227
- try {
228
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "all");
229
- this.sendSuccess(msg.transmissionID, permissions);
230
- break;
231
- }
232
- catch (err) {
233
- this.sendErr(msg.transmissionID, err.toString());
234
- }
235
- }
236
- if (msg.action === "DELETE") {
237
- try {
238
- // msg.data is permID
239
- const permToDelete = yield this.db.retrievePermission(msg.data);
240
- if (!permToDelete) {
241
- this.sendErr(msg.transmissionID, "That doesn't exist.");
242
- break;
243
- }
244
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, permToDelete.resourceType);
245
- let found = false;
246
- for (const perm of permissions) {
247
- // msg.data is resourceID
248
- if (perm.resourceID === permToDelete.resourceID &&
249
- (perm.userID === this.getUser().userID ||
250
- (perm.powerLevel > exports.POWER_LEVELS.DELETE &&
251
- perm.powerLevel >
252
- permToDelete.powerLevel))) {
253
- this.db.deletePermission(perm.permissionID);
254
- this.sendSuccess(msg.transmissionID, null);
255
- found = true;
256
- break;
257
- }
258
- }
259
- if (!found) {
260
- this.sendErr(msg.transmissionID, "You don't have permission to do that.");
261
- break;
262
- }
263
- }
264
- catch (err) {
265
- this.sendErr(msg.transmissionID, err.toString());
266
- break;
267
- }
268
- }
269
- if (msg.action === "CREATE") {
270
- try {
271
- const { resourceType, userID, resourceID } = msg.data;
272
- const userHeldPerms = yield this.db.retrievePermissions(this.getUser().userID, "all");
273
- let found = false;
274
- for (const perm of userHeldPerms) {
275
- if (perm.resourceID === resourceID) {
276
- if (perm.powerLevel > exports.POWER_LEVELS.CREATE) {
277
- // he's got the perm and the power level, we're good to go
278
- const newPerm = yield this.db.createPermission(userID, resourceType, resourceID, 0);
279
- this.sendSuccess(msg.transmissionID, newPerm);
280
- // notify the user of their new permission
281
- this.notify(userID, "permission", msg.transmissionID, newPerm);
282
- found = true;
283
- break;
284
- }
285
- }
286
- }
287
- if (!found) {
288
- this.sendErr(msg.transmissionID, "You don't have permission for that.");
289
- }
290
- break;
291
- }
292
- catch (err) {
293
- this.sendErr(msg.transmissionID, err.toString());
294
- }
295
- }
296
- break;
297
- case "otk":
298
- if (msg.action === "RETRIEVE") {
299
- try {
300
- const keyCount = yield this.db.getOTKCount(this.getDevice().deviceID);
301
- this.sendSuccess(msg.transmissionID, keyCount);
302
- }
303
- catch (err) {
304
- this.log.error(err);
305
- this.sendErr(msg.transmissionID, err.toString());
306
- }
307
- }
308
- if (msg.action === "CREATE") {
309
- try {
310
- yield this.db.saveOTK(this.getDevice().deviceID, msg.data);
311
- this.sendSuccess(msg.transmissionID, msg);
312
- }
313
- catch (err) {
314
- this.log.error(err);
315
- this.sendErr(msg.transmissionID, err.toString());
316
- }
317
- }
318
- break;
319
- case "user":
320
- if (msg.action === "RETRIEVE") {
321
- try {
322
- const user = yield this.db.retrieveUser(msg.data);
323
- if (user) {
324
- this.sendSuccess(msg.transmissionID, utils_1.censorUser(user));
325
- }
326
- else {
327
- this.log.error("User doesn't exist.");
328
- this.sendErr(msg.transmissionID, "That user doesn't exist.");
329
- }
330
- }
331
- catch (err) {
332
- this.log.error(err);
333
- this.sendErr(msg.transmissionID, err.toString());
334
- }
335
- }
336
- break;
337
- // this is a single channel userlist
338
- case "userlist":
339
- if (msg.action === "RETRIEVE") {
340
- const channelID = msg.data;
341
- try {
342
- const channel = yield this.db.retrieveChannel(channelID);
343
- if (!channel) {
344
- this.sendErr(msg.transmissionID, "That channel doesn't exist.");
345
- break;
346
- }
347
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "server");
348
- for (const permission of permissions) {
349
- if (permission.resourceID === channel.serverID) {
350
- // we've got the permission, it's ok to give them the userlist
351
- const groupMembers = yield this.db.retrieveGroupMembers(channelID);
352
- this.sendSuccess(msg.transmissionID, groupMembers.map((user) => utils_1.censorUser(user)));
353
- break;
354
- }
355
- }
356
- }
357
- catch (err) {
358
- this.sendErr(msg.transmissionID, err.toString());
359
- }
360
- }
361
- break;
362
- case "keyBundle":
363
- if (msg.action === "RETRIEVE") {
364
- try {
365
- const keyBundle = yield this.db.getKeyBundle(msg.data);
366
- if (keyBundle) {
367
- this.sendSuccess(msg.transmissionID, keyBundle);
368
- }
369
- else {
370
- this.sendErr(msg.transmissionID, "Couldn't retrieve key bundle.");
371
- }
372
- }
373
- catch (err) {
374
- this.log.error(err);
375
- this.sendErr(msg.transmissionID, err.toString());
376
- }
377
- }
378
- break;
379
- case "mail":
380
- if (msg.action === "RETRIEVE") {
381
- try {
382
- const inbox = yield this.db.retrieveMail(this.getDevice().deviceID);
383
- for (const mail of inbox) {
384
- const [mailHeader, mailBody] = mail;
385
- this.sendSuccess(msg.transmissionID, mailBody, mailHeader);
386
- }
387
- this.sendSuccess(msg.transmissionID, null);
388
- }
389
- catch (err) {
390
- this.log.error(err);
391
- this.sendErr(msg.transmissionID, err.toString());
392
- }
393
- }
394
- if (msg.action === "CREATE") {
395
- const mail = msg.data;
396
- try {
397
- yield this.db.saveMail(mail, header, this.getDevice().deviceID, this.getUser().userID);
398
- this.log.info("Received mail for " + msg.data.recipient);
399
- const deviceDetails = yield this.db.retrieveDevice(msg.data.recipient);
400
- if (!deviceDetails) {
401
- this.sendErr(msg.transmissionID, "No associated user record found for device.");
402
- return;
403
- }
404
- this.sendSuccess(msg.transmissionID, null);
405
- this.notify(deviceDetails.owner, "mail", msg.transmissionID, null, msg.data.recipient);
406
- }
407
- catch (err) {
408
- this.log.error(err);
409
- this.sendErr(msg.transmissionID, err.toString());
410
- }
411
- }
412
- break;
413
- case "servers":
414
- if (msg.action === "RETRIEVE") {
415
- const servers = yield this.db.retrieveServers(this.getUser().userID);
416
- this.sendSuccess(msg.transmissionID, servers);
417
- }
418
- if (msg.action === "CREATE") {
419
- const server = yield this.db.createServer(msg.data, this.getUser().userID);
420
- this.sendSuccess(msg.transmissionID, server);
421
- }
422
- if (msg.action === "DELETE") {
423
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "server");
424
- let found = false;
425
- for (const permission of permissions) {
426
- if (permission.resourceID === msg.data &&
427
- permission.powerLevel > 50) {
428
- // msg.data is the serverID
429
- yield this.db.deleteServer(msg.data);
430
- this.sendSuccess(msg.transmissionID, null);
431
- found = true;
432
- break;
433
- }
434
- }
435
- if (!found) {
436
- this.sendErr(msg.transmissionID, "You don't have permission to do that.");
437
- }
438
- }
439
- break;
440
- case "channels":
441
- if (msg.action === "RETRIEVE") {
442
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "server");
443
- for (const permission of permissions) {
444
- if (msg.data === permission.resourceID) {
445
- const channels = yield this.db.retrieveChannels(permission.resourceID);
446
- this.sendSuccess(msg.transmissionID, channels);
447
- break;
448
- }
449
- }
450
- this.sendSuccess(msg.transmissionID, []);
451
- }
452
- if (msg.action === "CREATE") {
453
- // resourceID is serverID
454
- const { serverID, name } = msg.data;
455
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "server");
456
- let found = false;
457
- for (const permission of permissions) {
458
- if (permission.resourceID === serverID &&
459
- permission.powerLevel > exports.POWER_LEVELS.CREATE) {
460
- const channel = yield this.db.createChannel(name, serverID);
461
- this.sendSuccess(msg.transmissionID, channel);
462
- found = true;
463
- break;
464
- }
465
- }
466
- if (!found) {
467
- this.sendErr(msg.transmissionID, "You don't have permission to do that.");
468
- }
469
- else {
470
- this.notifyServerChange(serverID, msg.transmissionID);
471
- }
472
- break;
473
- }
474
- if (msg.action === "DELETE") {
475
- const channel = yield this.db.retrieveChannel(msg.data || "");
476
- if (!channel) {
477
- this.sendErr(msg.transmissionID, "You don't have permission to do that.");
478
- break;
479
- }
480
- const permissions = yield this.db.retrievePermissions(this.getUser().userID, "server");
481
- let found = false;
482
- for (const permission of permissions) {
483
- if (permission.resourceID === channel.serverID &&
484
- permission.powerLevel > 50) {
485
- found = true;
486
- // msg.data is the channelID
487
- yield this.db.deleteChannel(msg.data);
488
- this.sendSuccess(msg.transmissionID, null);
489
- this.notifyServerChange(channel.serverID, msg.transmissionID);
490
- found = true;
491
- break;
492
- }
493
- }
494
- if (!found) {
495
- this.sendErr(msg.transmissionID, "You don't have permission to do that.");
496
- }
497
- }
498
- break;
499
- default:
500
- this.log.info("Unsupported resource type " + msg.resourceType);
501
- }
502
- });
503
- }
504
- handleReceipt(msg) {
505
- return __awaiter(this, void 0, void 0, function* () {
506
- yield this.db.deleteMail(msg.nonce, this.getDevice().deviceID);
507
- });
111
+ async handleReceipt(msg) {
112
+ await this.db.deleteMail(msg.nonce, this.getDevice().deviceID);
508
113
  }
509
114
  initListeners() {
510
115
  this.conn.on("open", () => {
@@ -512,8 +117,8 @@ class ClientManager extends events_1.EventEmitter {
512
117
  if (!this.authed) {
513
118
  this.conn.close();
514
119
  }
515
- }, Spire_1.TOKEN_EXPIRY);
516
- this.pingLoop();
120
+ }, TOKEN_EXPIRY);
121
+ void this.pingLoop();
517
122
  });
518
123
  this.conn.on("close", () => {
519
124
  this.fail();
@@ -523,50 +128,49 @@ class ClientManager extends events_1.EventEmitter {
523
128
  const size = Buffer.byteLength(message);
524
129
  if (size > MAX_MSG_SIZE) {
525
130
  this.sendErr(msg.transmissionID, "Message is too big. Received size " +
526
- size +
131
+ String(size) +
527
132
  " while max size is " +
528
- MAX_MSG_SIZE);
133
+ String(MAX_MSG_SIZE));
529
134
  return;
530
135
  }
531
- this.log.info(chalk_1.default.bold("⟵ ") +
532
- (msg.type === "resource"
533
- ? crudColor(msg.action.toUpperCase()) +
534
- " " +
535
- chalk_1.default.bold(msg.resourceType.toUpperCase())
536
- : chalk_1.default.bold(msg.type.toUpperCase())) +
136
+ this.log.info(pc.bold("⟵ ") +
137
+ pc.bold(msg.type.toUpperCase()) +
537
138
  " " +
538
139
  this.toString() +
539
140
  " " +
540
- chalk_1.default.yellow(size));
541
- this.log.debug(chalk_1.default.red.bold("INH"), header.toString());
542
- this.log.debug(chalk_1.default.red.bold("IN"), msg);
141
+ pc.yellow(String(size)));
142
+ this.log.debug(pc.bold(pc.red("INH")), header.toString());
143
+ this.log.debug(pc.bold(pc.red("IN")), msg);
543
144
  if (!msg.type) {
544
145
  this.sendErr(msg.transmissionID, "Message type is required.");
545
146
  return;
546
147
  }
547
- if (!uuid_1.validate(msg.transmissionID)) {
548
- this.sendErr(uuid_1.v4(), "transmissionID is required and must be a valid uuid.");
148
+ if (!uuidValidate(msg.transmissionID)) {
149
+ this.sendErr(crypto.randomUUID(), "transmissionID is required and must be a valid uuid.");
549
150
  return;
550
151
  }
551
152
  switch (msg.type) {
153
+ case "ping":
154
+ this.pong(msg.transmissionID);
155
+ break;
156
+ case "pong":
157
+ this.setAlive(true);
158
+ break;
552
159
  case "receipt":
553
- this.handleReceipt(msg);
160
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by msg.type
161
+ void this.handleReceipt(msg);
554
162
  break;
555
163
  case "resource":
556
164
  if (!this.authed) {
557
165
  this.sendErr(msg.transmissionID, "You are not authenticated.");
558
166
  break;
559
167
  }
560
- this.parseResourceMsg(msg, header);
168
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by msg.type
169
+ void this.parseResourceMsg(msg, header);
561
170
  break;
562
171
  case "response":
563
- this.verifyResponse(msg);
564
- break;
565
- case "ping":
566
- this.pong(msg.transmissionID);
567
- break;
568
- case "pong":
569
- this.setAlive(true);
172
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by msg.type
173
+ void this.verifyResponse(msg);
570
174
  break;
571
175
  default:
572
176
  this.log.info("unsupported message %s", msg.type);
@@ -574,29 +178,148 @@ class ClientManager extends events_1.EventEmitter {
574
178
  }
575
179
  });
576
180
  }
577
- }
578
- exports.ClientManager = ClientManager;
579
- const crudColor = (action) => {
580
- switch (action) {
581
- case "CREATE":
582
- return chalk_1.default.yellow.bold(action);
583
- case "RETRIEVE":
584
- return chalk_1.default.yellow.bold(action);
585
- case "UPDATE":
586
- return chalk_1.default.cyan.bold(action);
587
- case "DELETE":
588
- return chalk_1.default.red.bold(action);
589
- default:
590
- return action;
181
+ async parseResourceMsg(msg, header) {
182
+ switch (msg.resourceType) {
183
+ case "mail":
184
+ if (msg.action === "CREATE") {
185
+ const mailResult = MailWSSchema.safeParse(msg.data);
186
+ if (!mailResult.success) {
187
+ this.sendErr(msg.transmissionID, "Invalid mail payload: " +
188
+ JSON.stringify(mailResult.error.issues));
189
+ return;
190
+ }
191
+ const mail = mailResult.data;
192
+ try {
193
+ await this.db.saveMail(mail, header, this.getDevice().deviceID, this.getUser().userID);
194
+ this.log.info("Received mail for " + mail.recipient);
195
+ const deviceDetails = await this.db.retrieveDevice(mail.recipient);
196
+ if (!deviceDetails) {
197
+ this.sendErr(msg.transmissionID, "No associated user record found for device.");
198
+ return;
199
+ }
200
+ this.sendSuccess(msg.transmissionID, null);
201
+ this.notify(deviceDetails.owner, "mail", msg.transmissionID, null, mail.recipient);
202
+ }
203
+ catch (err) {
204
+ this.log.error(String(err));
205
+ this.sendErr(msg.transmissionID, String(err));
206
+ }
207
+ }
208
+ break;
209
+ default:
210
+ this.log.info("Unsupported resource type " + msg.resourceType);
211
+ }
591
212
  }
592
- };
213
+ ping() {
214
+ if (!this.alive) {
215
+ this.fail();
216
+ return;
217
+ }
218
+ this.setAlive(false);
219
+ const p = { transmissionID: crypto.randomUUID(), type: "ping" };
220
+ this.send(p);
221
+ }
222
+ async pingLoop() {
223
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop
224
+ while (true) {
225
+ this.ping();
226
+ await sleep(5000);
227
+ }
228
+ }
229
+ pong(transmissionID) {
230
+ // ping is allowed before auth
231
+ if (this.user) {
232
+ void this.db.markUserSeen(this.user);
233
+ }
234
+ const p = { transmissionID, type: "pong" };
235
+ this.send(p);
236
+ }
237
+ sendAuthedMessage(transmissionID) {
238
+ this.send({ transmissionID, type: "authorized" });
239
+ }
240
+ sendAuthError(error) {
241
+ const msg = {
242
+ error,
243
+ transmissionID: crypto.randomUUID(),
244
+ type: "authErr",
245
+ };
246
+ this.send(msg);
247
+ }
248
+ sendErr(transmissionID, message, data) {
249
+ const error = {
250
+ data,
251
+ error: message,
252
+ transmissionID,
253
+ type: "error",
254
+ };
255
+ this.send(error);
256
+ }
257
+ sendSuccess(transmissionID, data, header, timestamp) {
258
+ const msg = {
259
+ data,
260
+ timestamp,
261
+ transmissionID,
262
+ type: "success",
263
+ };
264
+ this.send(msg, header);
265
+ }
266
+ setAlive(status) {
267
+ this.alive = status;
268
+ }
269
+ async verifyResponse(msg) {
270
+ const user = await this.db.retrieveUser(this.userDetails.userID);
271
+ if (user) {
272
+ const devices = await this.db.retrieveUserDeviceList([user.userID]);
273
+ let message = null;
274
+ for (const device of devices) {
275
+ const verified = xSignOpen(msg.signed, XUtils.decodeHex(device.signKey));
276
+ if (verified) {
277
+ message = verified;
278
+ this.device = device;
279
+ }
280
+ }
281
+ if (!message) {
282
+ this.log.warn("Signature verification failed!");
283
+ this.sendAuthError(SocketAuthErrors.BadSignature);
284
+ this.fail();
285
+ return;
286
+ }
287
+ if (XUtils.bytesEqual(this.challengeID, message)) {
288
+ this.user = user;
289
+ this.authorize(msg.transmissionID);
290
+ }
291
+ else {
292
+ this.log.warn("Token is bad!");
293
+ this.sendAuthError(SocketAuthErrors.InvalidToken);
294
+ }
295
+ }
296
+ else {
297
+ this.log.info("User is not registered.");
298
+ this.sendAuthError(SocketAuthErrors.UserNotRegistered);
299
+ this.fail();
300
+ }
301
+ }
302
+ }
303
+ function packMessage(msg, header) {
304
+ const msgb = Uint8Array.from(msgpack.encode(msg));
305
+ const msgh = header || emptyHeader();
306
+ return xConcat(msgh, msgb);
307
+ }
308
+ function unpackMessage(msg) {
309
+ const msgp = Uint8Array.from(msg);
310
+ const msgh = msgp.slice(0, 32);
311
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- msgpack.decode returns any
312
+ const msgb = msgpack.decode(msgp.slice(32));
313
+ return [msgh, msgb];
314
+ }
593
315
  const responseColor = (status) => {
594
316
  switch (status) {
595
- case "SUCCESS":
596
- return chalk_1.default.green.bold(status);
597
317
  case "ERROR":
598
- return chalk_1.default.red.bold(status);
318
+ return pc.bold(pc.red(status));
319
+ case "SUCCESS":
320
+ return pc.bold(pc.green(status));
599
321
  default:
600
322
  return status;
601
323
  }
602
324
  };
325
+ //# sourceMappingURL=ClientManager.js.map