@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.
- package/README.md +82 -26
- package/dist/ClientManager.d.ts +23 -25
- package/dist/ClientManager.js +230 -249
- package/dist/ClientManager.js.map +1 -0
- package/dist/Database.d.ts +49 -47
- package/dist/Database.js +698 -773
- package/dist/Database.js.map +1 -0
- package/dist/Spire.d.ts +22 -14
- package/dist/Spire.js +496 -236
- package/dist/Spire.js.map +1 -0
- package/dist/__tests__/Database.spec.js +116 -75
- package/dist/__tests__/Database.spec.js.map +1 -0
- package/dist/db/schema.d.ts +134 -0
- package/dist/db/schema.js +2 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -0
- package/dist/middleware/validate.d.ts +12 -0
- package/dist/middleware/validate.js +35 -0
- package/dist/middleware/validate.js.map +1 -0
- package/dist/migrations/2026-04-06_initial-schema.d.ts +3 -0
- package/dist/migrations/2026-04-06_initial-schema.js +192 -0
- package/dist/migrations/2026-04-06_initial-schema.js.map +1 -0
- package/dist/run.js +26 -21
- package/dist/run.js.map +1 -0
- package/dist/server/avatar.d.ts +3 -4
- package/dist/server/avatar.js +64 -64
- package/dist/server/avatar.js.map +1 -0
- package/dist/server/errors.d.ts +59 -0
- package/dist/server/errors.js +94 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/file.d.ts +3 -4
- package/dist/server/file.js +81 -62
- package/dist/server/file.js.map +1 -0
- package/dist/server/index.d.ts +8 -10
- package/dist/server/index.js +414 -405
- package/dist/server/index.js.map +1 -0
- package/dist/server/invite.d.ts +4 -5
- package/dist/server/invite.js +18 -52
- package/dist/server/invite.js.map +1 -0
- package/dist/server/openapi.d.ts +2 -0
- package/dist/server/openapi.js +40 -0
- package/dist/server/openapi.js.map +1 -0
- package/dist/server/permissions.d.ts +16 -0
- package/dist/server/permissions.js +22 -0
- package/dist/server/permissions.js.map +1 -0
- package/dist/server/rateLimit.d.ts +28 -0
- package/dist/server/rateLimit.js +58 -0
- package/dist/server/rateLimit.js.map +1 -0
- package/dist/server/user.d.ts +4 -7
- package/dist/server/user.js +55 -66
- package/dist/server/user.js.map +1 -0
- package/dist/server/utils.d.ts +35 -7
- package/dist/server/utils.js +50 -6
- package/dist/server/utils.js.map +1 -0
- package/dist/types/express.d.ts +20 -0
- package/dist/types/express.js +2 -0
- package/dist/types/express.js.map +1 -0
- package/dist/utils/createLogger.js +13 -19
- package/dist/utils/createLogger.js.map +1 -0
- package/dist/utils/createUint8UUID.js +6 -10
- package/dist/utils/createUint8UUID.js.map +1 -0
- package/dist/utils/jwtSecret.d.ts +7 -0
- package/dist/utils/jwtSecret.js +15 -0
- package/dist/utils/jwtSecret.js.map +1 -0
- package/dist/utils/loadEnv.js +7 -22
- package/dist/utils/loadEnv.js.map +1 -0
- package/dist/utils/msgpack.d.ts +2 -0
- package/dist/utils/msgpack.js +4 -0
- package/dist/utils/msgpack.js.map +1 -0
- package/package.json +91 -65
- package/src/ClientManager.ts +434 -0
- package/src/Database.ts +925 -0
- package/src/Spire.ts +878 -0
- package/src/__tests__/Database.spec.ts +167 -0
- package/src/ambient-modules.d.ts +1 -0
- package/src/db/schema.ts +165 -0
- package/src/index.ts +3 -0
- package/src/middleware/validate.ts +38 -0
- package/src/migrations/2026-04-06_initial-schema.ts +218 -0
- package/src/run.ts +37 -0
- package/src/server/avatar.ts +141 -0
- package/src/server/errors.ts +133 -0
- package/src/server/file.ts +172 -0
- package/src/server/index.ts +855 -0
- package/src/server/invite.ts +65 -0
- package/src/server/openapi.ts +51 -0
- package/src/server/permissions.ts +40 -0
- package/src/server/rateLimit.ts +86 -0
- package/src/server/user.ts +125 -0
- package/src/server/utils.ts +59 -0
- package/src/types/express.ts +23 -0
- package/src/utils/createLogger.ts +47 -0
- package/src/utils/createUint8UUID.ts +9 -0
- package/src/utils/jwtSecret.ts +16 -0
- package/src/utils/loadEnv.ts +15 -0
- package/src/utils/msgpack.ts +4 -0
- package/avatars/052242d0-4129-4a6e-8076-22709c157549 +0 -0
- package/avatars/0a677c9a-4986-4b2c-ae2e-12faf22f55db +0 -0
- package/avatars/0ba21b91-decb-4a3e-ac86-4dd54d805a9a +0 -0
- package/avatars/0c48d8b6-1d1b-4297-a6c6-2fe50af6fc35 +0 -0
- package/avatars/0d993cdf-19a6-4299-a4ee-a06579d106cf +0 -0
- package/avatars/17b000e4-ac38-46ec-9dec-d2568086129a +0 -0
- package/avatars/19dc5594-0f06-4ac6-af18-8740dd39ef6b +0 -0
- package/avatars/20444fa3-6d5e-429e-b55f-b81c3d2c61ee +0 -0
- package/avatars/21c0512a-5630-4931-9442-d66db66737be +0 -0
- package/avatars/22830a60-0b6f-4912-83a5-72245465f332 +0 -0
- package/avatars/243639ce-f59f-4404-a1f1-4ec0eb5d2af3 +0 -0
- package/avatars/30d2c01d-7b7f-4ea9-9859-1c90837a23f7 +0 -0
- package/avatars/315a04f0-9a6f-4b0f-bb9f-5fa774c4752b +0 -0
- package/avatars/3563d333-53fe-4885-ac2d-9a4f761db85e +0 -0
- package/avatars/36a10c00-3b4c-437f-8e1f-4428ecde0003 +0 -0
- package/avatars/40b83eeb-c6e8-4268-82ab-69799a796405 +0 -0
- package/avatars/45b5ddb9-ad2c-4404-8ab3-cb4699e6d61c +0 -0
- package/avatars/4e4f0ffb-9a75-479a-bccb-446d0bf85020 +0 -0
- package/avatars/4e62c3bd-08c6-4fdd-bd65-f01c7322ed64 +0 -0
- package/avatars/5004d2e7-51af-44af-8776-6c71f7019843 +0 -0
- package/avatars/5041eb29-5c4b-4dea-8c1b-31ba4473161a +0 -0
- package/avatars/5065cf78-31c5-46cb-8d5c-c0b6be2d994e +0 -0
- package/avatars/51b91d2c-8956-4d73-b4ad-ca6a8d9da9a8 +0 -0
- package/avatars/58264a2c-5651-4a42-8ca2-a9907b311e48 +0 -0
- package/avatars/58c2357c-8080-4725-a0ce-182c96b037c4 +0 -0
- package/avatars/59b5f6dd-8e04-4d15-b4dc-c1c652558a74 +0 -0
- package/avatars/5b417a78-b274-48bc-98a4-6e54b74ee62d +0 -0
- package/avatars/611f5a93-1ed4-45a1-bc8e-e8e413f9b171 +0 -0
- package/avatars/65abe919-9921-46f6-9bc9-183e9cc53c8a +0 -0
- package/avatars/6934202d-1546-4270-8a15-97ba8b8c6fa9 +0 -0
- package/avatars/6acd24be-f4b7-4399-9e7c-807821828d29 +0 -0
- package/avatars/6b2d6ac5-e35c-4297-994e-f0eb6fe56740 +0 -0
- package/avatars/6cae9ddd-f163-44e7-a632-30425716a159 +0 -0
- package/avatars/6d90e79e-b9c1-4b89-843b-96636de8d26b +0 -0
- package/avatars/6f82c7bb-a974-4372-8a64-ce287e668c8c +0 -0
- package/avatars/74a45091-5a76-4bb7-ae8a-cd7373adc128 +0 -0
- package/avatars/7d071ab2-e0b5-4dbb-8bf5-1fae50c3663f +0 -0
- package/avatars/7de818c8-bda1-4f51-976e-160fc087184b +0 -0
- package/avatars/873937df-8cb1-427a-92bf-f829f4259624 +0 -0
- package/avatars/8b45ffa5-3322-4109-bfa0-1be088336135 +0 -0
- package/avatars/8efaa426-22a9-42a2-b4ac-a275717b812f +0 -0
- package/avatars/903fd1a6-d6ea-431c-b98f-f21e424a2852 +0 -0
- package/avatars/943d8533-5174-4199-990f-1ec69e5d60c4 +0 -0
- package/avatars/952d014e-3804-4cd2-a4a0-ffe40a11e4ac +0 -0
- package/avatars/95d3acb0-724d-4413-b20f-edad55812d5d +0 -0
- package/avatars/9641a946-f613-471b-bedd-c1730b96b51e +0 -0
- package/avatars/9b01cbbf-f6b2-43fb-b569-589b6f2a8134 +0 -0
- package/avatars/9cd4424d-a34f-4467-acc0-93cf82703e0d +0 -0
- package/avatars/9d9ad3b0-e5a6-420a-a6c5-fb9085b70376 +0 -0
- package/avatars/9e9e34b5-4e63-4c4b-9722-c7f5674b47aa +0 -0
- package/avatars/a387d5c1-59eb-4a6b-80c1-a8982ed12c33 +0 -0
- package/avatars/a3e86d21-d881-4824-8ebf-45e3bf0f9186 +0 -0
- package/avatars/a8d5cc1c-3f42-4b7b-8d33-f9a9ef77f96b +0 -0
- package/avatars/a91d815f-badc-4604-a7be-6c7a44e6101d +0 -0
- package/avatars/aa8d0324-bcec-4737-a8c4-bdbff914148d +0 -0
- package/avatars/abb8a941-8b6b-47d7-a2f9-8b153ba44aa2 +0 -0
- package/avatars/b011bb38-1ef3-4d22-82fa-8bf60faf7b5d +0 -0
- package/avatars/b24bcbc1-11f0-473d-a8b9-ba8ce4ca127d +0 -0
- package/avatars/b2607346-af1c-4e98-b725-7650a766db2a +0 -0
- package/avatars/b6300f7c-cb37-459b-b1bf-8a0a0e797a52 +0 -0
- package/avatars/b7d3cff3-84dd-4547-93a1-de1aaa8aa34c +0 -0
- package/avatars/baa4b51c-e97f-4f51-bcb3-f27bc506cfaf +0 -0
- package/avatars/be7022e4-e292-4515-80d5-f9b61ebeb4ce +0 -0
- package/avatars/bed596a3-7569-4854-9e76-f52d33c0a541 +0 -0
- package/avatars/bf69992f-3f72-4930-99bb-0ffe17f3aebf +0 -0
- package/avatars/ca00c250-c6d4-464d-a6de-1c8467a18fe8 +0 -0
- package/avatars/ca19d78f-c0bc-4bd5-b26f-6923cb19996d +0 -0
- package/avatars/cda4d6e1-e0a4-4024-ac95-6de98e713b98 +0 -0
- package/avatars/cf72c30d-2da8-4e81-aa71-735b9e714274 +0 -0
- package/avatars/d5a35b78-99b3-4564-b6b9-b2ccab28c470 +0 -0
- package/avatars/dadb38c1-2a9d-47a3-8d92-b56b6166973c +0 -0
- package/avatars/e68705c7-375d-4423-9a86-29a16bd3ee0e +0 -0
- package/avatars/e9af3e4c-1f62-4302-8b99-b68ce93b7a86 +0 -0
- package/avatars/ea7e7331-e845-4189-8248-5f5b1d63f5e3 +0 -0
- package/avatars/ef4f8dcb-ef6c-4e7a-9be1-0476161bfce5 +0 -0
- package/avatars/ef7d0917-a206-4f88-8b60-93f8253774dc +0 -0
- package/avatars/f1a554d6-1db3-4ff5-b0dc-b607d6c3b4ff +0 -0
- package/avatars/f1fecc21-c81f-49a8-88f3-f942a0a679f6 +0 -0
- package/avatars/f30a2427-1755-4053-813e-129a179e1dd3 +0 -0
- package/avatars/f5370717-5109-46a5-a8d7-e1dd996d0615 +0 -0
- package/avatars/f6dd7126-1144-4998-bbd3-d4e0fbee2e95 +0 -0
- package/avatars/f83413d5-0003-4756-9ece-745fd61cc468 +0 -0
- package/avatars/f9b3149e-7ec8-4bb3-a9b9-dcbe66dac197 +0 -0
- package/avatars/fa41d70b-857e-4423-bd7d-26ddcddc13b9 +0 -0
- package/avatars/fb551ee8-99c7-400b-8e1d-322ce4619998 +0 -0
- package/avatars/fe83a6d4-abb0-4ab0-b61d-76a7cc08be84 +0 -0
- package/dist/migrations/20210103192527_users.d.ts +0 -3
- package/dist/migrations/20210103192527_users.js +0 -30
- package/dist/migrations/20210103193502_mail.d.ts +0 -3
- package/dist/migrations/20210103193502_mail.js +0 -35
- package/dist/migrations/20210103193525_preKeys.d.ts +0 -3
- package/dist/migrations/20210103193525_preKeys.js +0 -30
- package/dist/migrations/20210103193553_oneTimeKeys.d.ts +0 -3
- package/dist/migrations/20210103193553_oneTimeKeys.js +0 -30
- package/dist/migrations/20210103193615_servers.d.ts +0 -3
- package/dist/migrations/20210103193615_servers.js +0 -28
- package/dist/migrations/20210103193729_channels.d.ts +0 -3
- package/dist/migrations/20210103193729_channels.js +0 -28
- package/dist/migrations/20210103193749_permissions.d.ts +0 -3
- package/dist/migrations/20210103193749_permissions.js +0 -30
- package/dist/migrations/20210103193801_files.d.ts +0 -3
- package/dist/migrations/20210103193801_files.js +0 -28
- package/emoji/04d98632-2c86-421b-a407-17f14fe86f8f +0 -0
- package/emoji/1160ed6e-1163-4043-9808-4029e863ed30 +0 -0
- package/emoji/1547ab18-1635-4a80-a82d-ebbb767b9932 +0 -0
- package/emoji/16922521-f6cb-4de4-860c-27916b22c6ba +0 -0
- package/emoji/198a9432-0e41-4866-994a-448d4775afcb +0 -0
- package/emoji/1be886b3-c9c5-4593-b516-f357ed931f96 +0 -0
- package/emoji/1c2b3d1d-637f-4103-b066-4bc4511a3ad7 +0 -0
- package/emoji/1efd27e7-b15f-475c-8b32-9159d26b169d +0 -0
- package/emoji/270b9409-0ea5-4be2-a239-a8dce13f9c31 +0 -0
- package/emoji/27812f76-fee2-49dd-a217-363de6d159dc +0 -0
- package/emoji/297ec202-8c24-44c6-aead-689d6d461883 +0 -0
- package/emoji/2bf06d86-17cb-4f40-a5ef-bd75d239a1a3 +0 -0
- package/emoji/31a75163-1cce-4dc1-b0a2-ecad6a4c500b +0 -0
- package/emoji/35235635-fdbd-4273-8428-f3cb3e1e8fd3 +0 -0
- package/emoji/3690fff2-6824-4403-a6e3-16a6a54979a9 +0 -0
- package/emoji/391014c2-59e0-46a8-85ec-7a7fdaca1d2d +0 -0
- package/emoji/3b383dcb-6e76-4e85-8e16-7c68040c06c2 +0 -0
- package/emoji/42d617a7-b104-42f5-9618-473181f752cf +0 -0
- package/emoji/482495d3-cce9-4f88-bf2a-f6003f03a9b5 +0 -0
- package/emoji/48390e06-0efb-404c-89bd-5f2be241bd50 +0 -0
- package/emoji/4b808d8d-3248-4149-b919-71b108391bcf +0 -0
- package/emoji/4bc13544-d82a-4e32-bd17-a70592274314 +0 -0
- package/emoji/4fcebf70-8623-4343-8243-67c8547b2edd +0 -0
- package/emoji/509d09aa-1214-459c-8081-50918a17b9af +0 -0
- package/emoji/5272abd8-d4d7-4b90-acd2-bf30e6c27243 +0 -0
- package/emoji/53c272ce-48bd-4d7e-bfb8-a6482b88be54 +0 -0
- package/emoji/5b279e65-06f7-4b26-8b4c-d1b48fba728d +0 -0
- package/emoji/5bd141f9-4394-4108-9376-66ebbc2c2bc1 +0 -0
- package/emoji/5c769156-f9cb-40bd-ab89-4edeece613fd +0 -0
- package/emoji/5c85fba9-8ba7-4fc9-b1b2-48dc30d24a1b +0 -0
- package/emoji/61a5e565-d20b-40ba-a139-b0c73a6027f3 +0 -0
- package/emoji/6913f43d-dd45-456c-9641-a126104d9ae5 +0 -0
- package/emoji/6957e74e-9622-492d-a950-242db3752260 +0 -0
- package/emoji/6a14bab5-26af-4bfa-9c17-be7c2511976d +0 -0
- package/emoji/6be09439-509e-4095-a30a-b1c7c573895d +0 -0
- package/emoji/6cc435b1-fe53-433c-b5a9-2b2019053997 +0 -0
- package/emoji/74f1b2af-bc7e-4a0f-802a-64ded185d5e2 +0 -0
- package/emoji/7890ba09-f02f-428e-807d-006d03d51d4a +0 -0
- package/emoji/7dc69179-6b3c-4f40-b20b-0ff573deea2d +0 -0
- package/emoji/7dd1b6b1-439d-4279-916a-995408863172 +0 -0
- package/emoji/820498ad-a2c8-43a2-ab83-d26f9c2246d4 +0 -0
- package/emoji/8319469c-2787-44e5-91a6-c8c39810dd7c +0 -0
- package/emoji/86745d1d-9e59-4607-b2b0-46c741079be1 +0 -0
- package/emoji/887b3cff-ae9e-4b5f-ad00-3ca9fc72f689 +0 -0
- package/emoji/8c6cf621-71d6-4fca-abe6-e19f4dd7f883 +0 -0
- package/emoji/8ca6d32e-a1ef-4956-a416-d8d0d680f085 +0 -0
- package/emoji/8d979f5e-38a2-4dd1-b3e4-80938bbe499b +0 -0
- package/emoji/99f68ea0-e3fe-4f03-9cc3-5f7f5315404d +0 -0
- package/emoji/9ad3aedc-7f79-4d68-a144-82e5b5dc3033 +0 -0
- package/emoji/9e418c2f-1f0f-46c4-be39-3bda38a28545 +0 -0
- package/emoji/a1f616bf-7402-4e24-9111-18acaebabb48 +0 -0
- package/emoji/a25ed9c1-3f9c-4e5f-ade2-7b159fb9fbf4 +0 -0
- package/emoji/a5176bc2-39a8-467b-8c75-6fbbc81b59c7 +0 -0
- package/emoji/a584215c-6547-438b-8ae8-dd490b51890e +0 -0
- package/emoji/a739895f-cf61-4b7c-b350-8e8283aaf751 +0 -0
- package/emoji/aaa10dd2-02a2-499e-9e17-c83787436508 +0 -0
- package/emoji/ae90baf2-a0ef-4d4d-9cc4-94f8ddd60f45 +0 -0
- package/emoji/b0564c48-feae-431a-95f9-df597c6c124c +0 -0
- package/emoji/b218bb93-e69c-4793-a669-83316650c4e7 +0 -0
- package/emoji/b2998c27-85d5-4598-ab41-469aa8e0fcad +0 -0
- package/emoji/b3da08ba-4179-4b5d-826f-5fc15e1a3ad2 +0 -0
- package/emoji/b840eb6a-a917-4bb2-854b-8f1022e7904b +0 -0
- package/emoji/b84baa76-b4b0-4b83-bc83-78661cb4f1d4 +0 -0
- package/emoji/baf69d80-8b1d-4032-855f-605cf0d489c3 +0 -0
- package/emoji/bb4d372c-ccd0-4a47-b157-b6a3b9f763e2 +0 -0
- package/emoji/bdbd1627-c81d-42d9-b3f5-8979e2ab74dc +0 -0
- package/emoji/c257388f-8b85-450b-b168-ebdf8d8c3026 +0 -0
- package/emoji/c573fde1-faa9-4c1d-a172-e283645afcfd +0 -0
- package/emoji/c8e27810-e8ea-47ce-b7ec-cef0a6becb28 +0 -0
- package/emoji/cdeef182-b220-4850-9ecf-5d7c472fd754 +0 -0
- package/emoji/d59c1aa9-6f81-4c07-96dd-9953401ff211 +0 -0
- package/emoji/dd407dbf-a077-40ba-957f-337b3c5efdc7 +0 -0
- package/emoji/e01f6e06-5728-4e2c-90fb-314a5827b766 +0 -0
- package/emoji/e1f9ed12-a2ce-433d-b454-b833438a1f9c +0 -0
- package/emoji/e697e5c4-acd6-41cd-a43e-edee8da3ab7b +0 -0
- package/emoji/e8182220-3464-4e31-8c08-466baead7bfc +0 -0
- package/emoji/eb0f3fd5-abc9-4abf-b816-d8458aeb7ec8 +0 -0
- package/emoji/eb38ecf7-0d13-4c51-b96a-3777f79321c4 +0 -0
- package/emoji/ee515c85-b7ce-4493-a427-994cc0af0d59 +0 -0
- package/emoji/f485cef2-d3fa-4d59-88af-b79a3105cacf +0 -0
- package/emoji/ff0dab2a-7015-4e8c-b0d0-3569058359dc +0 -0
- package/files/01087968-07b6-4fdb-9aeb-fa9dc061be94 +0 -0
- package/files/030455b3-17cc-415f-b3b3-2bb56c92ee8b +0 -0
- package/files/06129f52-a858-4031-ad85-4d7f2bb793af +0 -0
- package/files/0a6155a9-069f-45e9-8a06-56992fe55187 +0 -0
- package/files/12b9dda5-feb1-4f20-a987-ad422db5ba73 +0 -0
- package/files/13c8fa1e-8821-4628-b607-9e4fa4510df7 +0 -0
- package/files/159b0ad3-1a30-419d-af22-28a1096ce825 +0 -0
- package/files/1699c563-6769-431b-8041-99a6a4386f25 +0 -0
- package/files/176b916c-0dd9-4b93-bfdc-b8ccabf15a96 +0 -0
- package/files/1a27dad9-8cc9-4a0c-9d4e-7bde63adda60 +0 -0
- package/files/1d29832c-059a-4190-bca6-b83ac77540d9 +0 -0
- package/files/1dcde013-8833-4369-8726-81236e4eb30e +0 -0
- package/files/2080fc9f-d2c4-4fbc-af84-232fe4900a4f +0 -0
- package/files/20889623-6869-46a2-999b-c07708c12521 +0 -0
- package/files/2107e243-c378-418d-a183-7df13873c65b +0 -0
- package/files/225ed61e-8f8c-4d17-b675-9a9f9918d5b4 +0 -0
- package/files/29ffba15-5acc-4ef8-b5a4-5ce61d3f6e85 +0 -0
- package/files/2a69434c-1d8a-4e9d-90d4-569aaeaae7e3 +0 -0
- package/files/2aec8fcf-25bf-478e-b2f6-fe67ad753071 +0 -0
- package/files/2c64490e-c7de-4cb2-b22e-70e2ac69d88f +0 -0
- package/files/2d80b4f4-6389-4f5d-8d32-f0ed93820907 +0 -0
- package/files/2f9dd26e-363f-4445-8ee5-28548007a33a +0 -0
- package/files/360dd8f5-76c4-4e86-ba22-9025dc7ca2a4 +0 -0
- package/files/3a0a7f5b-45a5-4340-ba7c-6a0fa2c63871 +0 -0
- package/files/3a8fa6be-8acf-4b7b-b653-25edc6b28cdc +0 -5
- package/files/3d9e191b-2c15-42aa-9928-c2cdbb5e14ca +0 -0
- package/files/3dd0f2ef-0d4e-4837-bffc-22aa645cbe85 +0 -0
- package/files/402c4b9b-adbf-4ab6-a21d-c17369b48abf +0 -0
- package/files/4685d988-33eb-4902-872f-3b824f497c8b +0 -0
- package/files/495f6c55-07f8-4713-a444-a2261a789b94 +0 -0
- package/files/4acba8e3-567b-4062-b81b-340e205a01de +0 -0
- package/files/4c0a10cc-395b-474a-8140-677ed607da89 +0 -0
- package/files/537584d9-25ad-4830-808a-a1e3d63e2a52 +0 -0
- package/files/5519f10d-745e-4474-8ca8-6c111693704c +0 -0
- package/files/5563cf92-a5e3-4be3-867d-9647a02298d4 +0 -0
- package/files/55b70d9b-fd58-4800-832c-d6b4521ba6d0 +0 -0
- package/files/5623cff3-ce9b-403b-9915-50d2bcbc981c +0 -0
- package/files/576314ba-2a1a-4753-8d77-2a9e04f509d1 +0 -0
- package/files/58ed97ae-0eac-4e04-add1-76646effa2d5 +0 -0
- package/files/68638efd-5389-481d-a841-36164c62c078 +0 -0
- package/files/6936d34b-a1f8-4a9d-b005-5544dbdcf5e5 +0 -0
- package/files/6bda9e88-3a28-47f7-994e-900ede6bf984 +0 -0
- package/files/7024a3b9-a863-4618-9dbd-fa6502017ae0 +0 -0
- package/files/707caccb-3780-456d-9056-c20bfdfc0e5b +0 -0
- package/files/74b8c73c-032b-4640-a5b3-d30dd270cbcb +0 -0
- package/files/767cb262-2974-40f8-8182-f7770b431923 +0 -0
- package/files/7a669935-cb48-4349-b26b-7705f8a04fbc +0 -0
- package/files/7bc8f678-6ee7-454c-a1f8-bb9358b89c95 +0 -0
- package/files/7ccc2699-07ec-4177-a9ce-ee7dc952fda1 +0 -0
- package/files/7e0eabf4-b334-4683-8156-ab8d949a0532 +0 -0
- package/files/7f0644ab-02a3-4121-adfc-29a7c55cf804 +0 -0
- package/files/7ff3266d-f103-48ce-90dd-95b9dfe5fcc9 +0 -0
- package/files/836e2e8e-aefd-4b4a-a9b9-bf7436158a8c +0 -0
- package/files/875f7ae5-fa23-4fc5-b04a-8433a7f7089c +0 -0
- package/files/8ca62da9-f204-49e3-b418-9451661b2904 +0 -0
- package/files/9283054c-107f-474d-b61e-f1d0061bcb86 +0 -0
- package/files/93b1ce7f-9566-452b-b4be-30f87d3de150 +0 -0
- package/files/93fb51c7-e19b-4ac1-9dc3-aeb8da0672ed +0 -0
- package/files/9b54a3a1-534a-4ed5-b016-3c74ed4c9edd +0 -0
- package/files/9b5beb6f-712d-4969-b127-fd66c9b2a9c6 +0 -0
- package/files/9e964fbf-d063-498e-b2e3-79f8d6afcf5f +0 -0
- package/files/a66b7a9f-58c2-47a8-a429-a6f0647c6fe9 +0 -0
- package/files/a7cbda7d-81ba-40f7-a997-51146af63e5f +0 -0
- package/files/ac01e83a-e572-41b6-81ab-c992cff7c170 +0 -0
- package/files/ad11a58f-f963-4233-bd29-1658b6b7e600 +0 -0
- package/files/ae60a4a8-08b9-4521-a0a3-d015a8b3ed08 +0 -0
- package/files/b1d3cf27-8d76-4cf9-aa51-d7c6bfd1b3bf +0 -0
- package/files/b2c68863-8554-4ac6-8e99-821f0267cf91 +0 -0
- package/files/b3043e01-a771-44af-bf19-5327646ff929 +0 -0
- package/files/b6d01b89-def5-4c7c-8e97-a3d88617f8f4 +0 -0
- package/files/b8760b32-bb1e-4cd7-a9d6-29c6e0b071bc +0 -0
- package/files/ba5e0470-44f7-47b3-bcb5-eeab3ca8c292 +0 -0
- package/files/c3969b5f-43ae-43f3-9bdd-d3959c79ca01 +0 -0
- package/files/caecd488-dbe6-4a30-a400-bced2ba8dae6 +0 -0
- package/files/d7d865b8-3a05-4ed1-b95d-b93dc1ebb9a9 +0 -0
- package/files/dbca5f31-cf38-4c1e-83b0-5ec8473196fc +0 -0
- package/files/dbf07e82-fff6-4985-bebe-d62c0458bfd0 +0 -0
- package/files/dd759c20-eead-4e57-9e74-4d3a2b978e91 +0 -5
- package/files/de0b2cf1-981b-4e4a-a04e-ac185d1620cd +0 -0
- package/files/de6837fe-5aa2-4ff9-a067-2646a008c780 +0 -0
- package/files/e2fde852-91cb-4e01-88a2-ee086b5f227c +0 -0
- package/files/e391d1ce-8d39-460c-a462-791730131f7f +0 -0
- package/files/e6d9b60f-2b1b-4c6f-ba6d-02f6de90d40f +0 -0
- package/files/f693c62d-2ac8-49fd-aa38-30904e013e3c +0 -0
- package/files/f749fdf5-193e-41f7-b643-5696f67c6402 +0 -0
- package/files/f8910384-e75c-4d65-825f-52a6748f6475 +0 -0
- package/files/fad8826b-e952-4acb-a509-3e6543b94d61 +0 -0
- package/jest.config.js +0 -13
- package/spire.sqlite +0 -0
package/dist/server/index.js
CHANGED
|
@@ -1,520 +1,519 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
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 =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
77
|
-
|
|
91
|
+
catch {
|
|
92
|
+
// Token verification failed — continue without auth
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
next();
|
|
81
96
|
};
|
|
82
|
-
const checkDevice = (req,
|
|
83
|
-
|
|
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 =
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
90
|
-
|
|
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
|
-
|
|
116
|
+
return;
|
|
99
117
|
}
|
|
100
118
|
next();
|
|
101
119
|
};
|
|
102
|
-
|
|
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
|
|
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
|
|
126
|
+
catch {
|
|
109
127
|
res.sendStatus(400);
|
|
110
128
|
return;
|
|
111
129
|
}
|
|
112
130
|
}
|
|
113
131
|
next();
|
|
114
132
|
};
|
|
115
|
-
|
|
133
|
+
const isProduction = process.env["NODE_ENV"] === "production";
|
|
116
134
|
const directories = ["files", "avatars"];
|
|
117
135
|
for (const dir of directories) {
|
|
118
|
-
if (!
|
|
119
|
-
|
|
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 =
|
|
125
|
-
const fileRouter =
|
|
126
|
-
const avatarRouter =
|
|
127
|
-
const inviteRouter =
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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(
|
|
163
|
+
api.use(morgan("dev", { stream: process.stdout }));
|
|
141
164
|
}
|
|
142
|
-
api.use(
|
|
143
|
-
api.get("/server/:id",
|
|
144
|
-
const server =
|
|
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(
|
|
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",
|
|
153
|
-
const userDetails = req
|
|
154
|
-
const serverName =
|
|
155
|
-
const server =
|
|
156
|
-
res.send(
|
|
157
|
-
})
|
|
158
|
-
api.post("/server/:serverID/invites",
|
|
159
|
-
const userDetails = req
|
|
160
|
-
const
|
|
161
|
-
|
|
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 =
|
|
167
|
-
|
|
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 =
|
|
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 =
|
|
186
|
-
res.send(
|
|
187
|
-
})
|
|
188
|
-
api.get("/server/:serverID/invites",
|
|
189
|
-
const userDetails = req
|
|
190
|
-
const permissions =
|
|
191
|
-
|
|
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 =
|
|
203
|
-
res.send(
|
|
204
|
-
})
|
|
205
|
-
api.delete("/server/:id",
|
|
206
|
-
const userDetails = req
|
|
207
|
-
const serverID = req
|
|
208
|
-
const permissions =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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",
|
|
221
|
-
const userDetails = req
|
|
222
|
-
const serverID = req
|
|
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
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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/:
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
const
|
|
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 =
|
|
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
|
-
|
|
299
|
-
// msg.data is the channelID
|
|
300
|
-
yield db.deleteChannel(channelID);
|
|
297
|
+
await db.deleteChannel(channelID);
|
|
301
298
|
res.sendStatus(200);
|
|
302
|
-
const affectedUsers =
|
|
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",
|
|
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",
|
|
313
|
-
const channel =
|
|
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(
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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(
|
|
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",
|
|
377
|
+
});
|
|
378
|
+
api.post("/device/:id/keyBundle", protect, async (req, res) => {
|
|
388
379
|
try {
|
|
389
|
-
const keyBundle =
|
|
380
|
+
const keyBundle = await db.getKeyBundle(getParam(req, "id"));
|
|
390
381
|
if (keyBundle) {
|
|
391
|
-
res.send(
|
|
382
|
+
res.send(msgpack.encode(keyBundle));
|
|
392
383
|
}
|
|
393
384
|
else {
|
|
394
385
|
res.sendStatus(404);
|
|
395
386
|
}
|
|
396
387
|
}
|
|
397
|
-
catch
|
|
388
|
+
catch {
|
|
398
389
|
res.sendStatus(500);
|
|
399
390
|
}
|
|
400
|
-
})
|
|
401
|
-
api.post("/device/:id/mail",
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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 =
|
|
416
|
+
const regKey = xSignOpen(signed, XUtils.decodeHex(device.signKey));
|
|
424
417
|
if (regKey &&
|
|
425
|
-
tokenValidator(
|
|
426
|
-
const token =
|
|
427
|
-
expiresIn:
|
|
418
|
+
tokenValidator(uuidStringify(regKey), TokenScopes.Connect)) {
|
|
419
|
+
const token = jwt.sign({ device }, getJwtSecret(), {
|
|
420
|
+
expiresIn: JWT_EXPIRY,
|
|
428
421
|
});
|
|
429
|
-
|
|
430
|
-
res.
|
|
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",
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
450
|
-
|
|
447
|
+
const submittedOTKs = parsedOTKs.data;
|
|
448
|
+
if (submittedOTKs.length === 0) {
|
|
449
|
+
res.sendStatus(200);
|
|
450
|
+
return;
|
|
451
451
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
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 =
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
477
|
-
|
|
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
|
|
492
|
-
|
|
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
|
-
|
|
497
|
-
})
|
|
498
|
-
api.post("/emoji/:serverID/json",
|
|
499
|
-
const
|
|
500
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
528
|
+
log.warn("File too big.");
|
|
530
529
|
res.sendStatus(413);
|
|
531
530
|
}
|
|
532
|
-
const mimeType =
|
|
533
|
-
if (!
|
|
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 " +
|
|
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:
|
|
541
|
-
owner: req.params.serverID,
|
|
540
|
+
emojiID: crypto.randomUUID(),
|
|
542
541
|
name: payload.name,
|
|
542
|
+
owner: getParam(req, "serverID"),
|
|
543
543
|
};
|
|
544
|
-
|
|
544
|
+
await db.createEmoji(emoji);
|
|
545
545
|
try {
|
|
546
546
|
// write the file to disk
|
|
547
|
-
|
|
548
|
-
|
|
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",
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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 =
|
|
568
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
+
log.warn("File too big.");
|
|
594
597
|
res.sendStatus(413);
|
|
595
598
|
}
|
|
596
|
-
const mimeType =
|
|
597
|
-
if (!
|
|
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 " +
|
|
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:
|
|
605
|
-
owner: req.params.serverID,
|
|
608
|
+
emojiID: crypto.randomUUID(),
|
|
606
609
|
name: payload.name,
|
|
610
|
+
owner: serverID,
|
|
607
611
|
};
|
|
608
|
-
|
|
612
|
+
await db.createEmoji(emoji);
|
|
609
613
|
try {
|
|
610
614
|
// write the file to disk
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
640
|
+
return process.env["JEST_WORKER_ID"] !== undefined;
|
|
633
641
|
};
|
|
642
|
+
//# sourceMappingURL=index.js.map
|