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