@vex-chat/spire 0.8.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -28
- package/dist/ClientManager.d.ts +22 -26
- package/dist/ClientManager.js +198 -259
- package/dist/ClientManager.js.map +1 -0
- package/dist/Database.d.ts +71 -49
- package/dist/Database.js +737 -774
- package/dist/Database.js.map +1 -0
- package/dist/Spire.d.ts +20 -15
- package/dist/Spire.js +471 -249
- package/dist/Spire.js.map +1 -0
- package/dist/__tests__/Database.spec.js +102 -75
- package/dist/__tests__/Database.spec.js.map +1 -0
- package/dist/db/schema.d.ts +135 -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/migrations/2026-04-14_argon2id-password-hashing.d.ts +3 -0
- package/dist/migrations/2026-04-14_argon2id-password-hashing.js +12 -0
- package/dist/migrations/2026-04-14_argon2id-password-hashing.js.map +1 -0
- package/dist/run.js +25 -21
- package/dist/run.js.map +1 -0
- package/dist/server/avatar.d.ts +1 -4
- package/dist/server/avatar.js +63 -67
- package/dist/server/avatar.js.map +1 -0
- package/dist/server/errors.d.ts +56 -0
- package/dist/server/errors.js +68 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/file.d.ts +2 -4
- package/dist/server/file.js +81 -64
- package/dist/server/file.js.map +1 -0
- package/dist/server/index.d.ts +7 -10
- package/dist/server/index.js +414 -416
- package/dist/server/index.js.map +1 -0
- package/dist/server/invite.d.ts +3 -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 +3 -7
- package/dist/server/user.js +56 -69
- 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/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 +14 -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 +93 -65
- package/src/ClientManager.ts +382 -0
- package/src/Database.ts +987 -0
- package/src/Spire.ts +807 -0
- package/src/__tests__/Database.spec.ts +150 -0
- package/src/ambient-modules.d.ts +1 -0
- package/src/db/schema.ts +166 -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/migrations/2026-04-14_argon2id-password-hashing.ts +18 -0
- package/src/run.ts +36 -0
- package/src/server/avatar.ts +134 -0
- package/src/server/errors.ts +104 -0
- package/src/server/file.ts +167 -0
- package/src/server/index.ts +841 -0
- package/src/server/invite.ts +63 -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 +121 -0
- package/src/server/utils.ts +59 -0
- package/src/types/express.ts +23 -0
- package/src/utils/createUint8UUID.ts +9 -0
- package/src/utils/jwtSecret.ts +15 -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/dist/utils/createLogger.d.ts +0 -5
- package/dist/utils/createLogger.js +0 -41
- 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,515 @@
|
|
|
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 multer from "multer";
|
|
12
|
+
import parseDuration from "parse-duration";
|
|
13
|
+
import { stringify as uuidStringify } from "uuid";
|
|
14
|
+
import { z } from "zod/v4";
|
|
15
|
+
import { POWER_LEVELS } from "../ClientManager.js";
|
|
16
|
+
import { JWT_EXPIRY } from "../Spire.js";
|
|
17
|
+
import { getJwtSecret } from "../utils/jwtSecret.js";
|
|
18
|
+
import { msgpack } from "../utils/msgpack.js";
|
|
19
|
+
import { getAvatarRouter } from "./avatar.js";
|
|
20
|
+
import { errorHandler } from "./errors.js";
|
|
21
|
+
import { getFileRouter } from "./file.js";
|
|
22
|
+
import { getInviteRouter } from "./invite.js";
|
|
23
|
+
import { setupDocs } from "./openapi.js";
|
|
24
|
+
import { hasAnyPermission, hasPermission, userHasPermission, } from "./permissions.js";
|
|
25
|
+
import { globalLimiter } from "./rateLimit.js";
|
|
26
|
+
import { getUserRouter } from "./user.js";
|
|
27
|
+
import { censorUser, getParam, getUser } from "./utils.js";
|
|
58
28
|
// expiry of regkeys
|
|
59
|
-
|
|
60
|
-
|
|
29
|
+
export const EXPIRY_TIME = 1000 * 60 * 5;
|
|
30
|
+
export const ALLOWED_IMAGE_TYPES = [
|
|
61
31
|
"image/jpeg",
|
|
62
32
|
"image/png",
|
|
63
33
|
"image/gif",
|
|
64
34
|
"image/apng",
|
|
65
35
|
"image/avif",
|
|
66
36
|
];
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
37
|
+
// ── Zod schemas for trust-boundary validation ──────────────────────────
|
|
38
|
+
const invitePayload = z.object({
|
|
39
|
+
duration: z.string().min(1),
|
|
40
|
+
serverID: z.string().min(1),
|
|
41
|
+
});
|
|
42
|
+
const channelPayload = z.object({
|
|
43
|
+
name: z.string().min(1).max(255),
|
|
44
|
+
});
|
|
45
|
+
const deviceListPayload = z.array(z.string());
|
|
46
|
+
const connectPayload = z.object({
|
|
47
|
+
signed: z.custom((val) => val instanceof Uint8Array),
|
|
48
|
+
});
|
|
49
|
+
const safePathParam = z.string().regex(/^[a-zA-Z0-9._-]+$/);
|
|
50
|
+
const emojiPayload = z.object({
|
|
51
|
+
file: z.string().optional(),
|
|
52
|
+
name: z.string().min(1),
|
|
53
|
+
signed: z.string().optional(),
|
|
54
|
+
});
|
|
55
|
+
const jwtUserPayload = z.object({
|
|
56
|
+
exp: z.number().optional(),
|
|
57
|
+
user: UserSchema,
|
|
58
|
+
});
|
|
59
|
+
const jwtDevicePayload = z.object({
|
|
60
|
+
device: z.object({
|
|
61
|
+
deleted: z.boolean(),
|
|
62
|
+
deviceID: z.string(),
|
|
63
|
+
lastLogin: z.string(),
|
|
64
|
+
name: z.string(),
|
|
65
|
+
owner: z.string(),
|
|
66
|
+
signKey: z.string(),
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
/** Extract Bearer token from Authorization header. */
|
|
70
|
+
function extractBearer(req) {
|
|
71
|
+
const header = req.headers.authorization;
|
|
72
|
+
if (!header || !header.startsWith("Bearer "))
|
|
73
|
+
return null;
|
|
74
|
+
return header.slice(7);
|
|
75
|
+
}
|
|
76
|
+
const checkAuth = (req, _res, next) => {
|
|
77
|
+
const token = extractBearer(req);
|
|
78
|
+
if (token) {
|
|
70
79
|
try {
|
|
71
|
-
const result =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const result = jwt.verify(token, getJwtSecret());
|
|
81
|
+
const parsed = jwtUserPayload.safeParse(result);
|
|
82
|
+
if (parsed.success) {
|
|
83
|
+
req.user = parsed.data.user;
|
|
84
|
+
if (parsed.data.exp !== undefined) {
|
|
85
|
+
req.exp = parsed.data.exp;
|
|
86
|
+
}
|
|
87
|
+
req.bearerToken = token;
|
|
88
|
+
}
|
|
75
89
|
}
|
|
76
|
-
catch
|
|
77
|
-
|
|
90
|
+
catch {
|
|
91
|
+
// Token verification failed — continue without auth
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
next();
|
|
81
95
|
};
|
|
82
|
-
const checkDevice = (req,
|
|
83
|
-
|
|
96
|
+
const checkDevice = (req, _res, next) => {
|
|
97
|
+
const token = req.headers["x-device-token"];
|
|
98
|
+
if (typeof token === "string" && token) {
|
|
84
99
|
try {
|
|
85
|
-
const result =
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
const result = jwt.verify(token, getJwtSecret());
|
|
101
|
+
const parsed = jwtDevicePayload.safeParse(result);
|
|
102
|
+
if (parsed.success) {
|
|
103
|
+
req.device = parsed.data.device;
|
|
104
|
+
}
|
|
88
105
|
}
|
|
89
|
-
catch
|
|
90
|
-
|
|
106
|
+
catch {
|
|
107
|
+
// Device token verification failed — continue without device
|
|
91
108
|
}
|
|
92
109
|
}
|
|
93
110
|
next();
|
|
94
111
|
};
|
|
95
|
-
const protect = (req, res, next) => {
|
|
112
|
+
export const protect = (req, res, next) => {
|
|
96
113
|
if (!req.user) {
|
|
97
114
|
res.sendStatus(401);
|
|
98
|
-
|
|
115
|
+
return;
|
|
99
116
|
}
|
|
100
117
|
next();
|
|
101
118
|
};
|
|
102
|
-
|
|
103
|
-
const msgpackParser = (req, res, next) => {
|
|
119
|
+
export const msgpackParser = (req, res, next) => {
|
|
104
120
|
if (req.is("application/msgpack")) {
|
|
105
121
|
try {
|
|
106
|
-
req.body
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument -- Express req.body is any; decoded body is validated by route-level Zod schemas
|
|
123
|
+
req.body = msgpack.decode(req.body);
|
|
107
124
|
}
|
|
108
|
-
catch
|
|
125
|
+
catch {
|
|
109
126
|
res.sendStatus(400);
|
|
110
127
|
return;
|
|
111
128
|
}
|
|
112
129
|
}
|
|
113
130
|
next();
|
|
114
131
|
};
|
|
115
|
-
exports.msgpackParser = msgpackParser;
|
|
116
132
|
const directories = ["files", "avatars"];
|
|
117
133
|
for (const dir of directories) {
|
|
118
|
-
if (!
|
|
119
|
-
|
|
134
|
+
if (!fs.existsSync(dir)) {
|
|
135
|
+
fs.mkdirSync(dir);
|
|
120
136
|
}
|
|
121
137
|
}
|
|
122
|
-
const initApp = (api, db,
|
|
138
|
+
export const initApp = (api, db, tokenValidator, signKeys, notify) => {
|
|
123
139
|
// INIT ROUTERS
|
|
124
|
-
const userRouter =
|
|
125
|
-
const fileRouter =
|
|
126
|
-
const avatarRouter =
|
|
127
|
-
const inviteRouter =
|
|
140
|
+
const userRouter = getUserRouter(db, tokenValidator);
|
|
141
|
+
const fileRouter = getFileRouter(db);
|
|
142
|
+
const avatarRouter = getAvatarRouter();
|
|
143
|
+
const inviteRouter = getInviteRouter(db, tokenValidator, notify);
|
|
128
144
|
// MIDDLEWARE
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
// Global per-IP rate limit is the FIRST middleware so a flooded
|
|
146
|
+
// source hits the limiter before Express spends any cycles on
|
|
147
|
+
// body parsing, helmet, or auth. See src/server/rateLimit.ts.
|
|
148
|
+
api.use(globalLimiter);
|
|
149
|
+
api.use(express.json({ limit: "20mb" }));
|
|
150
|
+
api.use(express.raw({
|
|
132
151
|
limit: "20mb",
|
|
152
|
+
type: "application/msgpack",
|
|
133
153
|
}));
|
|
134
|
-
api.use(
|
|
135
|
-
api.use(
|
|
136
|
-
api.use(exports.msgpackParser);
|
|
154
|
+
api.use(helmet());
|
|
155
|
+
api.use(msgpackParser);
|
|
137
156
|
api.use(checkAuth);
|
|
138
157
|
api.use(checkDevice);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
const allowedOrigins = process.env["CORS_ORIGINS"]?.split(",") ?? [];
|
|
159
|
+
api.use(cors({
|
|
160
|
+
credentials: true,
|
|
161
|
+
origin: allowedOrigins.length > 0 ? allowedOrigins : false,
|
|
162
|
+
}));
|
|
163
|
+
api.get("/server/:id", protect, async (req, res) => {
|
|
164
|
+
const server = await db.retrieveServer(getParam(req, "id"));
|
|
145
165
|
if (server) {
|
|
146
|
-
return res.send(
|
|
166
|
+
return res.send(msgpack.encode(server));
|
|
147
167
|
}
|
|
148
168
|
else {
|
|
149
|
-
res.sendStatus(404);
|
|
169
|
+
return res.sendStatus(404);
|
|
150
170
|
}
|
|
151
|
-
})
|
|
152
|
-
api.post("/server/:name",
|
|
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
|
-
|
|
171
|
+
});
|
|
172
|
+
api.post("/server/:name", protect, async (req, res) => {
|
|
173
|
+
const userDetails = getUser(req);
|
|
174
|
+
const serverName = atob(getParam(req, "name"));
|
|
175
|
+
const server = await db.createServer(serverName, userDetails.userID);
|
|
176
|
+
res.send(msgpack.encode(server));
|
|
177
|
+
});
|
|
178
|
+
api.post("/server/:serverID/invites", protect, async (req, res) => {
|
|
179
|
+
const userDetails = getUser(req);
|
|
180
|
+
const parsedPayload = invitePayload.safeParse(req.body);
|
|
181
|
+
if (!parsedPayload.success) {
|
|
182
|
+
res.status(400).json({
|
|
183
|
+
error: "Invalid invite payload",
|
|
184
|
+
issues: parsedPayload.error.issues,
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const payload = parsedPayload.data;
|
|
189
|
+
const serverEntry = await db.retrieveServer(getParam(req, "serverID"));
|
|
162
190
|
if (!serverEntry) {
|
|
163
191
|
res.sendStatus(404);
|
|
164
192
|
return;
|
|
165
193
|
}
|
|
166
|
-
const permissions =
|
|
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) {
|
|
175
|
-
log.warn("No permission!");
|
|
194
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
195
|
+
if (!hasPermission(permissions, getParam(req, "serverID"), POWER_LEVELS.INVITE)) {
|
|
176
196
|
res.sendStatus(401);
|
|
177
197
|
return;
|
|
178
198
|
}
|
|
179
|
-
const duration =
|
|
199
|
+
const duration = parseDuration(payload.duration, "ms");
|
|
180
200
|
if (!duration) {
|
|
181
201
|
res.sendStatus(400);
|
|
182
202
|
return;
|
|
183
203
|
}
|
|
184
204
|
const expires = new Date(Date.now() + duration);
|
|
185
|
-
const invite =
|
|
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) {
|
|
205
|
+
const invite = await db.createInvite(crypto.randomUUID(), serverEntry.serverID, userDetails.userID, expires.toString());
|
|
206
|
+
res.send(msgpack.encode(invite));
|
|
207
|
+
});
|
|
208
|
+
api.get("/server/:serverID/invites", protect, async (req, res) => {
|
|
209
|
+
const userDetails = getUser(req);
|
|
210
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
211
|
+
if (!hasPermission(permissions, getParam(req, "serverID"), POWER_LEVELS.INVITE)) {
|
|
199
212
|
res.sendStatus(401);
|
|
200
213
|
return;
|
|
201
214
|
}
|
|
202
|
-
const inviteList =
|
|
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
|
-
}
|
|
215
|
+
const inviteList = await db.retrieveServerInvites(getParam(req, "serverID"));
|
|
216
|
+
res.send(msgpack.encode(inviteList));
|
|
217
|
+
});
|
|
218
|
+
api.delete("/server/:id", protect, async (req, res) => {
|
|
219
|
+
const userDetails = getUser(req);
|
|
220
|
+
const serverID = getParam(req, "id");
|
|
221
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
222
|
+
if (hasPermission(permissions, serverID, POWER_LEVELS.DELETE)) {
|
|
223
|
+
await db.deleteServer(serverID);
|
|
224
|
+
res.sendStatus(200);
|
|
225
|
+
return;
|
|
217
226
|
}
|
|
218
227
|
res.sendStatus(401);
|
|
219
|
-
})
|
|
220
|
-
api.post("/server/:id/channels",
|
|
221
|
-
const userDetails = req
|
|
222
|
-
const serverID = req
|
|
228
|
+
});
|
|
229
|
+
api.post("/server/:id/channels", protect, async (req, res) => {
|
|
230
|
+
const userDetails = getUser(req);
|
|
231
|
+
const serverID = getParam(req, "id");
|
|
223
232
|
// resourceID is serverID
|
|
224
|
-
const
|
|
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
|
-
}
|
|
233
|
+
const parsedBody = channelPayload.safeParse(req.body);
|
|
234
|
+
if (!parsedBody.success) {
|
|
235
|
+
res.status(400).json({
|
|
236
|
+
error: "Invalid channel payload",
|
|
237
|
+
issues: parsedBody.error.issues,
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
238
240
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
res.send(msgpack_lite_1.default.encode(channels));
|
|
249
|
-
return;
|
|
241
|
+
const { name } = parsedBody.data;
|
|
242
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
243
|
+
if (hasPermission(permissions, serverID, POWER_LEVELS.CREATE)) {
|
|
244
|
+
const channel = await db.createChannel(name, serverID);
|
|
245
|
+
res.send(msgpack.encode(channel));
|
|
246
|
+
const affectedUsers = await db.retrieveAffectedUsers(serverID);
|
|
247
|
+
// tell everyone about server change
|
|
248
|
+
for (const user of affectedUsers) {
|
|
249
|
+
notify(user.userID, "serverChange", crypto.randomUUID(), serverID);
|
|
250
250
|
}
|
|
251
|
+
return;
|
|
251
252
|
}
|
|
252
253
|
res.sendStatus(401);
|
|
253
|
-
})
|
|
254
|
-
api.get("/server/:
|
|
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
|
-
}
|
|
254
|
+
});
|
|
255
|
+
api.get("/server/:id/channels", protect, async (req, res) => {
|
|
256
|
+
const serverID = getParam(req, "id");
|
|
257
|
+
const userDetails = getUser(req);
|
|
258
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
259
|
+
if (hasAnyPermission(permissions, serverID)) {
|
|
260
|
+
const channels = await db.retrieveChannels(serverID);
|
|
261
|
+
res.send(msgpack.encode(channels));
|
|
262
|
+
return;
|
|
280
263
|
}
|
|
281
|
-
|
|
282
|
-
|
|
264
|
+
res.sendStatus(401);
|
|
265
|
+
});
|
|
266
|
+
api.get("/server/:serverID/emoji", protect, async (req, res) => {
|
|
267
|
+
const rows = await db.retrieveEmojiList(getParam(req, "serverID"));
|
|
268
|
+
res.send(msgpack.encode(rows));
|
|
269
|
+
});
|
|
270
|
+
api.get("/server/:serverID/permissions", protect, async (req, res) => {
|
|
271
|
+
const userDetails = getUser(req);
|
|
272
|
+
const serverID = getParam(req, "serverID");
|
|
273
|
+
const permissions = await db.retrievePermissionsByResourceID(serverID);
|
|
274
|
+
const canSee = permissions.some((perm) => perm.userID === userDetails.userID);
|
|
275
|
+
if (!canSee) {
|
|
276
|
+
res.sendStatus(401);
|
|
277
|
+
return;
|
|
283
278
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
const
|
|
279
|
+
res.send(msgpack.encode(permissions));
|
|
280
|
+
});
|
|
281
|
+
api.delete("/channel/:id", protect, async (req, res) => {
|
|
282
|
+
const channelID = getParam(req, "id");
|
|
283
|
+
const userDetails = getUser(req);
|
|
284
|
+
const channel = await db.retrieveChannel(channelID);
|
|
289
285
|
if (!channel) {
|
|
290
286
|
res.sendStatus(401);
|
|
291
287
|
return;
|
|
292
288
|
}
|
|
293
|
-
const permissions =
|
|
294
|
-
let found = false;
|
|
289
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
295
290
|
for (const permission of permissions) {
|
|
296
291
|
if (permission.resourceID === channel.serverID &&
|
|
297
292
|
permission.powerLevel > 50) {
|
|
298
|
-
|
|
299
|
-
// msg.data is the channelID
|
|
300
|
-
yield db.deleteChannel(channelID);
|
|
293
|
+
await db.deleteChannel(channelID);
|
|
301
294
|
res.sendStatus(200);
|
|
302
|
-
const affectedUsers =
|
|
295
|
+
const affectedUsers = await db.retrieveAffectedUsers(channel.serverID);
|
|
303
296
|
// tell everyone about server change
|
|
304
297
|
for (const user of affectedUsers) {
|
|
305
|
-
notify(user.userID, "serverChange",
|
|
298
|
+
notify(user.userID, "serverChange", crypto.randomUUID(), channel.serverID);
|
|
306
299
|
}
|
|
307
300
|
return;
|
|
308
301
|
}
|
|
309
302
|
}
|
|
310
303
|
res.sendStatus(401);
|
|
311
|
-
})
|
|
312
|
-
api.get("/channel/:id",
|
|
313
|
-
const channel =
|
|
304
|
+
});
|
|
305
|
+
api.get("/channel/:id", protect, async (req, res) => {
|
|
306
|
+
const channel = await db.retrieveChannel(getParam(req, "id"));
|
|
314
307
|
if (channel) {
|
|
315
|
-
return res.send(
|
|
308
|
+
return res.send(msgpack.encode(channel));
|
|
316
309
|
}
|
|
317
310
|
else {
|
|
311
|
+
return res.sendStatus(404);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
api.delete("/permission/:permissionID", protect, async (req, res) => {
|
|
315
|
+
const permissionID = getParam(req, "permissionID");
|
|
316
|
+
const userDetails = getUser(req);
|
|
317
|
+
const permToDelete = await db.retrievePermission(permissionID);
|
|
318
|
+
if (!permToDelete) {
|
|
318
319
|
res.sendStatus(404);
|
|
320
|
+
return;
|
|
319
321
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
res.sendStatus(404);
|
|
322
|
+
const permissions = await db.retrievePermissions(userDetails.userID, permToDelete.resourceType);
|
|
323
|
+
for (const perm of permissions) {
|
|
324
|
+
if (perm.resourceID === permToDelete.resourceID &&
|
|
325
|
+
(perm.userID === userDetails.userID ||
|
|
326
|
+
(perm.powerLevel > POWER_LEVELS.DELETE &&
|
|
327
|
+
perm.powerLevel > permToDelete.powerLevel))) {
|
|
328
|
+
await db.deletePermission(permToDelete.permissionID);
|
|
329
|
+
res.sendStatus(200);
|
|
329
330
|
return;
|
|
330
331
|
}
|
|
331
|
-
const permissions = yield db.retrievePermissions(userDetails.userID, permToDelete.resourceType);
|
|
332
|
-
for (const perm of permissions) {
|
|
333
|
-
// msg.data is resourceID
|
|
334
|
-
if (perm.resourceID === permToDelete.resourceID &&
|
|
335
|
-
(perm.userID === userDetails.userID ||
|
|
336
|
-
(perm.powerLevel > ClientManager_1.POWER_LEVELS.DELETE &&
|
|
337
|
-
perm.powerLevel > permToDelete.powerLevel))) {
|
|
338
|
-
db.deletePermission(permToDelete.permissionID);
|
|
339
|
-
res.sendStatus(200);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
res.sendStatus(401);
|
|
344
|
-
return;
|
|
345
332
|
}
|
|
346
|
-
|
|
347
|
-
|
|
333
|
+
res.sendStatus(401);
|
|
334
|
+
});
|
|
335
|
+
api.post("/userList/:channelID", protect, async (req, res) => {
|
|
336
|
+
const userDetails = getUser(req);
|
|
337
|
+
const channelID = getParam(req, "channelID");
|
|
338
|
+
const channel = await db.retrieveChannel(channelID);
|
|
339
|
+
if (!channel) {
|
|
340
|
+
res.sendStatus(404);
|
|
341
|
+
return;
|
|
348
342
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const channel = yield db.retrieveChannel(channelID);
|
|
355
|
-
if (!channel) {
|
|
356
|
-
res.sendStatus(404);
|
|
343
|
+
const permissions = await db.retrievePermissions(userDetails.userID, "server");
|
|
344
|
+
for (const permission of permissions) {
|
|
345
|
+
if (permission.resourceID === channel.serverID) {
|
|
346
|
+
const groupMembers = await db.retrieveGroupMembers(channelID);
|
|
347
|
+
res.send(msgpack.encode(groupMembers.map((user) => censorUser(user))));
|
|
357
348
|
return;
|
|
358
349
|
}
|
|
359
|
-
const permissions = yield db.retrievePermissions(userDetails.userID, "server");
|
|
360
|
-
for (const permission of permissions) {
|
|
361
|
-
if (permission.resourceID === channel.serverID) {
|
|
362
|
-
// we've got the permission, it's ok to give them the userlist
|
|
363
|
-
const groupMembers = yield db.retrieveGroupMembers(channelID);
|
|
364
|
-
res.send(msgpack_lite_1.default.encode(groupMembers.map((user) => utils_1.censorUser(user))));
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
350
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
351
|
+
res.sendStatus(401);
|
|
352
|
+
});
|
|
353
|
+
api.post("/deviceList", protect, async (req, res) => {
|
|
354
|
+
const parsed = deviceListPayload.safeParse(req.body);
|
|
355
|
+
if (!parsed.success) {
|
|
356
|
+
res.status(400).json({
|
|
357
|
+
error: "Expected array of user ID strings",
|
|
358
|
+
issues: parsed.error.issues,
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
371
361
|
}
|
|
372
|
-
|
|
373
|
-
|
|
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);
|
|
362
|
+
const devices = await db.retrieveUserDeviceList(parsed.data);
|
|
363
|
+
res.send(msgpack.encode(devices));
|
|
364
|
+
});
|
|
365
|
+
api.get("/device/:id", protect, async (req, res) => {
|
|
366
|
+
const device = await db.retrieveDevice(getParam(req, "id"));
|
|
380
367
|
if (device) {
|
|
381
|
-
return res.send(
|
|
368
|
+
return res.send(msgpack.encode(device));
|
|
382
369
|
}
|
|
383
370
|
else {
|
|
384
|
-
res.sendStatus(404);
|
|
371
|
+
return res.sendStatus(404);
|
|
385
372
|
}
|
|
386
|
-
})
|
|
387
|
-
api.post("/device/:id/keyBundle",
|
|
373
|
+
});
|
|
374
|
+
api.post("/device/:id/keyBundle", protect, async (req, res) => {
|
|
388
375
|
try {
|
|
389
|
-
const keyBundle =
|
|
376
|
+
const keyBundle = await db.getKeyBundle(getParam(req, "id"));
|
|
390
377
|
if (keyBundle) {
|
|
391
|
-
res.send(
|
|
378
|
+
res.send(msgpack.encode(keyBundle));
|
|
392
379
|
}
|
|
393
380
|
else {
|
|
394
381
|
res.sendStatus(404);
|
|
395
382
|
}
|
|
396
383
|
}
|
|
397
|
-
catch
|
|
384
|
+
catch {
|
|
398
385
|
res.sendStatus(500);
|
|
399
386
|
}
|
|
400
|
-
})
|
|
401
|
-
api.post("/device/:id/mail",
|
|
402
|
-
const deviceDetails = req
|
|
403
|
-
.device;
|
|
387
|
+
});
|
|
388
|
+
api.post("/device/:id/mail", protect, async (req, res) => {
|
|
389
|
+
const deviceDetails = req.device;
|
|
404
390
|
if (!deviceDetails) {
|
|
405
391
|
res.sendStatus(401);
|
|
406
392
|
return;
|
|
407
393
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
394
|
+
const inbox = await db.retrieveMail(deviceDetails.deviceID);
|
|
395
|
+
res.send(msgpack.encode(inbox));
|
|
396
|
+
});
|
|
397
|
+
api.post("/device/:id/connect", protect, async (req, res) => {
|
|
398
|
+
const parsedBody = connectPayload.safeParse(req.body);
|
|
399
|
+
if (!parsedBody.success) {
|
|
400
|
+
res.status(400).json({
|
|
401
|
+
error: "Invalid connect payload",
|
|
402
|
+
issues: parsedBody.error.issues,
|
|
403
|
+
});
|
|
404
|
+
return;
|
|
414
405
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const { signed } = req.body;
|
|
418
|
-
const device = yield db.retrieveDevice(req.params.id);
|
|
406
|
+
const { signed } = parsedBody.data;
|
|
407
|
+
const device = await db.retrieveDevice(getParam(req, "id"));
|
|
419
408
|
if (!device) {
|
|
420
409
|
res.sendStatus(404);
|
|
421
410
|
return;
|
|
422
411
|
}
|
|
423
|
-
const regKey =
|
|
412
|
+
const regKey = xSignOpen(signed, XUtils.decodeHex(device.signKey));
|
|
424
413
|
if (regKey &&
|
|
425
|
-
tokenValidator(
|
|
426
|
-
const token =
|
|
427
|
-
expiresIn:
|
|
414
|
+
tokenValidator(uuidStringify(regKey), TokenScopes.Connect)) {
|
|
415
|
+
const token = jwt.sign({ device }, getJwtSecret(), {
|
|
416
|
+
expiresIn: JWT_EXPIRY,
|
|
428
417
|
});
|
|
429
|
-
|
|
430
|
-
res.
|
|
431
|
-
res.sendStatus(200);
|
|
418
|
+
jwt.verify(token, getJwtSecret());
|
|
419
|
+
res.send(msgpack.encode({ deviceToken: token }));
|
|
432
420
|
}
|
|
433
421
|
else {
|
|
434
422
|
res.sendStatus(401);
|
|
435
423
|
}
|
|
436
|
-
})
|
|
437
|
-
api.get("/device/:id/otk/count",
|
|
438
|
-
const deviceDetails = req
|
|
439
|
-
.device;
|
|
424
|
+
});
|
|
425
|
+
api.get("/device/:id/otk/count", protect, async (req, res) => {
|
|
426
|
+
const deviceDetails = req.device;
|
|
440
427
|
if (!deviceDetails) {
|
|
441
428
|
res.sendStatus(401);
|
|
442
429
|
return;
|
|
443
430
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
431
|
+
const count = await db.getOTKCount(deviceDetails.deviceID);
|
|
432
|
+
res.send(msgpack.encode({ count }));
|
|
433
|
+
});
|
|
434
|
+
api.post("/device/:id/otk", protect, async (req, res) => {
|
|
435
|
+
const parsedOTKs = z.array(PreKeysWSSchema).safeParse(req.body);
|
|
436
|
+
if (!parsedOTKs.success) {
|
|
437
|
+
res.status(400).json({
|
|
438
|
+
error: "Invalid OTK payload",
|
|
439
|
+
issues: parsedOTKs.error.issues,
|
|
440
|
+
});
|
|
447
441
|
return;
|
|
448
442
|
}
|
|
449
|
-
|
|
450
|
-
|
|
443
|
+
const submittedOTKs = parsedOTKs.data;
|
|
444
|
+
if (submittedOTKs.length === 0) {
|
|
445
|
+
res.sendStatus(200);
|
|
446
|
+
return;
|
|
451
447
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
const [otk] = submittedOTKs;
|
|
458
|
-
const device = yield db.retrieveDevice(deviceID);
|
|
459
|
-
if (!device) {
|
|
448
|
+
const userDetails = getUser(req);
|
|
449
|
+
const deviceID = getParam(req, "id");
|
|
450
|
+
const otk = submittedOTKs[0];
|
|
451
|
+
const device = await db.retrieveDevice(deviceID);
|
|
452
|
+
if (!device || !otk) {
|
|
460
453
|
res.sendStatus(404);
|
|
461
454
|
return;
|
|
462
455
|
}
|
|
463
|
-
const message =
|
|
456
|
+
const message = xSignOpen(otk.signature, XUtils.decodeHex(device.signKey));
|
|
464
457
|
if (!message) {
|
|
465
458
|
res.sendStatus(401);
|
|
466
459
|
return;
|
|
467
460
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
461
|
+
await db.saveOTK(userDetails.userID, deviceID, submittedOTKs);
|
|
462
|
+
res.sendStatus(200);
|
|
463
|
+
});
|
|
464
|
+
api.get("/emoji/:emojiID/details", protect, async (req, res) => {
|
|
465
|
+
const emoji = await db.retrieveEmoji(getParam(req, "emojiID"));
|
|
466
|
+
res.send(msgpack.encode(emoji));
|
|
467
|
+
});
|
|
468
|
+
api.get("/emoji/:emojiID", protect, async (req, res) => {
|
|
469
|
+
const safeId = safePathParam.safeParse(getParam(req, "emojiID"));
|
|
470
|
+
if (!safeId.success) {
|
|
471
|
+
res.sendStatus(400);
|
|
472
|
+
return;
|
|
474
473
|
}
|
|
475
|
-
|
|
476
|
-
|
|
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());
|
|
474
|
+
const filePath = "./emoji/" + safeId.data;
|
|
475
|
+
const typeDetails = await fileTypeFromFile(filePath).catch(() => null);
|
|
476
|
+
if (!typeDetails) {
|
|
484
477
|
res.sendStatus(404);
|
|
485
|
-
|
|
486
|
-
const typeDetails = yield file_type_1.default.fromStream(stream);
|
|
487
|
-
if (typeDetails) {
|
|
488
|
-
res.set("Content-type", typeDetails.mime);
|
|
478
|
+
return;
|
|
489
479
|
}
|
|
480
|
+
res.set("Content-type", typeDetails.mime);
|
|
490
481
|
res.set("Cache-control", "public, max-age=31536000");
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
482
|
+
const stream = fs.createReadStream(filePath);
|
|
483
|
+
stream.on("error", (_err) => {
|
|
484
|
+
// debugger: emoji stream read error
|
|
494
485
|
res.sendStatus(500);
|
|
495
486
|
});
|
|
496
|
-
|
|
497
|
-
})
|
|
498
|
-
api.post("/emoji/:serverID/json",
|
|
499
|
-
const
|
|
500
|
-
|
|
487
|
+
stream.pipe(res);
|
|
488
|
+
});
|
|
489
|
+
api.post("/emoji/:serverID/json", protect, async (req, res) => {
|
|
490
|
+
const parsedPayload = emojiPayload.safeParse(req.body);
|
|
491
|
+
if (!parsedPayload.success) {
|
|
492
|
+
res.status(400).json({
|
|
493
|
+
error: "Invalid emoji payload",
|
|
494
|
+
issues: parsedPayload.error.issues,
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const payload = parsedPayload.data;
|
|
499
|
+
const userDetails = getUser(req);
|
|
501
500
|
const device = req.device;
|
|
502
501
|
if (!device) {
|
|
503
502
|
res.sendStatus(401);
|
|
504
503
|
return;
|
|
505
504
|
}
|
|
506
|
-
|
|
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
|
-
}
|
|
505
|
+
if (!payload.file) {
|
|
506
|
+
res.sendStatus(400);
|
|
507
|
+
return;
|
|
516
508
|
}
|
|
517
|
-
|
|
509
|
+
const buf = Buffer.from(XUtils.decodeBase64(payload.file));
|
|
510
|
+
const serverEntry = await db.retrieveServer(getParam(req, "serverID"));
|
|
511
|
+
const permissionList = await db.retrievePermissionsByResourceID(getParam(req, "serverID"));
|
|
512
|
+
if (!userHasPermission(permissionList, userDetails.userID, POWER_LEVELS.EMOJI)) {
|
|
518
513
|
res.sendStatus(401);
|
|
519
514
|
return;
|
|
520
515
|
}
|
|
@@ -524,56 +519,60 @@ const initApp = (api, db, log, tokenValidator, signKeys, notify) => {
|
|
|
524
519
|
}
|
|
525
520
|
if (!payload.name) {
|
|
526
521
|
res.sendStatus(400);
|
|
522
|
+
return;
|
|
527
523
|
}
|
|
528
524
|
if (Buffer.byteLength(buf) > 256000) {
|
|
529
|
-
console.warn("File to big.");
|
|
530
525
|
res.sendStatus(413);
|
|
526
|
+
return;
|
|
531
527
|
}
|
|
532
|
-
const mimeType =
|
|
533
|
-
if (!
|
|
528
|
+
const mimeType = await fileTypeFromBuffer(buf);
|
|
529
|
+
if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
|
|
534
530
|
res.status(400).send({
|
|
535
|
-
error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
|
|
531
|
+
error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
|
|
532
|
+
String(mimeType?.ext),
|
|
536
533
|
});
|
|
537
534
|
return;
|
|
538
535
|
}
|
|
539
536
|
const emoji = {
|
|
540
|
-
emojiID:
|
|
541
|
-
owner: req.params.serverID,
|
|
537
|
+
emojiID: crypto.randomUUID(),
|
|
542
538
|
name: payload.name,
|
|
539
|
+
owner: getParam(req, "serverID"),
|
|
543
540
|
};
|
|
544
|
-
|
|
541
|
+
await db.createEmoji(emoji);
|
|
545
542
|
try {
|
|
546
543
|
// write the file to disk
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
});
|
|
550
|
-
res.send(msgpack_lite_1.default.encode(emoji));
|
|
544
|
+
await fsp.writeFile("emoji/" + emoji.emojiID, buf);
|
|
545
|
+
res.send(msgpack.encode(emoji));
|
|
551
546
|
}
|
|
552
|
-
catch (
|
|
553
|
-
|
|
547
|
+
catch (_err) {
|
|
548
|
+
// debugger: emoji write failed
|
|
554
549
|
res.sendStatus(500);
|
|
555
550
|
}
|
|
556
|
-
})
|
|
557
|
-
api.post("/emoji/:serverID",
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
551
|
+
});
|
|
552
|
+
api.post("/emoji/:serverID", protect, multer().single("emoji"), async (req, res) => {
|
|
553
|
+
const parsedPayload = emojiPayload.safeParse(req.body);
|
|
554
|
+
if (!parsedPayload.success) {
|
|
555
|
+
res.status(400).json({
|
|
556
|
+
error: "Invalid emoji payload",
|
|
557
|
+
issues: parsedPayload.error.issues,
|
|
558
|
+
});
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const payload = parsedPayload.data;
|
|
562
|
+
const serverID = getParam(req, "serverID");
|
|
563
|
+
if (typeof serverID !== "string") {
|
|
564
|
+
res.sendStatus(400);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const serverEntry = await db.retrieveServer(serverID);
|
|
568
|
+
const userDetails = getUser(req);
|
|
569
|
+
const deviceDetails = req.device;
|
|
563
570
|
if (!deviceDetails) {
|
|
564
571
|
res.sendStatus(401);
|
|
565
572
|
return;
|
|
566
573
|
}
|
|
567
|
-
const permissionList =
|
|
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) {
|
|
574
|
+
const permissionList = await db.retrievePermissionsByResourceID(serverID);
|
|
575
|
+
if (!userHasPermission(permissionList, userDetails.userID, POWER_LEVELS.EMOJI)) {
|
|
577
576
|
res.sendStatus(401);
|
|
578
577
|
return;
|
|
579
578
|
}
|
|
@@ -583,51 +582,50 @@ const initApp = (api, db, log, tokenValidator, signKeys, notify) => {
|
|
|
583
582
|
}
|
|
584
583
|
if (!payload.name) {
|
|
585
584
|
res.sendStatus(400);
|
|
585
|
+
return;
|
|
586
586
|
}
|
|
587
587
|
if (!req.file) {
|
|
588
|
-
console.warn("MISSING FILE");
|
|
589
588
|
res.sendStatus(400);
|
|
590
589
|
return;
|
|
591
590
|
}
|
|
592
591
|
if (Buffer.byteLength(req.file.buffer) > 256000) {
|
|
593
|
-
console.warn("File to big.");
|
|
594
592
|
res.sendStatus(413);
|
|
593
|
+
return;
|
|
595
594
|
}
|
|
596
|
-
const mimeType =
|
|
597
|
-
if (!
|
|
595
|
+
const mimeType = await fileTypeFromBuffer(req.file.buffer);
|
|
596
|
+
if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
|
|
598
597
|
res.status(400).send({
|
|
599
|
-
error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
|
|
598
|
+
error: "Unsupported file type. Expected jpeg, png, gif, apng, or avif but received " +
|
|
599
|
+
String(mimeType?.ext),
|
|
600
600
|
});
|
|
601
601
|
return;
|
|
602
602
|
}
|
|
603
603
|
const emoji = {
|
|
604
|
-
emojiID:
|
|
605
|
-
owner: req.params.serverID,
|
|
604
|
+
emojiID: crypto.randomUUID(),
|
|
606
605
|
name: payload.name,
|
|
606
|
+
owner: serverID,
|
|
607
607
|
};
|
|
608
|
-
|
|
608
|
+
await db.createEmoji(emoji);
|
|
609
609
|
try {
|
|
610
610
|
// write the file to disk
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
});
|
|
614
|
-
res.send(msgpack_lite_1.default.encode(emoji));
|
|
611
|
+
await fsp.writeFile("emoji/" + emoji.emojiID, req.file.buffer);
|
|
612
|
+
res.send(msgpack.encode(emoji));
|
|
615
613
|
}
|
|
616
|
-
catch (
|
|
617
|
-
|
|
614
|
+
catch (_err) {
|
|
615
|
+
// debugger: emoji write failed
|
|
618
616
|
res.sendStatus(500);
|
|
619
617
|
}
|
|
620
|
-
})
|
|
618
|
+
});
|
|
621
619
|
// COMPLEX RESOURCES
|
|
622
620
|
api.use("/user", userRouter);
|
|
623
621
|
api.use("/file", fileRouter);
|
|
624
622
|
api.use("/avatar", avatarRouter);
|
|
625
623
|
api.use("/invite", inviteRouter);
|
|
624
|
+
setupDocs(api);
|
|
625
|
+
// Central error handler MUST be last. Handles both thrown AppErrors
|
|
626
|
+
// (client-safe status + message) and programmer errors (generic 500
|
|
627
|
+
// with full details logged server-side). See src/server/errors.ts
|
|
628
|
+
// for the CWE mapping.
|
|
629
|
+
api.use(errorHandler());
|
|
626
630
|
};
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* @ignore
|
|
630
|
-
*/
|
|
631
|
-
const jestRun = () => {
|
|
632
|
-
return process.env.JEST_WORKER_ID !== undefined;
|
|
633
|
-
};
|
|
631
|
+
//# sourceMappingURL=index.js.map
|