@vex-chat/spire 0.8.0 → 1.0.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 (373) hide show
  1. package/README.md +83 -28
  2. package/dist/ClientManager.d.ts +22 -26
  3. package/dist/ClientManager.js +198 -259
  4. package/dist/ClientManager.js.map +1 -0
  5. package/dist/Database.d.ts +71 -49
  6. package/dist/Database.js +737 -774
  7. package/dist/Database.js.map +1 -0
  8. package/dist/Spire.d.ts +20 -15
  9. package/dist/Spire.js +471 -249
  10. package/dist/Spire.js.map +1 -0
  11. package/dist/__tests__/Database.spec.js +102 -75
  12. package/dist/__tests__/Database.spec.js.map +1 -0
  13. package/dist/db/schema.d.ts +135 -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/migrations/2026-04-14_argon2id-password-hashing.d.ts +3 -0
  26. package/dist/migrations/2026-04-14_argon2id-password-hashing.js +12 -0
  27. package/dist/migrations/2026-04-14_argon2id-password-hashing.js.map +1 -0
  28. package/dist/run.js +25 -21
  29. package/dist/run.js.map +1 -0
  30. package/dist/server/avatar.d.ts +1 -4
  31. package/dist/server/avatar.js +63 -67
  32. package/dist/server/avatar.js.map +1 -0
  33. package/dist/server/errors.d.ts +56 -0
  34. package/dist/server/errors.js +68 -0
  35. package/dist/server/errors.js.map +1 -0
  36. package/dist/server/file.d.ts +2 -4
  37. package/dist/server/file.js +81 -64
  38. package/dist/server/file.js.map +1 -0
  39. package/dist/server/index.d.ts +7 -10
  40. package/dist/server/index.js +414 -416
  41. package/dist/server/index.js.map +1 -0
  42. package/dist/server/invite.d.ts +3 -5
  43. package/dist/server/invite.js +18 -52
  44. package/dist/server/invite.js.map +1 -0
  45. package/dist/server/openapi.d.ts +2 -0
  46. package/dist/server/openapi.js +40 -0
  47. package/dist/server/openapi.js.map +1 -0
  48. package/dist/server/permissions.d.ts +16 -0
  49. package/dist/server/permissions.js +22 -0
  50. package/dist/server/permissions.js.map +1 -0
  51. package/dist/server/rateLimit.d.ts +28 -0
  52. package/dist/server/rateLimit.js +58 -0
  53. package/dist/server/rateLimit.js.map +1 -0
  54. package/dist/server/user.d.ts +3 -7
  55. package/dist/server/user.js +56 -69
  56. package/dist/server/user.js.map +1 -0
  57. package/dist/server/utils.d.ts +35 -7
  58. package/dist/server/utils.js +50 -6
  59. package/dist/server/utils.js.map +1 -0
  60. package/dist/types/express.d.ts +20 -0
  61. package/dist/types/express.js +2 -0
  62. package/dist/types/express.js.map +1 -0
  63. package/dist/utils/createUint8UUID.js +6 -10
  64. package/dist/utils/createUint8UUID.js.map +1 -0
  65. package/dist/utils/jwtSecret.d.ts +7 -0
  66. package/dist/utils/jwtSecret.js +14 -0
  67. package/dist/utils/jwtSecret.js.map +1 -0
  68. package/dist/utils/loadEnv.js +7 -22
  69. package/dist/utils/loadEnv.js.map +1 -0
  70. package/dist/utils/msgpack.d.ts +2 -0
  71. package/dist/utils/msgpack.js +4 -0
  72. package/dist/utils/msgpack.js.map +1 -0
  73. package/package.json +93 -65
  74. package/src/ClientManager.ts +382 -0
  75. package/src/Database.ts +987 -0
  76. package/src/Spire.ts +807 -0
  77. package/src/__tests__/Database.spec.ts +150 -0
  78. package/src/ambient-modules.d.ts +1 -0
  79. package/src/db/schema.ts +166 -0
  80. package/src/index.ts +3 -0
  81. package/src/middleware/validate.ts +38 -0
  82. package/src/migrations/2026-04-06_initial-schema.ts +218 -0
  83. package/src/migrations/2026-04-14_argon2id-password-hashing.ts +18 -0
  84. package/src/run.ts +36 -0
  85. package/src/server/avatar.ts +134 -0
  86. package/src/server/errors.ts +104 -0
  87. package/src/server/file.ts +167 -0
  88. package/src/server/index.ts +841 -0
  89. package/src/server/invite.ts +63 -0
  90. package/src/server/openapi.ts +51 -0
  91. package/src/server/permissions.ts +40 -0
  92. package/src/server/rateLimit.ts +86 -0
  93. package/src/server/user.ts +121 -0
  94. package/src/server/utils.ts +59 -0
  95. package/src/types/express.ts +23 -0
  96. package/src/utils/createUint8UUID.ts +9 -0
  97. package/src/utils/jwtSecret.ts +15 -0
  98. package/src/utils/loadEnv.ts +15 -0
  99. package/src/utils/msgpack.ts +4 -0
  100. package/avatars/052242d0-4129-4a6e-8076-22709c157549 +0 -0
  101. package/avatars/0a677c9a-4986-4b2c-ae2e-12faf22f55db +0 -0
  102. package/avatars/0ba21b91-decb-4a3e-ac86-4dd54d805a9a +0 -0
  103. package/avatars/0c48d8b6-1d1b-4297-a6c6-2fe50af6fc35 +0 -0
  104. package/avatars/0d993cdf-19a6-4299-a4ee-a06579d106cf +0 -0
  105. package/avatars/17b000e4-ac38-46ec-9dec-d2568086129a +0 -0
  106. package/avatars/19dc5594-0f06-4ac6-af18-8740dd39ef6b +0 -0
  107. package/avatars/20444fa3-6d5e-429e-b55f-b81c3d2c61ee +0 -0
  108. package/avatars/21c0512a-5630-4931-9442-d66db66737be +0 -0
  109. package/avatars/22830a60-0b6f-4912-83a5-72245465f332 +0 -0
  110. package/avatars/243639ce-f59f-4404-a1f1-4ec0eb5d2af3 +0 -0
  111. package/avatars/30d2c01d-7b7f-4ea9-9859-1c90837a23f7 +0 -0
  112. package/avatars/315a04f0-9a6f-4b0f-bb9f-5fa774c4752b +0 -0
  113. package/avatars/3563d333-53fe-4885-ac2d-9a4f761db85e +0 -0
  114. package/avatars/36a10c00-3b4c-437f-8e1f-4428ecde0003 +0 -0
  115. package/avatars/40b83eeb-c6e8-4268-82ab-69799a796405 +0 -0
  116. package/avatars/45b5ddb9-ad2c-4404-8ab3-cb4699e6d61c +0 -0
  117. package/avatars/4e4f0ffb-9a75-479a-bccb-446d0bf85020 +0 -0
  118. package/avatars/4e62c3bd-08c6-4fdd-bd65-f01c7322ed64 +0 -0
  119. package/avatars/5004d2e7-51af-44af-8776-6c71f7019843 +0 -0
  120. package/avatars/5041eb29-5c4b-4dea-8c1b-31ba4473161a +0 -0
  121. package/avatars/5065cf78-31c5-46cb-8d5c-c0b6be2d994e +0 -0
  122. package/avatars/51b91d2c-8956-4d73-b4ad-ca6a8d9da9a8 +0 -0
  123. package/avatars/58264a2c-5651-4a42-8ca2-a9907b311e48 +0 -0
  124. package/avatars/58c2357c-8080-4725-a0ce-182c96b037c4 +0 -0
  125. package/avatars/59b5f6dd-8e04-4d15-b4dc-c1c652558a74 +0 -0
  126. package/avatars/5b417a78-b274-48bc-98a4-6e54b74ee62d +0 -0
  127. package/avatars/611f5a93-1ed4-45a1-bc8e-e8e413f9b171 +0 -0
  128. package/avatars/65abe919-9921-46f6-9bc9-183e9cc53c8a +0 -0
  129. package/avatars/6934202d-1546-4270-8a15-97ba8b8c6fa9 +0 -0
  130. package/avatars/6acd24be-f4b7-4399-9e7c-807821828d29 +0 -0
  131. package/avatars/6b2d6ac5-e35c-4297-994e-f0eb6fe56740 +0 -0
  132. package/avatars/6cae9ddd-f163-44e7-a632-30425716a159 +0 -0
  133. package/avatars/6d90e79e-b9c1-4b89-843b-96636de8d26b +0 -0
  134. package/avatars/6f82c7bb-a974-4372-8a64-ce287e668c8c +0 -0
  135. package/avatars/74a45091-5a76-4bb7-ae8a-cd7373adc128 +0 -0
  136. package/avatars/7d071ab2-e0b5-4dbb-8bf5-1fae50c3663f +0 -0
  137. package/avatars/7de818c8-bda1-4f51-976e-160fc087184b +0 -0
  138. package/avatars/873937df-8cb1-427a-92bf-f829f4259624 +0 -0
  139. package/avatars/8b45ffa5-3322-4109-bfa0-1be088336135 +0 -0
  140. package/avatars/8efaa426-22a9-42a2-b4ac-a275717b812f +0 -0
  141. package/avatars/903fd1a6-d6ea-431c-b98f-f21e424a2852 +0 -0
  142. package/avatars/943d8533-5174-4199-990f-1ec69e5d60c4 +0 -0
  143. package/avatars/952d014e-3804-4cd2-a4a0-ffe40a11e4ac +0 -0
  144. package/avatars/95d3acb0-724d-4413-b20f-edad55812d5d +0 -0
  145. package/avatars/9641a946-f613-471b-bedd-c1730b96b51e +0 -0
  146. package/avatars/9b01cbbf-f6b2-43fb-b569-589b6f2a8134 +0 -0
  147. package/avatars/9cd4424d-a34f-4467-acc0-93cf82703e0d +0 -0
  148. package/avatars/9d9ad3b0-e5a6-420a-a6c5-fb9085b70376 +0 -0
  149. package/avatars/9e9e34b5-4e63-4c4b-9722-c7f5674b47aa +0 -0
  150. package/avatars/a387d5c1-59eb-4a6b-80c1-a8982ed12c33 +0 -0
  151. package/avatars/a3e86d21-d881-4824-8ebf-45e3bf0f9186 +0 -0
  152. package/avatars/a8d5cc1c-3f42-4b7b-8d33-f9a9ef77f96b +0 -0
  153. package/avatars/a91d815f-badc-4604-a7be-6c7a44e6101d +0 -0
  154. package/avatars/aa8d0324-bcec-4737-a8c4-bdbff914148d +0 -0
  155. package/avatars/abb8a941-8b6b-47d7-a2f9-8b153ba44aa2 +0 -0
  156. package/avatars/b011bb38-1ef3-4d22-82fa-8bf60faf7b5d +0 -0
  157. package/avatars/b24bcbc1-11f0-473d-a8b9-ba8ce4ca127d +0 -0
  158. package/avatars/b2607346-af1c-4e98-b725-7650a766db2a +0 -0
  159. package/avatars/b6300f7c-cb37-459b-b1bf-8a0a0e797a52 +0 -0
  160. package/avatars/b7d3cff3-84dd-4547-93a1-de1aaa8aa34c +0 -0
  161. package/avatars/baa4b51c-e97f-4f51-bcb3-f27bc506cfaf +0 -0
  162. package/avatars/be7022e4-e292-4515-80d5-f9b61ebeb4ce +0 -0
  163. package/avatars/bed596a3-7569-4854-9e76-f52d33c0a541 +0 -0
  164. package/avatars/bf69992f-3f72-4930-99bb-0ffe17f3aebf +0 -0
  165. package/avatars/ca00c250-c6d4-464d-a6de-1c8467a18fe8 +0 -0
  166. package/avatars/ca19d78f-c0bc-4bd5-b26f-6923cb19996d +0 -0
  167. package/avatars/cda4d6e1-e0a4-4024-ac95-6de98e713b98 +0 -0
  168. package/avatars/cf72c30d-2da8-4e81-aa71-735b9e714274 +0 -0
  169. package/avatars/d5a35b78-99b3-4564-b6b9-b2ccab28c470 +0 -0
  170. package/avatars/dadb38c1-2a9d-47a3-8d92-b56b6166973c +0 -0
  171. package/avatars/e68705c7-375d-4423-9a86-29a16bd3ee0e +0 -0
  172. package/avatars/e9af3e4c-1f62-4302-8b99-b68ce93b7a86 +0 -0
  173. package/avatars/ea7e7331-e845-4189-8248-5f5b1d63f5e3 +0 -0
  174. package/avatars/ef4f8dcb-ef6c-4e7a-9be1-0476161bfce5 +0 -0
  175. package/avatars/ef7d0917-a206-4f88-8b60-93f8253774dc +0 -0
  176. package/avatars/f1a554d6-1db3-4ff5-b0dc-b607d6c3b4ff +0 -0
  177. package/avatars/f1fecc21-c81f-49a8-88f3-f942a0a679f6 +0 -0
  178. package/avatars/f30a2427-1755-4053-813e-129a179e1dd3 +0 -0
  179. package/avatars/f5370717-5109-46a5-a8d7-e1dd996d0615 +0 -0
  180. package/avatars/f6dd7126-1144-4998-bbd3-d4e0fbee2e95 +0 -0
  181. package/avatars/f83413d5-0003-4756-9ece-745fd61cc468 +0 -0
  182. package/avatars/f9b3149e-7ec8-4bb3-a9b9-dcbe66dac197 +0 -0
  183. package/avatars/fa41d70b-857e-4423-bd7d-26ddcddc13b9 +0 -0
  184. package/avatars/fb551ee8-99c7-400b-8e1d-322ce4619998 +0 -0
  185. package/avatars/fe83a6d4-abb0-4ab0-b61d-76a7cc08be84 +0 -0
  186. package/dist/migrations/20210103192527_users.d.ts +0 -3
  187. package/dist/migrations/20210103192527_users.js +0 -30
  188. package/dist/migrations/20210103193502_mail.d.ts +0 -3
  189. package/dist/migrations/20210103193502_mail.js +0 -35
  190. package/dist/migrations/20210103193525_preKeys.d.ts +0 -3
  191. package/dist/migrations/20210103193525_preKeys.js +0 -30
  192. package/dist/migrations/20210103193553_oneTimeKeys.d.ts +0 -3
  193. package/dist/migrations/20210103193553_oneTimeKeys.js +0 -30
  194. package/dist/migrations/20210103193615_servers.d.ts +0 -3
  195. package/dist/migrations/20210103193615_servers.js +0 -28
  196. package/dist/migrations/20210103193729_channels.d.ts +0 -3
  197. package/dist/migrations/20210103193729_channels.js +0 -28
  198. package/dist/migrations/20210103193749_permissions.d.ts +0 -3
  199. package/dist/migrations/20210103193749_permissions.js +0 -30
  200. package/dist/migrations/20210103193801_files.d.ts +0 -3
  201. package/dist/migrations/20210103193801_files.js +0 -28
  202. package/dist/utils/createLogger.d.ts +0 -5
  203. package/dist/utils/createLogger.js +0 -41
  204. package/emoji/04d98632-2c86-421b-a407-17f14fe86f8f +0 -0
  205. package/emoji/1160ed6e-1163-4043-9808-4029e863ed30 +0 -0
  206. package/emoji/1547ab18-1635-4a80-a82d-ebbb767b9932 +0 -0
  207. package/emoji/16922521-f6cb-4de4-860c-27916b22c6ba +0 -0
  208. package/emoji/198a9432-0e41-4866-994a-448d4775afcb +0 -0
  209. package/emoji/1be886b3-c9c5-4593-b516-f357ed931f96 +0 -0
  210. package/emoji/1c2b3d1d-637f-4103-b066-4bc4511a3ad7 +0 -0
  211. package/emoji/1efd27e7-b15f-475c-8b32-9159d26b169d +0 -0
  212. package/emoji/270b9409-0ea5-4be2-a239-a8dce13f9c31 +0 -0
  213. package/emoji/27812f76-fee2-49dd-a217-363de6d159dc +0 -0
  214. package/emoji/297ec202-8c24-44c6-aead-689d6d461883 +0 -0
  215. package/emoji/2bf06d86-17cb-4f40-a5ef-bd75d239a1a3 +0 -0
  216. package/emoji/31a75163-1cce-4dc1-b0a2-ecad6a4c500b +0 -0
  217. package/emoji/35235635-fdbd-4273-8428-f3cb3e1e8fd3 +0 -0
  218. package/emoji/3690fff2-6824-4403-a6e3-16a6a54979a9 +0 -0
  219. package/emoji/391014c2-59e0-46a8-85ec-7a7fdaca1d2d +0 -0
  220. package/emoji/3b383dcb-6e76-4e85-8e16-7c68040c06c2 +0 -0
  221. package/emoji/42d617a7-b104-42f5-9618-473181f752cf +0 -0
  222. package/emoji/482495d3-cce9-4f88-bf2a-f6003f03a9b5 +0 -0
  223. package/emoji/48390e06-0efb-404c-89bd-5f2be241bd50 +0 -0
  224. package/emoji/4b808d8d-3248-4149-b919-71b108391bcf +0 -0
  225. package/emoji/4bc13544-d82a-4e32-bd17-a70592274314 +0 -0
  226. package/emoji/4fcebf70-8623-4343-8243-67c8547b2edd +0 -0
  227. package/emoji/509d09aa-1214-459c-8081-50918a17b9af +0 -0
  228. package/emoji/5272abd8-d4d7-4b90-acd2-bf30e6c27243 +0 -0
  229. package/emoji/53c272ce-48bd-4d7e-bfb8-a6482b88be54 +0 -0
  230. package/emoji/5b279e65-06f7-4b26-8b4c-d1b48fba728d +0 -0
  231. package/emoji/5bd141f9-4394-4108-9376-66ebbc2c2bc1 +0 -0
  232. package/emoji/5c769156-f9cb-40bd-ab89-4edeece613fd +0 -0
  233. package/emoji/5c85fba9-8ba7-4fc9-b1b2-48dc30d24a1b +0 -0
  234. package/emoji/61a5e565-d20b-40ba-a139-b0c73a6027f3 +0 -0
  235. package/emoji/6913f43d-dd45-456c-9641-a126104d9ae5 +0 -0
  236. package/emoji/6957e74e-9622-492d-a950-242db3752260 +0 -0
  237. package/emoji/6a14bab5-26af-4bfa-9c17-be7c2511976d +0 -0
  238. package/emoji/6be09439-509e-4095-a30a-b1c7c573895d +0 -0
  239. package/emoji/6cc435b1-fe53-433c-b5a9-2b2019053997 +0 -0
  240. package/emoji/74f1b2af-bc7e-4a0f-802a-64ded185d5e2 +0 -0
  241. package/emoji/7890ba09-f02f-428e-807d-006d03d51d4a +0 -0
  242. package/emoji/7dc69179-6b3c-4f40-b20b-0ff573deea2d +0 -0
  243. package/emoji/7dd1b6b1-439d-4279-916a-995408863172 +0 -0
  244. package/emoji/820498ad-a2c8-43a2-ab83-d26f9c2246d4 +0 -0
  245. package/emoji/8319469c-2787-44e5-91a6-c8c39810dd7c +0 -0
  246. package/emoji/86745d1d-9e59-4607-b2b0-46c741079be1 +0 -0
  247. package/emoji/887b3cff-ae9e-4b5f-ad00-3ca9fc72f689 +0 -0
  248. package/emoji/8c6cf621-71d6-4fca-abe6-e19f4dd7f883 +0 -0
  249. package/emoji/8ca6d32e-a1ef-4956-a416-d8d0d680f085 +0 -0
  250. package/emoji/8d979f5e-38a2-4dd1-b3e4-80938bbe499b +0 -0
  251. package/emoji/99f68ea0-e3fe-4f03-9cc3-5f7f5315404d +0 -0
  252. package/emoji/9ad3aedc-7f79-4d68-a144-82e5b5dc3033 +0 -0
  253. package/emoji/9e418c2f-1f0f-46c4-be39-3bda38a28545 +0 -0
  254. package/emoji/a1f616bf-7402-4e24-9111-18acaebabb48 +0 -0
  255. package/emoji/a25ed9c1-3f9c-4e5f-ade2-7b159fb9fbf4 +0 -0
  256. package/emoji/a5176bc2-39a8-467b-8c75-6fbbc81b59c7 +0 -0
  257. package/emoji/a584215c-6547-438b-8ae8-dd490b51890e +0 -0
  258. package/emoji/a739895f-cf61-4b7c-b350-8e8283aaf751 +0 -0
  259. package/emoji/aaa10dd2-02a2-499e-9e17-c83787436508 +0 -0
  260. package/emoji/ae90baf2-a0ef-4d4d-9cc4-94f8ddd60f45 +0 -0
  261. package/emoji/b0564c48-feae-431a-95f9-df597c6c124c +0 -0
  262. package/emoji/b218bb93-e69c-4793-a669-83316650c4e7 +0 -0
  263. package/emoji/b2998c27-85d5-4598-ab41-469aa8e0fcad +0 -0
  264. package/emoji/b3da08ba-4179-4b5d-826f-5fc15e1a3ad2 +0 -0
  265. package/emoji/b840eb6a-a917-4bb2-854b-8f1022e7904b +0 -0
  266. package/emoji/b84baa76-b4b0-4b83-bc83-78661cb4f1d4 +0 -0
  267. package/emoji/baf69d80-8b1d-4032-855f-605cf0d489c3 +0 -0
  268. package/emoji/bb4d372c-ccd0-4a47-b157-b6a3b9f763e2 +0 -0
  269. package/emoji/bdbd1627-c81d-42d9-b3f5-8979e2ab74dc +0 -0
  270. package/emoji/c257388f-8b85-450b-b168-ebdf8d8c3026 +0 -0
  271. package/emoji/c573fde1-faa9-4c1d-a172-e283645afcfd +0 -0
  272. package/emoji/c8e27810-e8ea-47ce-b7ec-cef0a6becb28 +0 -0
  273. package/emoji/cdeef182-b220-4850-9ecf-5d7c472fd754 +0 -0
  274. package/emoji/d59c1aa9-6f81-4c07-96dd-9953401ff211 +0 -0
  275. package/emoji/dd407dbf-a077-40ba-957f-337b3c5efdc7 +0 -0
  276. package/emoji/e01f6e06-5728-4e2c-90fb-314a5827b766 +0 -0
  277. package/emoji/e1f9ed12-a2ce-433d-b454-b833438a1f9c +0 -0
  278. package/emoji/e697e5c4-acd6-41cd-a43e-edee8da3ab7b +0 -0
  279. package/emoji/e8182220-3464-4e31-8c08-466baead7bfc +0 -0
  280. package/emoji/eb0f3fd5-abc9-4abf-b816-d8458aeb7ec8 +0 -0
  281. package/emoji/eb38ecf7-0d13-4c51-b96a-3777f79321c4 +0 -0
  282. package/emoji/ee515c85-b7ce-4493-a427-994cc0af0d59 +0 -0
  283. package/emoji/f485cef2-d3fa-4d59-88af-b79a3105cacf +0 -0
  284. package/emoji/ff0dab2a-7015-4e8c-b0d0-3569058359dc +0 -0
  285. package/files/01087968-07b6-4fdb-9aeb-fa9dc061be94 +0 -0
  286. package/files/030455b3-17cc-415f-b3b3-2bb56c92ee8b +0 -0
  287. package/files/06129f52-a858-4031-ad85-4d7f2bb793af +0 -0
  288. package/files/0a6155a9-069f-45e9-8a06-56992fe55187 +0 -0
  289. package/files/12b9dda5-feb1-4f20-a987-ad422db5ba73 +0 -0
  290. package/files/13c8fa1e-8821-4628-b607-9e4fa4510df7 +0 -0
  291. package/files/159b0ad3-1a30-419d-af22-28a1096ce825 +0 -0
  292. package/files/1699c563-6769-431b-8041-99a6a4386f25 +0 -0
  293. package/files/176b916c-0dd9-4b93-bfdc-b8ccabf15a96 +0 -0
  294. package/files/1a27dad9-8cc9-4a0c-9d4e-7bde63adda60 +0 -0
  295. package/files/1d29832c-059a-4190-bca6-b83ac77540d9 +0 -0
  296. package/files/1dcde013-8833-4369-8726-81236e4eb30e +0 -0
  297. package/files/2080fc9f-d2c4-4fbc-af84-232fe4900a4f +0 -0
  298. package/files/20889623-6869-46a2-999b-c07708c12521 +0 -0
  299. package/files/2107e243-c378-418d-a183-7df13873c65b +0 -0
  300. package/files/225ed61e-8f8c-4d17-b675-9a9f9918d5b4 +0 -0
  301. package/files/29ffba15-5acc-4ef8-b5a4-5ce61d3f6e85 +0 -0
  302. package/files/2a69434c-1d8a-4e9d-90d4-569aaeaae7e3 +0 -0
  303. package/files/2aec8fcf-25bf-478e-b2f6-fe67ad753071 +0 -0
  304. package/files/2c64490e-c7de-4cb2-b22e-70e2ac69d88f +0 -0
  305. package/files/2d80b4f4-6389-4f5d-8d32-f0ed93820907 +0 -0
  306. package/files/2f9dd26e-363f-4445-8ee5-28548007a33a +0 -0
  307. package/files/360dd8f5-76c4-4e86-ba22-9025dc7ca2a4 +0 -0
  308. package/files/3a0a7f5b-45a5-4340-ba7c-6a0fa2c63871 +0 -0
  309. package/files/3a8fa6be-8acf-4b7b-b653-25edc6b28cdc +0 -5
  310. package/files/3d9e191b-2c15-42aa-9928-c2cdbb5e14ca +0 -0
  311. package/files/3dd0f2ef-0d4e-4837-bffc-22aa645cbe85 +0 -0
  312. package/files/402c4b9b-adbf-4ab6-a21d-c17369b48abf +0 -0
  313. package/files/4685d988-33eb-4902-872f-3b824f497c8b +0 -0
  314. package/files/495f6c55-07f8-4713-a444-a2261a789b94 +0 -0
  315. package/files/4acba8e3-567b-4062-b81b-340e205a01de +0 -0
  316. package/files/4c0a10cc-395b-474a-8140-677ed607da89 +0 -0
  317. package/files/537584d9-25ad-4830-808a-a1e3d63e2a52 +0 -0
  318. package/files/5519f10d-745e-4474-8ca8-6c111693704c +0 -0
  319. package/files/5563cf92-a5e3-4be3-867d-9647a02298d4 +0 -0
  320. package/files/55b70d9b-fd58-4800-832c-d6b4521ba6d0 +0 -0
  321. package/files/5623cff3-ce9b-403b-9915-50d2bcbc981c +0 -0
  322. package/files/576314ba-2a1a-4753-8d77-2a9e04f509d1 +0 -0
  323. package/files/58ed97ae-0eac-4e04-add1-76646effa2d5 +0 -0
  324. package/files/68638efd-5389-481d-a841-36164c62c078 +0 -0
  325. package/files/6936d34b-a1f8-4a9d-b005-5544dbdcf5e5 +0 -0
  326. package/files/6bda9e88-3a28-47f7-994e-900ede6bf984 +0 -0
  327. package/files/7024a3b9-a863-4618-9dbd-fa6502017ae0 +0 -0
  328. package/files/707caccb-3780-456d-9056-c20bfdfc0e5b +0 -0
  329. package/files/74b8c73c-032b-4640-a5b3-d30dd270cbcb +0 -0
  330. package/files/767cb262-2974-40f8-8182-f7770b431923 +0 -0
  331. package/files/7a669935-cb48-4349-b26b-7705f8a04fbc +0 -0
  332. package/files/7bc8f678-6ee7-454c-a1f8-bb9358b89c95 +0 -0
  333. package/files/7ccc2699-07ec-4177-a9ce-ee7dc952fda1 +0 -0
  334. package/files/7e0eabf4-b334-4683-8156-ab8d949a0532 +0 -0
  335. package/files/7f0644ab-02a3-4121-adfc-29a7c55cf804 +0 -0
  336. package/files/7ff3266d-f103-48ce-90dd-95b9dfe5fcc9 +0 -0
  337. package/files/836e2e8e-aefd-4b4a-a9b9-bf7436158a8c +0 -0
  338. package/files/875f7ae5-fa23-4fc5-b04a-8433a7f7089c +0 -0
  339. package/files/8ca62da9-f204-49e3-b418-9451661b2904 +0 -0
  340. package/files/9283054c-107f-474d-b61e-f1d0061bcb86 +0 -0
  341. package/files/93b1ce7f-9566-452b-b4be-30f87d3de150 +0 -0
  342. package/files/93fb51c7-e19b-4ac1-9dc3-aeb8da0672ed +0 -0
  343. package/files/9b54a3a1-534a-4ed5-b016-3c74ed4c9edd +0 -0
  344. package/files/9b5beb6f-712d-4969-b127-fd66c9b2a9c6 +0 -0
  345. package/files/9e964fbf-d063-498e-b2e3-79f8d6afcf5f +0 -0
  346. package/files/a66b7a9f-58c2-47a8-a429-a6f0647c6fe9 +0 -0
  347. package/files/a7cbda7d-81ba-40f7-a997-51146af63e5f +0 -0
  348. package/files/ac01e83a-e572-41b6-81ab-c992cff7c170 +0 -0
  349. package/files/ad11a58f-f963-4233-bd29-1658b6b7e600 +0 -0
  350. package/files/ae60a4a8-08b9-4521-a0a3-d015a8b3ed08 +0 -0
  351. package/files/b1d3cf27-8d76-4cf9-aa51-d7c6bfd1b3bf +0 -0
  352. package/files/b2c68863-8554-4ac6-8e99-821f0267cf91 +0 -0
  353. package/files/b3043e01-a771-44af-bf19-5327646ff929 +0 -0
  354. package/files/b6d01b89-def5-4c7c-8e97-a3d88617f8f4 +0 -0
  355. package/files/b8760b32-bb1e-4cd7-a9d6-29c6e0b071bc +0 -0
  356. package/files/ba5e0470-44f7-47b3-bcb5-eeab3ca8c292 +0 -0
  357. package/files/c3969b5f-43ae-43f3-9bdd-d3959c79ca01 +0 -0
  358. package/files/caecd488-dbe6-4a30-a400-bced2ba8dae6 +0 -0
  359. package/files/d7d865b8-3a05-4ed1-b95d-b93dc1ebb9a9 +0 -0
  360. package/files/dbca5f31-cf38-4c1e-83b0-5ec8473196fc +0 -0
  361. package/files/dbf07e82-fff6-4985-bebe-d62c0458bfd0 +0 -0
  362. package/files/dd759c20-eead-4e57-9e74-4d3a2b978e91 +0 -5
  363. package/files/de0b2cf1-981b-4e4a-a04e-ac185d1620cd +0 -0
  364. package/files/de6837fe-5aa2-4ff9-a067-2646a008c780 +0 -0
  365. package/files/e2fde852-91cb-4e01-88a2-ee086b5f227c +0 -0
  366. package/files/e391d1ce-8d39-460c-a462-791730131f7f +0 -0
  367. package/files/e6d9b60f-2b1b-4c6f-ba6d-02f6de90d40f +0 -0
  368. package/files/f693c62d-2ac8-49fd-aa38-30904e013e3c +0 -0
  369. package/files/f749fdf5-193e-41f7-b643-5696f67c6402 +0 -0
  370. package/files/f8910384-e75c-4d65-825f-52a6748f6475 +0 -0
  371. package/files/fad8826b-e952-4acb-a509-3e6543b94d61 +0 -0
  372. package/jest.config.js +0 -13
  373. package/spire.sqlite +0 -0
@@ -1,520 +1,515 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
- return new (P || (P = Promise))(function (resolve, reject) {
24
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
- step((generator = generator.apply(thisArg, _arguments || [])).next());
28
- });
29
- };
30
- var __importDefault = (this && this.__importDefault) || function (mod) {
31
- return (mod && mod.__esModule) ? mod : { "default": mod };
32
- };
33
- Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.initApp = exports.msgpackParser = exports.protect = exports.ALLOWED_IMAGE_TYPES = exports.EXPIRY_TIME = void 0;
35
- const fs_1 = __importDefault(require("fs"));
36
- const types_1 = require("@vex-chat/types");
37
- const cookie_parser_1 = __importDefault(require("cookie-parser"));
38
- const cors_1 = __importDefault(require("cors"));
39
- const express_1 = __importDefault(require("express"));
40
- const helmet_1 = __importDefault(require("helmet"));
41
- const morgan_1 = __importDefault(require("morgan"));
42
- const parse_duration_1 = __importDefault(require("parse-duration"));
43
- const crypto_1 = require("@vex-chat/crypto");
44
- const atob_1 = __importDefault(require("atob"));
45
- const file_type_1 = __importDefault(require("file-type"));
46
- const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
47
- const msgpack_lite_1 = __importDefault(require("msgpack-lite"));
48
- const multer_1 = __importDefault(require("multer"));
49
- const tweetnacl_1 = __importDefault(require("tweetnacl"));
50
- const avatar_1 = require("./avatar");
51
- const file_1 = require("./file");
52
- const invite_1 = require("./invite");
53
- const user_1 = require("./user");
54
- const uuid = __importStar(require("uuid"));
55
- const ClientManager_1 = require("../ClientManager");
56
- const Spire_1 = require("../Spire");
57
- const utils_1 = require("./utils");
1
+ import * as fs from "node:fs";
2
+ import * as fsp from "node:fs/promises";
3
+ import express from "express";
4
+ import { XUtils } from "@vex-chat/crypto";
5
+ import { xSignOpen } from "@vex-chat/crypto";
6
+ import { PreKeysWSSchema, TokenScopes, UserSchema } from "@vex-chat/types";
7
+ import cors from "cors";
8
+ import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
9
+ import helmet from "helmet";
10
+ import jwt from "jsonwebtoken";
11
+ import multer from "multer";
12
+ import parseDuration from "parse-duration";
13
+ import { stringify as uuidStringify } from "uuid";
14
+ import { z } from "zod/v4";
15
+ import { POWER_LEVELS } from "../ClientManager.js";
16
+ import { JWT_EXPIRY } from "../Spire.js";
17
+ import { getJwtSecret } from "../utils/jwtSecret.js";
18
+ import { msgpack } from "../utils/msgpack.js";
19
+ import { getAvatarRouter } from "./avatar.js";
20
+ import { errorHandler } from "./errors.js";
21
+ import { getFileRouter } from "./file.js";
22
+ import { getInviteRouter } from "./invite.js";
23
+ import { setupDocs } from "./openapi.js";
24
+ import { hasAnyPermission, hasPermission, userHasPermission, } from "./permissions.js";
25
+ import { globalLimiter } from "./rateLimit.js";
26
+ import { getUserRouter } from "./user.js";
27
+ import { censorUser, getParam, getUser } from "./utils.js";
58
28
  // expiry of regkeys
59
- exports.EXPIRY_TIME = 1000 * 60 * 5;
60
- exports.ALLOWED_IMAGE_TYPES = [
29
+ export const EXPIRY_TIME = 1000 * 60 * 5;
30
+ export const ALLOWED_IMAGE_TYPES = [
61
31
  "image/jpeg",
62
32
  "image/png",
63
33
  "image/gif",
64
34
  "image/apng",
65
35
  "image/avif",
66
36
  ];
67
- const TokenScopes = types_1.XTypes.HTTP.TokenScopes;
68
- const checkAuth = (req, res, next) => {
69
- if (req.cookies.auth) {
37
+ // ── Zod schemas for trust-boundary validation ──────────────────────────
38
+ const invitePayload = z.object({
39
+ duration: z.string().min(1),
40
+ serverID: z.string().min(1),
41
+ });
42
+ const channelPayload = z.object({
43
+ name: z.string().min(1).max(255),
44
+ });
45
+ const deviceListPayload = z.array(z.string());
46
+ const connectPayload = z.object({
47
+ signed: z.custom((val) => val instanceof Uint8Array),
48
+ });
49
+ const safePathParam = z.string().regex(/^[a-zA-Z0-9._-]+$/);
50
+ const emojiPayload = z.object({
51
+ file: z.string().optional(),
52
+ name: z.string().min(1),
53
+ signed: z.string().optional(),
54
+ });
55
+ const jwtUserPayload = z.object({
56
+ exp: z.number().optional(),
57
+ user: UserSchema,
58
+ });
59
+ const jwtDevicePayload = z.object({
60
+ device: z.object({
61
+ deleted: z.boolean(),
62
+ deviceID: z.string(),
63
+ lastLogin: z.string(),
64
+ name: z.string(),
65
+ owner: z.string(),
66
+ signKey: z.string(),
67
+ }),
68
+ });
69
+ /** Extract Bearer token from Authorization header. */
70
+ function extractBearer(req) {
71
+ const header = req.headers.authorization;
72
+ if (!header || !header.startsWith("Bearer "))
73
+ return null;
74
+ return header.slice(7);
75
+ }
76
+ const checkAuth = (req, _res, next) => {
77
+ const token = extractBearer(req);
78
+ if (token) {
70
79
  try {
71
- const result = jsonwebtoken_1.default.verify(req.cookies.auth, process.env.SPK);
72
- // lol glad this is a try/catch block
73
- req.user = result.user;
74
- req.exp = result.exp;
80
+ const result = jwt.verify(token, getJwtSecret());
81
+ const parsed = jwtUserPayload.safeParse(result);
82
+ if (parsed.success) {
83
+ req.user = parsed.data.user;
84
+ if (parsed.data.exp !== undefined) {
85
+ req.exp = parsed.data.exp;
86
+ }
87
+ req.bearerToken = token;
88
+ }
75
89
  }
76
- catch (err) {
77
- console.warn(err.toString());
90
+ catch {
91
+ // Token verification failed — continue without auth
78
92
  }
79
93
  }
80
94
  next();
81
95
  };
82
- const checkDevice = (req, res, next) => {
83
- if (req.cookies.device) {
96
+ const checkDevice = (req, _res, next) => {
97
+ const token = req.headers["x-device-token"];
98
+ if (typeof token === "string" && token) {
84
99
  try {
85
- const result = jsonwebtoken_1.default.verify(req.cookies.device, process.env.SPK);
86
- // lol glad this is a try/catch block
87
- req.device = result.device;
100
+ const result = jwt.verify(token, getJwtSecret());
101
+ const parsed = jwtDevicePayload.safeParse(result);
102
+ if (parsed.success) {
103
+ req.device = parsed.data.device;
104
+ }
88
105
  }
89
- catch (err) {
90
- console.warn(err.toString());
106
+ catch {
107
+ // Device token verification failed — continue without device
91
108
  }
92
109
  }
93
110
  next();
94
111
  };
95
- const protect = (req, res, next) => {
112
+ export const protect = (req, res, next) => {
96
113
  if (!req.user) {
97
114
  res.sendStatus(401);
98
- throw new Error("not authenticated!");
115
+ return;
99
116
  }
100
117
  next();
101
118
  };
102
- exports.protect = protect;
103
- const msgpackParser = (req, res, next) => {
119
+ export const msgpackParser = (req, res, next) => {
104
120
  if (req.is("application/msgpack")) {
105
121
  try {
106
- req.body = msgpack_lite_1.default.decode(req.body);
122
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument -- Express req.body is any; decoded body is validated by route-level Zod schemas
123
+ req.body = msgpack.decode(req.body);
107
124
  }
108
- catch (err) {
125
+ catch {
109
126
  res.sendStatus(400);
110
127
  return;
111
128
  }
112
129
  }
113
130
  next();
114
131
  };
115
- exports.msgpackParser = msgpackParser;
116
132
  const directories = ["files", "avatars"];
117
133
  for (const dir of directories) {
118
- if (!fs_1.default.existsSync(dir)) {
119
- fs_1.default.mkdirSync(dir);
134
+ if (!fs.existsSync(dir)) {
135
+ fs.mkdirSync(dir);
120
136
  }
121
137
  }
122
- const initApp = (api, db, log, tokenValidator, signKeys, notify) => {
138
+ export const initApp = (api, db, tokenValidator, signKeys, notify) => {
123
139
  // INIT ROUTERS
124
- const userRouter = user_1.getUserRouter(db, log, tokenValidator);
125
- const fileRouter = file_1.getFileRouter(db, log);
126
- const avatarRouter = avatar_1.getAvatarRouter(db, log);
127
- const inviteRouter = invite_1.getInviteRouter(db, log, tokenValidator, notify);
140
+ const userRouter = getUserRouter(db, tokenValidator);
141
+ const fileRouter = getFileRouter(db);
142
+ const avatarRouter = getAvatarRouter();
143
+ const inviteRouter = getInviteRouter(db, tokenValidator, notify);
128
144
  // MIDDLEWARE
129
- api.use(express_1.default.json({ limit: "20mb" }));
130
- api.use(express_1.default.raw({
131
- type: "application/msgpack",
145
+ // Global per-IP rate limit is the FIRST middleware so a flooded
146
+ // source hits the limiter before Express spends any cycles on
147
+ // body parsing, helmet, or auth. See src/server/rateLimit.ts.
148
+ api.use(globalLimiter);
149
+ api.use(express.json({ limit: "20mb" }));
150
+ api.use(express.raw({
132
151
  limit: "20mb",
152
+ type: "application/msgpack",
133
153
  }));
134
- api.use(helmet_1.default());
135
- api.use(cookie_parser_1.default());
136
- api.use(exports.msgpackParser);
154
+ api.use(helmet());
155
+ api.use(msgpackParser);
137
156
  api.use(checkAuth);
138
157
  api.use(checkDevice);
139
- if (!jestRun()) {
140
- api.use(morgan_1.default("dev", { stream: process.stdout }));
141
- }
142
- api.use(cors_1.default({ credentials: true }));
143
- api.get("/server/:id", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
144
- const server = yield db.retrieveServer(req.params.id);
158
+ const allowedOrigins = process.env["CORS_ORIGINS"]?.split(",") ?? [];
159
+ api.use(cors({
160
+ credentials: true,
161
+ origin: allowedOrigins.length > 0 ? allowedOrigins : false,
162
+ }));
163
+ api.get("/server/:id", protect, async (req, res) => {
164
+ const server = await db.retrieveServer(getParam(req, "id"));
145
165
  if (server) {
146
- return res.send(msgpack_lite_1.default.encode(server));
166
+ return res.send(msgpack.encode(server));
147
167
  }
148
168
  else {
149
- res.sendStatus(404);
169
+ return res.sendStatus(404);
150
170
  }
151
- }));
152
- api.post("/server/:name", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
153
- const userDetails = req.user;
154
- const serverName = atob_1.default(req.params.name);
155
- const server = yield db.createServer(serverName, userDetails.userID);
156
- res.send(msgpack_lite_1.default.encode(server));
157
- }));
158
- api.post("/server/:serverID/invites", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
159
- const userDetails = req.user;
160
- const payload = req.body;
161
- const serverEntry = yield db.retrieveServer(req.params.serverID);
171
+ });
172
+ api.post("/server/:name", protect, async (req, res) => {
173
+ const userDetails = getUser(req);
174
+ const serverName = atob(getParam(req, "name"));
175
+ const server = await db.createServer(serverName, userDetails.userID);
176
+ res.send(msgpack.encode(server));
177
+ });
178
+ api.post("/server/:serverID/invites", protect, async (req, res) => {
179
+ const userDetails = getUser(req);
180
+ const parsedPayload = invitePayload.safeParse(req.body);
181
+ if (!parsedPayload.success) {
182
+ res.status(400).json({
183
+ error: "Invalid invite payload",
184
+ issues: parsedPayload.error.issues,
185
+ });
186
+ return;
187
+ }
188
+ const payload = parsedPayload.data;
189
+ const serverEntry = await db.retrieveServer(getParam(req, "serverID"));
162
190
  if (!serverEntry) {
163
191
  res.sendStatus(404);
164
192
  return;
165
193
  }
166
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
167
- let hasPermission = false;
168
- for (const permission of permissions) {
169
- if (permission.resourceID === req.params.serverID &&
170
- permission.powerLevel > ClientManager_1.POWER_LEVELS.INVITE) {
171
- hasPermission = true;
172
- }
173
- }
174
- if (!hasPermission) {
175
- log.warn("No permission!");
194
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
195
+ if (!hasPermission(permissions, getParam(req, "serverID"), POWER_LEVELS.INVITE)) {
176
196
  res.sendStatus(401);
177
197
  return;
178
198
  }
179
- const duration = parse_duration_1.default(payload.duration, "ms");
199
+ const duration = parseDuration(payload.duration, "ms");
180
200
  if (!duration) {
181
201
  res.sendStatus(400);
182
202
  return;
183
203
  }
184
204
  const expires = new Date(Date.now() + duration);
185
- const invite = yield db.createInvite(uuid.v4(), serverEntry.serverID, userDetails.userID, expires.toString());
186
- res.send(msgpack_lite_1.default.encode(invite));
187
- }));
188
- api.get("/server/:serverID/invites", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
189
- const userDetails = req.user;
190
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
191
- let hasPermission = false;
192
- for (const permission of permissions) {
193
- if (permission.resourceID === req.params.serverID &&
194
- permission.powerLevel > ClientManager_1.POWER_LEVELS.INVITE) {
195
- hasPermission = true;
196
- }
197
- }
198
- if (!hasPermission) {
205
+ const invite = await db.createInvite(crypto.randomUUID(), serverEntry.serverID, userDetails.userID, expires.toString());
206
+ res.send(msgpack.encode(invite));
207
+ });
208
+ api.get("/server/:serverID/invites", protect, async (req, res) => {
209
+ const userDetails = getUser(req);
210
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
211
+ if (!hasPermission(permissions, getParam(req, "serverID"), POWER_LEVELS.INVITE)) {
199
212
  res.sendStatus(401);
200
213
  return;
201
214
  }
202
- const inviteList = yield db.retrieveServerInvites(req.params.serverID);
203
- res.send(msgpack_lite_1.default.encode(inviteList));
204
- }));
205
- api.delete("/server/:id", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
206
- const userDetails = req.user;
207
- const serverID = req.params.id;
208
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
209
- for (const permission of permissions) {
210
- if (permission.resourceID === serverID &&
211
- permission.powerLevel > ClientManager_1.POWER_LEVELS.DELETE) {
212
- // msg.data is the serverID
213
- yield db.deleteServer(serverID);
214
- res.sendStatus(200);
215
- return;
216
- }
215
+ const inviteList = await db.retrieveServerInvites(getParam(req, "serverID"));
216
+ res.send(msgpack.encode(inviteList));
217
+ });
218
+ api.delete("/server/:id", protect, async (req, res) => {
219
+ const userDetails = getUser(req);
220
+ const serverID = getParam(req, "id");
221
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
222
+ if (hasPermission(permissions, serverID, POWER_LEVELS.DELETE)) {
223
+ await db.deleteServer(serverID);
224
+ res.sendStatus(200);
225
+ return;
217
226
  }
218
227
  res.sendStatus(401);
219
- }));
220
- api.post("/server/:id/channels", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
221
- const userDetails = req.user;
222
- const serverID = req.params.id;
228
+ });
229
+ api.post("/server/:id/channels", protect, async (req, res) => {
230
+ const userDetails = getUser(req);
231
+ const serverID = getParam(req, "id");
223
232
  // resourceID is serverID
224
- const { name } = req.body;
225
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
226
- for (const permission of permissions) {
227
- if (permission.resourceID === serverID &&
228
- permission.powerLevel > ClientManager_1.POWER_LEVELS.CREATE) {
229
- const channel = yield db.createChannel(name, serverID);
230
- res.send(msgpack_lite_1.default.encode(channel));
231
- const affectedUsers = yield db.retrieveAffectedUsers(serverID);
232
- // tell everyone about server change
233
- for (const user of affectedUsers) {
234
- notify(user.userID, "serverChange", uuid.v4(), serverID);
235
- }
236
- return;
237
- }
233
+ const parsedBody = channelPayload.safeParse(req.body);
234
+ if (!parsedBody.success) {
235
+ res.status(400).json({
236
+ error: "Invalid channel payload",
237
+ issues: parsedBody.error.issues,
238
+ });
239
+ return;
238
240
  }
239
- res.sendStatus(401);
240
- }));
241
- api.get("/server/:id/channels", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
242
- const serverID = req.params.id;
243
- const userDetails = req.user;
244
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
245
- for (const permission of permissions) {
246
- if (serverID === permission.resourceID) {
247
- const channels = yield db.retrieveChannels(permission.resourceID);
248
- res.send(msgpack_lite_1.default.encode(channels));
249
- return;
241
+ const { name } = parsedBody.data;
242
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
243
+ if (hasPermission(permissions, serverID, POWER_LEVELS.CREATE)) {
244
+ const channel = await db.createChannel(name, serverID);
245
+ res.send(msgpack.encode(channel));
246
+ const affectedUsers = await db.retrieveAffectedUsers(serverID);
247
+ // tell everyone about server change
248
+ for (const user of affectedUsers) {
249
+ notify(user.userID, "serverChange", crypto.randomUUID(), serverID);
250
250
  }
251
+ return;
251
252
  }
252
253
  res.sendStatus(401);
253
- }));
254
- api.get("/server/:serverID/emoji", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
255
- const rows = yield db.retrieveEmojiList(req.params.serverID);
256
- res.send(msgpack_lite_1.default.encode(rows));
257
- }));
258
- api.get("/server/:serverID/permissions", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
259
- const userDetails = req.user;
260
- const serverID = req.params.serverID;
261
- try {
262
- const permissions = yield db.retrievePermissionsByResourceID(serverID);
263
- if (permissions) {
264
- let found = false;
265
- for (const perm of permissions) {
266
- if (perm.userID === userDetails.userID) {
267
- res.send(msgpack_lite_1.default.encode(permissions));
268
- found = true;
269
- break;
270
- }
271
- }
272
- if (!found) {
273
- res.sendStatus(401);
274
- return;
275
- }
276
- }
277
- else {
278
- res.sendStatus(404);
279
- }
254
+ });
255
+ api.get("/server/:id/channels", protect, async (req, res) => {
256
+ const serverID = getParam(req, "id");
257
+ const userDetails = getUser(req);
258
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
259
+ if (hasAnyPermission(permissions, serverID)) {
260
+ const channels = await db.retrieveChannels(serverID);
261
+ res.send(msgpack.encode(channels));
262
+ return;
280
263
  }
281
- catch (err) {
282
- res.status(500).send(err.toString());
264
+ res.sendStatus(401);
265
+ });
266
+ api.get("/server/:serverID/emoji", protect, async (req, res) => {
267
+ const rows = await db.retrieveEmojiList(getParam(req, "serverID"));
268
+ res.send(msgpack.encode(rows));
269
+ });
270
+ api.get("/server/:serverID/permissions", protect, async (req, res) => {
271
+ const userDetails = getUser(req);
272
+ const serverID = getParam(req, "serverID");
273
+ const permissions = await db.retrievePermissionsByResourceID(serverID);
274
+ const canSee = permissions.some((perm) => perm.userID === userDetails.userID);
275
+ if (!canSee) {
276
+ res.sendStatus(401);
277
+ return;
283
278
  }
284
- }));
285
- api.delete("/channel/:id", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
286
- const channelID = req.params.id;
287
- const userDetails = req.user;
288
- const channel = yield db.retrieveChannel(channelID);
279
+ res.send(msgpack.encode(permissions));
280
+ });
281
+ api.delete("/channel/:id", protect, async (req, res) => {
282
+ const channelID = getParam(req, "id");
283
+ const userDetails = getUser(req);
284
+ const channel = await db.retrieveChannel(channelID);
289
285
  if (!channel) {
290
286
  res.sendStatus(401);
291
287
  return;
292
288
  }
293
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
294
- let found = false;
289
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
295
290
  for (const permission of permissions) {
296
291
  if (permission.resourceID === channel.serverID &&
297
292
  permission.powerLevel > 50) {
298
- found = true;
299
- // msg.data is the channelID
300
- yield db.deleteChannel(channelID);
293
+ await db.deleteChannel(channelID);
301
294
  res.sendStatus(200);
302
- const affectedUsers = yield db.retrieveAffectedUsers(channel.serverID);
295
+ const affectedUsers = await db.retrieveAffectedUsers(channel.serverID);
303
296
  // tell everyone about server change
304
297
  for (const user of affectedUsers) {
305
- notify(user.userID, "serverChange", uuid.v4(), channel.serverID);
298
+ notify(user.userID, "serverChange", crypto.randomUUID(), channel.serverID);
306
299
  }
307
300
  return;
308
301
  }
309
302
  }
310
303
  res.sendStatus(401);
311
- }));
312
- api.get("/channel/:id", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
313
- const channel = yield db.retrieveChannel(req.params.id);
304
+ });
305
+ api.get("/channel/:id", protect, async (req, res) => {
306
+ const channel = await db.retrieveChannel(getParam(req, "id"));
314
307
  if (channel) {
315
- return res.send(msgpack_lite_1.default.encode(channel));
308
+ return res.send(msgpack.encode(channel));
316
309
  }
317
310
  else {
311
+ return res.sendStatus(404);
312
+ }
313
+ });
314
+ api.delete("/permission/:permissionID", protect, async (req, res) => {
315
+ const permissionID = getParam(req, "permissionID");
316
+ const userDetails = getUser(req);
317
+ const permToDelete = await db.retrievePermission(permissionID);
318
+ if (!permToDelete) {
318
319
  res.sendStatus(404);
320
+ return;
319
321
  }
320
- }));
321
- api.delete("/permission/:permissionID", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
322
- const permissionID = req.params.permissionID;
323
- const userDetails = req.user;
324
- try {
325
- // msg.data is permID
326
- const permToDelete = yield db.retrievePermission(permissionID);
327
- if (!permToDelete) {
328
- res.sendStatus(404);
322
+ const permissions = await db.retrievePermissions(userDetails.userID, permToDelete.resourceType);
323
+ for (const perm of permissions) {
324
+ if (perm.resourceID === permToDelete.resourceID &&
325
+ (perm.userID === userDetails.userID ||
326
+ (perm.powerLevel > POWER_LEVELS.DELETE &&
327
+ perm.powerLevel > permToDelete.powerLevel))) {
328
+ await db.deletePermission(permToDelete.permissionID);
329
+ res.sendStatus(200);
329
330
  return;
330
331
  }
331
- const permissions = yield db.retrievePermissions(userDetails.userID, permToDelete.resourceType);
332
- for (const perm of permissions) {
333
- // msg.data is resourceID
334
- if (perm.resourceID === permToDelete.resourceID &&
335
- (perm.userID === userDetails.userID ||
336
- (perm.powerLevel > ClientManager_1.POWER_LEVELS.DELETE &&
337
- perm.powerLevel > permToDelete.powerLevel))) {
338
- db.deletePermission(permToDelete.permissionID);
339
- res.sendStatus(200);
340
- return;
341
- }
342
- }
343
- res.sendStatus(401);
344
- return;
345
332
  }
346
- catch (err) {
347
- res.status(500).send(err.toString());
333
+ res.sendStatus(401);
334
+ });
335
+ api.post("/userList/:channelID", protect, async (req, res) => {
336
+ const userDetails = getUser(req);
337
+ const channelID = getParam(req, "channelID");
338
+ const channel = await db.retrieveChannel(channelID);
339
+ if (!channel) {
340
+ res.sendStatus(404);
341
+ return;
348
342
  }
349
- }));
350
- api.post("/userList/:channelID", (req, res) => __awaiter(void 0, void 0, void 0, function* () {
351
- const userDetails = req.user;
352
- const channelID = req.params.channelID;
353
- try {
354
- const channel = yield db.retrieveChannel(channelID);
355
- if (!channel) {
356
- res.sendStatus(404);
343
+ const permissions = await db.retrievePermissions(userDetails.userID, "server");
344
+ for (const permission of permissions) {
345
+ if (permission.resourceID === channel.serverID) {
346
+ const groupMembers = await db.retrieveGroupMembers(channelID);
347
+ res.send(msgpack.encode(groupMembers.map((user) => censorUser(user))));
357
348
  return;
358
349
  }
359
- const permissions = yield db.retrievePermissions(userDetails.userID, "server");
360
- for (const permission of permissions) {
361
- if (permission.resourceID === channel.serverID) {
362
- // we've got the permission, it's ok to give them the userlist
363
- const groupMembers = yield db.retrieveGroupMembers(channelID);
364
- res.send(msgpack_lite_1.default.encode(groupMembers.map((user) => utils_1.censorUser(user))));
365
- }
366
- }
367
350
  }
368
- catch (err) {
369
- log.error(err.toString());
370
- res.status(500).send(err.toString());
351
+ res.sendStatus(401);
352
+ });
353
+ api.post("/deviceList", protect, async (req, res) => {
354
+ const parsed = deviceListPayload.safeParse(req.body);
355
+ if (!parsed.success) {
356
+ res.status(400).json({
357
+ error: "Expected array of user ID strings",
358
+ issues: parsed.error.issues,
359
+ });
360
+ return;
371
361
  }
372
- }));
373
- api.post("/deviceList", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
374
- const userIDs = req.body;
375
- const devices = yield db.retrieveUserDeviceList(userIDs);
376
- res.send(msgpack_lite_1.default.encode(devices));
377
- }));
378
- api.get("/device/:id", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
379
- const device = yield db.retrieveDevice(req.params.id);
362
+ const devices = await db.retrieveUserDeviceList(parsed.data);
363
+ res.send(msgpack.encode(devices));
364
+ });
365
+ api.get("/device/:id", protect, async (req, res) => {
366
+ const device = await db.retrieveDevice(getParam(req, "id"));
380
367
  if (device) {
381
- return res.send(msgpack_lite_1.default.encode(device));
368
+ return res.send(msgpack.encode(device));
382
369
  }
383
370
  else {
384
- res.sendStatus(404);
371
+ return res.sendStatus(404);
385
372
  }
386
- }));
387
- api.post("/device/:id/keyBundle", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
373
+ });
374
+ api.post("/device/:id/keyBundle", protect, async (req, res) => {
388
375
  try {
389
- const keyBundle = yield db.getKeyBundle(req.params.id);
376
+ const keyBundle = await db.getKeyBundle(getParam(req, "id"));
390
377
  if (keyBundle) {
391
- res.send(msgpack_lite_1.default.encode(keyBundle));
378
+ res.send(msgpack.encode(keyBundle));
392
379
  }
393
380
  else {
394
381
  res.sendStatus(404);
395
382
  }
396
383
  }
397
- catch (err) {
384
+ catch {
398
385
  res.sendStatus(500);
399
386
  }
400
- }));
401
- api.post("/device/:id/mail", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
402
- const deviceDetails = req
403
- .device;
387
+ });
388
+ api.post("/device/:id/mail", protect, async (req, res) => {
389
+ const deviceDetails = req.device;
404
390
  if (!deviceDetails) {
405
391
  res.sendStatus(401);
406
392
  return;
407
393
  }
408
- try {
409
- const inbox = yield db.retrieveMail(deviceDetails.deviceID);
410
- res.send(msgpack_lite_1.default.encode(inbox));
411
- }
412
- catch (err) {
413
- res.status(500).send(err.toString());
394
+ const inbox = await db.retrieveMail(deviceDetails.deviceID);
395
+ res.send(msgpack.encode(inbox));
396
+ });
397
+ api.post("/device/:id/connect", protect, async (req, res) => {
398
+ const parsedBody = connectPayload.safeParse(req.body);
399
+ if (!parsedBody.success) {
400
+ res.status(400).json({
401
+ error: "Invalid connect payload",
402
+ issues: parsedBody.error.issues,
403
+ });
404
+ return;
414
405
  }
415
- }));
416
- api.post("/device/:id/connect", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
417
- const { signed } = req.body;
418
- const device = yield db.retrieveDevice(req.params.id);
406
+ const { signed } = parsedBody.data;
407
+ const device = await db.retrieveDevice(getParam(req, "id"));
419
408
  if (!device) {
420
409
  res.sendStatus(404);
421
410
  return;
422
411
  }
423
- const regKey = tweetnacl_1.default.sign.open(signed, crypto_1.XUtils.decodeHex(device.signKey));
412
+ const regKey = xSignOpen(signed, XUtils.decodeHex(device.signKey));
424
413
  if (regKey &&
425
- tokenValidator(uuid.stringify(regKey), TokenScopes.Connect)) {
426
- const token = jsonwebtoken_1.default.sign({ device }, process.env.SPK, {
427
- expiresIn: Spire_1.JWT_EXPIRY,
414
+ tokenValidator(uuidStringify(regKey), TokenScopes.Connect)) {
415
+ const token = jwt.sign({ device }, getJwtSecret(), {
416
+ expiresIn: JWT_EXPIRY,
428
417
  });
429
- jsonwebtoken_1.default.verify(token, process.env.SPK);
430
- res.cookie("device", token, { path: "/" });
431
- res.sendStatus(200);
418
+ jwt.verify(token, getJwtSecret());
419
+ res.send(msgpack.encode({ deviceToken: token }));
432
420
  }
433
421
  else {
434
422
  res.sendStatus(401);
435
423
  }
436
- }));
437
- api.get("/device/:id/otk/count", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
438
- const deviceDetails = req
439
- .device;
424
+ });
425
+ api.get("/device/:id/otk/count", protect, async (req, res) => {
426
+ const deviceDetails = req.device;
440
427
  if (!deviceDetails) {
441
428
  res.sendStatus(401);
442
429
  return;
443
430
  }
444
- try {
445
- const count = yield db.getOTKCount(deviceDetails.deviceID);
446
- res.send(msgpack_lite_1.default.encode({ count }));
431
+ const count = await db.getOTKCount(deviceDetails.deviceID);
432
+ res.send(msgpack.encode({ count }));
433
+ });
434
+ api.post("/device/:id/otk", protect, async (req, res) => {
435
+ const parsedOTKs = z.array(PreKeysWSSchema).safeParse(req.body);
436
+ if (!parsedOTKs.success) {
437
+ res.status(400).json({
438
+ error: "Invalid OTK payload",
439
+ issues: parsedOTKs.error.issues,
440
+ });
447
441
  return;
448
442
  }
449
- catch (err) {
450
- res.status(500).send(err.toString());
443
+ const submittedOTKs = parsedOTKs.data;
444
+ if (submittedOTKs.length === 0) {
445
+ res.sendStatus(200);
446
+ return;
451
447
  }
452
- }));
453
- api.post("/device/:id/otk", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
454
- const submittedOTKs = req.body;
455
- const userDetails = req.user;
456
- const deviceID = req.params.id;
457
- const [otk] = submittedOTKs;
458
- const device = yield db.retrieveDevice(deviceID);
459
- if (!device) {
448
+ const userDetails = getUser(req);
449
+ const deviceID = getParam(req, "id");
450
+ const otk = submittedOTKs[0];
451
+ const device = await db.retrieveDevice(deviceID);
452
+ if (!device || !otk) {
460
453
  res.sendStatus(404);
461
454
  return;
462
455
  }
463
- const message = tweetnacl_1.default.sign.open(otk.signature, crypto_1.XUtils.decodeHex(device.signKey));
456
+ const message = xSignOpen(otk.signature, XUtils.decodeHex(device.signKey));
464
457
  if (!message) {
465
458
  res.sendStatus(401);
466
459
  return;
467
460
  }
468
- try {
469
- yield db.saveOTK(userDetails.userID, deviceID, submittedOTKs);
470
- res.sendStatus(200);
471
- }
472
- catch (err) {
473
- res.status(500).send(err.toString());
461
+ await db.saveOTK(userDetails.userID, deviceID, submittedOTKs);
462
+ res.sendStatus(200);
463
+ });
464
+ api.get("/emoji/:emojiID/details", protect, async (req, res) => {
465
+ const emoji = await db.retrieveEmoji(getParam(req, "emojiID"));
466
+ res.send(msgpack.encode(emoji));
467
+ });
468
+ api.get("/emoji/:emojiID", protect, async (req, res) => {
469
+ const safeId = safePathParam.safeParse(getParam(req, "emojiID"));
470
+ if (!safeId.success) {
471
+ res.sendStatus(400);
472
+ return;
474
473
  }
475
- }));
476
- api.get("/emoji/:emojiID/details", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
477
- const emoji = yield db.retrieveEmoji(req.params.emojiID);
478
- res.send(msgpack_lite_1.default.encode(emoji));
479
- }));
480
- api.get("/emoji/:emojiID", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
481
- const stream = fs_1.default.createReadStream("./emoji/" + req.params.emojiID);
482
- stream.on("error", (err) => {
483
- // log.error(err.toString());
474
+ const filePath = "./emoji/" + safeId.data;
475
+ const typeDetails = await fileTypeFromFile(filePath).catch(() => null);
476
+ if (!typeDetails) {
484
477
  res.sendStatus(404);
485
- });
486
- const typeDetails = yield file_type_1.default.fromStream(stream);
487
- if (typeDetails) {
488
- res.set("Content-type", typeDetails.mime);
478
+ return;
489
479
  }
480
+ res.set("Content-type", typeDetails.mime);
490
481
  res.set("Cache-control", "public, max-age=31536000");
491
- const stream2 = fs_1.default.createReadStream("./emoji/" + req.params.emojiID);
492
- stream2.on("error", (err) => {
493
- log.error(err.toString());
482
+ const stream = fs.createReadStream(filePath);
483
+ stream.on("error", (_err) => {
484
+ // debugger: emoji stream read error
494
485
  res.sendStatus(500);
495
486
  });
496
- stream2.pipe(res);
497
- }));
498
- api.post("/emoji/:serverID/json", exports.protect, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
499
- const payload = req.body;
500
- const userDetails = req.user;
487
+ stream.pipe(res);
488
+ });
489
+ api.post("/emoji/:serverID/json", protect, async (req, res) => {
490
+ const parsedPayload = emojiPayload.safeParse(req.body);
491
+ if (!parsedPayload.success) {
492
+ res.status(400).json({
493
+ error: "Invalid emoji payload",
494
+ issues: parsedPayload.error.issues,
495
+ });
496
+ return;
497
+ }
498
+ const payload = parsedPayload.data;
499
+ const userDetails = getUser(req);
501
500
  const device = req.device;
502
501
  if (!device) {
503
502
  res.sendStatus(401);
504
503
  return;
505
504
  }
506
- const buf = Buffer.from(crypto_1.XUtils.decodeBase64(payload.file));
507
- const serverEntry = yield db.retrieveServer(req.params.serverID);
508
- const permissionList = yield db.retrievePermissionsByResourceID(req.params.serverID);
509
- let hasPermission = false;
510
- for (const permission of permissionList) {
511
- if (permission.userID === userDetails.userID &&
512
- permission.powerLevel > ClientManager_1.POWER_LEVELS.EMOJI) {
513
- hasPermission = true;
514
- break;
515
- }
505
+ if (!payload.file) {
506
+ res.sendStatus(400);
507
+ return;
516
508
  }
517
- if (!hasPermission) {
509
+ const buf = Buffer.from(XUtils.decodeBase64(payload.file));
510
+ const serverEntry = await db.retrieveServer(getParam(req, "serverID"));
511
+ const permissionList = await db.retrievePermissionsByResourceID(getParam(req, "serverID"));
512
+ if (!userHasPermission(permissionList, userDetails.userID, POWER_LEVELS.EMOJI)) {
518
513
  res.sendStatus(401);
519
514
  return;
520
515
  }
@@ -524,56 +519,60 @@ const initApp = (api, db, log, tokenValidator, signKeys, notify) => {
524
519
  }
525
520
  if (!payload.name) {
526
521
  res.sendStatus(400);
522
+ return;
527
523
  }
528
524
  if (Buffer.byteLength(buf) > 256000) {
529
- console.warn("File to big.");
530
525
  res.sendStatus(413);
526
+ return;
531
527
  }
532
- const mimeType = yield file_type_1.default.fromBuffer(buf);
533
- if (!exports.ALLOWED_IMAGE_TYPES.includes((mimeType === null || mimeType === void 0 ? void 0 : mimeType.mime) || "no/type")) {
528
+ const mimeType = await fileTypeFromBuffer(buf);
529
+ if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
534
530
  res.status(400).send({
535
- error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " + (mimeType === null || mimeType === void 0 ? void 0 : mimeType.ext),
531
+ error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
532
+ String(mimeType?.ext),
536
533
  });
537
534
  return;
538
535
  }
539
536
  const emoji = {
540
- emojiID: uuid.v4(),
541
- owner: req.params.serverID,
537
+ emojiID: crypto.randomUUID(),
542
538
  name: payload.name,
539
+ owner: getParam(req, "serverID"),
543
540
  };
544
- yield db.createEmoji(emoji);
541
+ await db.createEmoji(emoji);
545
542
  try {
546
543
  // write the file to disk
547
- fs_1.default.writeFile("emoji/" + emoji.emojiID, buf, () => {
548
- log.info("Wrote new emoji " + emoji.emojiID);
549
- });
550
- res.send(msgpack_lite_1.default.encode(emoji));
544
+ await fsp.writeFile("emoji/" + emoji.emojiID, buf);
545
+ res.send(msgpack.encode(emoji));
551
546
  }
552
- catch (err) {
553
- log.warn(err);
547
+ catch (_err) {
548
+ // debugger: emoji write failed
554
549
  res.sendStatus(500);
555
550
  }
556
- }));
557
- api.post("/emoji/:serverID", exports.protect, multer_1.default().single("emoji"), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
558
- const payload = req.body;
559
- const serverEntry = yield db.retrieveServer(req.params.serverID);
560
- const userDetails = req.user;
561
- const deviceDetails = req
562
- .device;
551
+ });
552
+ api.post("/emoji/:serverID", protect, multer().single("emoji"), async (req, res) => {
553
+ const parsedPayload = emojiPayload.safeParse(req.body);
554
+ if (!parsedPayload.success) {
555
+ res.status(400).json({
556
+ error: "Invalid emoji payload",
557
+ issues: parsedPayload.error.issues,
558
+ });
559
+ return;
560
+ }
561
+ const payload = parsedPayload.data;
562
+ const serverID = getParam(req, "serverID");
563
+ if (typeof serverID !== "string") {
564
+ res.sendStatus(400);
565
+ return;
566
+ }
567
+ const serverEntry = await db.retrieveServer(serverID);
568
+ const userDetails = getUser(req);
569
+ const deviceDetails = req.device;
563
570
  if (!deviceDetails) {
564
571
  res.sendStatus(401);
565
572
  return;
566
573
  }
567
- const permissionList = yield db.retrievePermissionsByResourceID(req.params.serverID);
568
- let hasPermission = false;
569
- for (const permission of permissionList) {
570
- if (permission.userID === userDetails.userID &&
571
- permission.powerLevel > ClientManager_1.POWER_LEVELS.EMOJI) {
572
- hasPermission = true;
573
- break;
574
- }
575
- }
576
- if (!hasPermission) {
574
+ const permissionList = await db.retrievePermissionsByResourceID(serverID);
575
+ if (!userHasPermission(permissionList, userDetails.userID, POWER_LEVELS.EMOJI)) {
577
576
  res.sendStatus(401);
578
577
  return;
579
578
  }
@@ -583,51 +582,50 @@ const initApp = (api, db, log, tokenValidator, signKeys, notify) => {
583
582
  }
584
583
  if (!payload.name) {
585
584
  res.sendStatus(400);
585
+ return;
586
586
  }
587
587
  if (!req.file) {
588
- console.warn("MISSING FILE");
589
588
  res.sendStatus(400);
590
589
  return;
591
590
  }
592
591
  if (Buffer.byteLength(req.file.buffer) > 256000) {
593
- console.warn("File to big.");
594
592
  res.sendStatus(413);
593
+ return;
595
594
  }
596
- const mimeType = yield file_type_1.default.fromBuffer(req.file.buffer);
597
- if (!exports.ALLOWED_IMAGE_TYPES.includes((mimeType === null || mimeType === void 0 ? void 0 : mimeType.mime) || "no/type")) {
595
+ const mimeType = await fileTypeFromBuffer(req.file.buffer);
596
+ if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
598
597
  res.status(400).send({
599
- error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " + (mimeType === null || mimeType === void 0 ? void 0 : mimeType.ext),
598
+ error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
599
+ String(mimeType?.ext),
600
600
  });
601
601
  return;
602
602
  }
603
603
  const emoji = {
604
- emojiID: uuid.v4(),
605
- owner: req.params.serverID,
604
+ emojiID: crypto.randomUUID(),
606
605
  name: payload.name,
606
+ owner: serverID,
607
607
  };
608
- yield db.createEmoji(emoji);
608
+ await db.createEmoji(emoji);
609
609
  try {
610
610
  // write the file to disk
611
- fs_1.default.writeFile("emoji/" + emoji.emojiID, req.file.buffer, () => {
612
- log.info("Wrote new emoji " + emoji.emojiID);
613
- });
614
- res.send(msgpack_lite_1.default.encode(emoji));
611
+ await fsp.writeFile("emoji/" + emoji.emojiID, req.file.buffer);
612
+ res.send(msgpack.encode(emoji));
615
613
  }
616
- catch (err) {
617
- log.warn(err);
614
+ catch (_err) {
615
+ // debugger: emoji write failed
618
616
  res.sendStatus(500);
619
617
  }
620
- }));
618
+ });
621
619
  // COMPLEX RESOURCES
622
620
  api.use("/user", userRouter);
623
621
  api.use("/file", fileRouter);
624
622
  api.use("/avatar", avatarRouter);
625
623
  api.use("/invite", inviteRouter);
624
+ setupDocs(api);
625
+ // Central error handler MUST be last. Handles both thrown AppErrors
626
+ // (client-safe status + message) and programmer errors (generic 500
627
+ // with full details logged server-side). See src/server/errors.ts
628
+ // for the CWE mapping.
629
+ api.use(errorHandler());
626
630
  };
627
- exports.initApp = initApp;
628
- /**
629
- * @ignore
630
- */
631
- const jestRun = () => {
632
- return process.env.JEST_WORKER_ID !== undefined;
633
- };
631
+ //# sourceMappingURL=index.js.map