@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/Database.js
CHANGED
|
@@ -1,394 +1,430 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}));
|
|
9
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
-
}) : function(o, v) {
|
|
12
|
-
o["default"] = v;
|
|
13
|
-
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
-
if (mod && mod.__esModule) return mod;
|
|
16
|
-
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
-
__setModuleDefault(result, mod);
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
22
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
23
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
24
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
25
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
26
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
27
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
32
8
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
9
|
+
import { EventEmitter } from "events";
|
|
10
|
+
import { pbkdf2Sync } from "node:crypto";
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
import { XUtils } from "@vex-chat/crypto";
|
|
15
|
+
import { MailType } from "@vex-chat/types";
|
|
16
|
+
import argon2 from "argon2";
|
|
17
|
+
/**
|
|
18
|
+
* Narrow a plain integer from the `mailType` SQL column to the
|
|
19
|
+
* `MailType` union (0 = initial, 1 = subsequent). Throws if the
|
|
20
|
+
* database contains an unexpected value, catching row corruption
|
|
21
|
+
* at read time instead of propagating an invalid literal into
|
|
22
|
+
* application code.
|
|
23
|
+
*/
|
|
24
|
+
function parseMailType(n) {
|
|
25
|
+
if (n === MailType.initial)
|
|
26
|
+
return MailType.initial;
|
|
27
|
+
if (n === MailType.subsequent)
|
|
28
|
+
return MailType.subsequent;
|
|
29
|
+
throw new Error(`Invalid mailType in database row: ${String(n)}`);
|
|
30
|
+
}
|
|
31
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
32
|
+
import { Kysely, Migrator, sql, SqliteDialect } from "kysely";
|
|
33
|
+
import { stringify as uuidStringify, validate as uuidValidate } from "uuid";
|
|
34
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const migrationFolder = path.join(__dirname, "migrations");
|
|
36
|
+
/**
|
|
37
|
+
* Cross-platform Kysely migration provider.
|
|
38
|
+
*
|
|
39
|
+
* Replaces Kysely's built-in `FileMigrationProvider`, which on Windows
|
|
40
|
+
* fails with `ERR_UNSUPPORTED_ESM_URL_SCHEME` because it does
|
|
41
|
+
* `await import(joinedPath)` where `joinedPath` is a Windows absolute
|
|
42
|
+
* path like `D:\\spire\\src\\migrations\\schema.ts`. Node's ESM loader
|
|
43
|
+
* requires `file://` URLs for absolute paths on Windows.
|
|
44
|
+
*
|
|
45
|
+
* This implementation uses `pathToFileURL` to convert each migration
|
|
46
|
+
* file's absolute path to a `file://` URL before passing it to
|
|
47
|
+
* `import()`. Works on Linux, macOS, and Windows. Filters out `.d.ts`
|
|
48
|
+
* declaration files and accepts both `.ts` and `.js` source files for
|
|
49
|
+
* spire's `--experimental-strip-types` runtime.
|
|
50
|
+
*/
|
|
51
|
+
class CrossPlatformMigrationProvider {
|
|
52
|
+
folder;
|
|
53
|
+
constructor(folder) {
|
|
54
|
+
this.folder = folder;
|
|
55
|
+
}
|
|
56
|
+
async getMigrations() {
|
|
57
|
+
const files = await fs.readdir(this.folder);
|
|
58
|
+
const migrations = {};
|
|
59
|
+
for (const file of files.sort()) {
|
|
60
|
+
if (file.endsWith(".d.ts"))
|
|
61
|
+
continue;
|
|
62
|
+
if (!file.endsWith(".ts") && !file.endsWith(".js"))
|
|
63
|
+
continue;
|
|
64
|
+
const fullPath = path.join(this.folder, file);
|
|
65
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
66
|
+
const mod = await import(__rewriteRelativeImportExtension(fileUrl));
|
|
67
|
+
if (!isMigration(mod)) {
|
|
68
|
+
throw new Error(`Invalid migration ${file}: expected an exported \`up\` function`);
|
|
69
|
+
}
|
|
70
|
+
const name = file.replace(/\.(ts|js)$/, "");
|
|
71
|
+
migrations[name] = mod;
|
|
72
|
+
}
|
|
73
|
+
return migrations;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function isMigration(mod) {
|
|
77
|
+
return (typeof mod === "object" &&
|
|
78
|
+
mod !== null &&
|
|
79
|
+
"up" in mod &&
|
|
80
|
+
typeof mod.up === "function");
|
|
81
|
+
}
|
|
41
82
|
const pubkeyRegex = /[0-9a-f]{64}/;
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
/** Legacy iteration count kept only for verifying old PBKDF2 hashes. */
|
|
84
|
+
const PBKDF2_ITERATIONS = 1000;
|
|
85
|
+
export class Database extends EventEmitter {
|
|
86
|
+
db;
|
|
44
87
|
constructor(options) {
|
|
45
88
|
super();
|
|
46
|
-
|
|
47
|
-
|
|
89
|
+
const dbType = options?.dbType || "sqlite3";
|
|
90
|
+
let filename;
|
|
91
|
+
switch (dbType) {
|
|
92
|
+
case "sqlite":
|
|
48
93
|
case "sqlite3":
|
|
49
|
-
|
|
50
|
-
client: "sqlite3",
|
|
51
|
-
connection: {
|
|
52
|
-
filename: "spire.sqlite",
|
|
53
|
-
},
|
|
54
|
-
useNullAsDefault: true,
|
|
55
|
-
});
|
|
94
|
+
filename = "spire.sqlite";
|
|
56
95
|
break;
|
|
57
96
|
case "sqlite3mem":
|
|
58
|
-
|
|
59
|
-
client: "sqlite3",
|
|
60
|
-
connection: {
|
|
61
|
-
filename: ":memory:",
|
|
62
|
-
},
|
|
63
|
-
useNullAsDefault: true,
|
|
64
|
-
});
|
|
97
|
+
filename = ":memory:";
|
|
65
98
|
break;
|
|
66
|
-
case "mysql":
|
|
67
99
|
default:
|
|
68
|
-
|
|
69
|
-
client: "mysql",
|
|
70
|
-
connection: {
|
|
71
|
-
host: process.env.SQL_HOST,
|
|
72
|
-
user: process.env.SQL_USER,
|
|
73
|
-
password: process.env.SQL_PASSWORD,
|
|
74
|
-
database: process.env.SQL_DB_NAME,
|
|
75
|
-
},
|
|
76
|
-
});
|
|
100
|
+
filename = "spire.sqlite";
|
|
77
101
|
break;
|
|
78
102
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
103
|
+
const sqliteDb = new BetterSqlite3(filename);
|
|
104
|
+
sqliteDb.pragma("journal_mode = WAL");
|
|
105
|
+
sqliteDb.pragma("synchronous = NORMAL");
|
|
106
|
+
sqliteDb.pragma("busy_timeout = 5000");
|
|
107
|
+
sqliteDb.pragma("cache_size = -64000");
|
|
108
|
+
sqliteDb.pragma("temp_store = memory");
|
|
109
|
+
sqliteDb.pragma("foreign_keys = ON");
|
|
110
|
+
this.db = new Kysely({
|
|
111
|
+
dialect: new SqliteDialect({ database: sqliteDb }),
|
|
112
|
+
});
|
|
113
|
+
void this.init();
|
|
114
|
+
}
|
|
115
|
+
async close() {
|
|
116
|
+
await this.db.destroy();
|
|
117
|
+
}
|
|
118
|
+
async createChannel(name, serverID) {
|
|
119
|
+
const channel = {
|
|
120
|
+
channelID: crypto.randomUUID(),
|
|
121
|
+
name,
|
|
122
|
+
serverID,
|
|
123
|
+
};
|
|
124
|
+
await this.db.insertInto("channels").values(channel).execute();
|
|
125
|
+
return channel;
|
|
126
|
+
}
|
|
127
|
+
async createDevice(owner, payload) {
|
|
128
|
+
const device = {
|
|
129
|
+
deleted: 0,
|
|
130
|
+
deviceID: crypto.randomUUID(),
|
|
131
|
+
lastLogin: new Date().toISOString(),
|
|
132
|
+
name: payload.deviceName,
|
|
133
|
+
owner,
|
|
134
|
+
signKey: payload.signKey,
|
|
135
|
+
};
|
|
136
|
+
await this.db.insertInto("devices").values(device).execute();
|
|
137
|
+
const medPreKeys = {
|
|
138
|
+
deviceID: device.deviceID,
|
|
139
|
+
index: payload.preKeyIndex,
|
|
140
|
+
keyID: crypto.randomUUID(),
|
|
141
|
+
publicKey: payload.preKey,
|
|
142
|
+
signature: payload.preKeySignature,
|
|
143
|
+
userID: owner,
|
|
144
|
+
};
|
|
145
|
+
await this.db.insertInto("preKeys").values(medPreKeys).execute();
|
|
146
|
+
return toDevice(device);
|
|
147
|
+
}
|
|
148
|
+
async createEmoji(emoji) {
|
|
149
|
+
await this.db.insertInto("emojis").values(emoji).execute();
|
|
150
|
+
}
|
|
151
|
+
async createFile(file) {
|
|
152
|
+
await this.db.insertInto("files").values(file).execute();
|
|
153
|
+
}
|
|
154
|
+
async createInvite(inviteID, serverID, ownerID, expiration) {
|
|
155
|
+
const invite = {
|
|
156
|
+
expiration,
|
|
157
|
+
inviteID,
|
|
158
|
+
owner: ownerID,
|
|
159
|
+
serverID,
|
|
160
|
+
};
|
|
161
|
+
await this.db.insertInto("invites").values(invite).execute();
|
|
162
|
+
return invite;
|
|
163
|
+
}
|
|
164
|
+
async createPermission(userID, resourceType, resourceID, powerLevel) {
|
|
165
|
+
const permissionID = crypto.randomUUID();
|
|
166
|
+
// check if it already exists
|
|
167
|
+
const checkPermission = await this.db
|
|
168
|
+
.selectFrom("permissions")
|
|
169
|
+
.selectAll()
|
|
170
|
+
.where("userID", "=", userID)
|
|
171
|
+
.where("resourceID", "=", resourceID)
|
|
172
|
+
.execute();
|
|
173
|
+
const existing = checkPermission[0];
|
|
174
|
+
if (existing) {
|
|
175
|
+
return existing;
|
|
176
|
+
}
|
|
177
|
+
const permission = {
|
|
178
|
+
permissionID,
|
|
179
|
+
powerLevel,
|
|
180
|
+
resourceID,
|
|
181
|
+
resourceType,
|
|
182
|
+
userID,
|
|
183
|
+
};
|
|
184
|
+
await this.db.insertInto("permissions").values(permission).execute();
|
|
185
|
+
return permission;
|
|
186
|
+
}
|
|
187
|
+
async createServer(name, ownerID) {
|
|
188
|
+
// create the server
|
|
189
|
+
const server = {
|
|
190
|
+
name,
|
|
191
|
+
serverID: crypto.randomUUID(),
|
|
192
|
+
};
|
|
193
|
+
await this.db
|
|
194
|
+
.insertInto("servers")
|
|
195
|
+
.values({
|
|
196
|
+
icon: server.icon ?? null,
|
|
197
|
+
name: server.name,
|
|
198
|
+
serverID: server.serverID,
|
|
199
|
+
})
|
|
200
|
+
.execute();
|
|
201
|
+
// create the admin permission
|
|
202
|
+
await this.createPermission(ownerID, "server", server.serverID, 100);
|
|
203
|
+
// create the general channel
|
|
204
|
+
await this.createChannel("general", server.serverID);
|
|
205
|
+
return server;
|
|
206
|
+
}
|
|
207
|
+
async createUser(regKey, regPayload) {
|
|
208
|
+
try {
|
|
209
|
+
const passwordHash = await hashPasswordArgon2(regPayload.password);
|
|
210
|
+
const user = {
|
|
211
|
+
lastSeen: new Date().toISOString(),
|
|
212
|
+
passwordHash,
|
|
213
|
+
passwordSalt: "",
|
|
214
|
+
userID: uuidStringify(regKey),
|
|
215
|
+
username: regPayload.username,
|
|
159
216
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return this.db
|
|
175
|
-
.from("devices")
|
|
176
|
-
.where({ deviceID })
|
|
177
|
-
.update({ deleted: true });
|
|
178
|
-
});
|
|
217
|
+
await this.db
|
|
218
|
+
.insertInto("users")
|
|
219
|
+
.values({
|
|
220
|
+
...user,
|
|
221
|
+
hashAlgo: "argon2id",
|
|
222
|
+
lastSeen: user.lastSeen,
|
|
223
|
+
})
|
|
224
|
+
.execute();
|
|
225
|
+
await this.createDevice(user.userID, regPayload);
|
|
226
|
+
return [user, null];
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
return [null, err instanceof Error ? err : new Error(String(err))];
|
|
230
|
+
}
|
|
179
231
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
async deleteChannel(channelID) {
|
|
233
|
+
await this.deletePermissions(channelID);
|
|
234
|
+
await this.db
|
|
235
|
+
.deleteFrom("mail")
|
|
236
|
+
.where("group", "=", channelID)
|
|
237
|
+
.execute();
|
|
238
|
+
await this.db
|
|
239
|
+
.deleteFrom("channels")
|
|
240
|
+
.where("channelID", "=", channelID)
|
|
241
|
+
.execute();
|
|
242
|
+
}
|
|
243
|
+
async deleteDevice(deviceID) {
|
|
244
|
+
await this.db
|
|
245
|
+
.deleteFrom("preKeys")
|
|
246
|
+
.where("deviceID", "=", deviceID)
|
|
247
|
+
.execute();
|
|
248
|
+
await this.db
|
|
249
|
+
.deleteFrom("oneTimeKeys")
|
|
250
|
+
.where("deviceID", "=", deviceID)
|
|
251
|
+
.execute();
|
|
252
|
+
await this.db
|
|
253
|
+
.updateTable("devices")
|
|
254
|
+
.set({ deleted: 1 })
|
|
255
|
+
.where("deviceID", "=", deviceID)
|
|
256
|
+
.execute();
|
|
257
|
+
}
|
|
258
|
+
async deleteEmoji(emojiID) {
|
|
259
|
+
await this.db
|
|
260
|
+
.deleteFrom("emojis")
|
|
261
|
+
.where("emojiID", "=", emojiID)
|
|
262
|
+
.execute();
|
|
263
|
+
}
|
|
264
|
+
async deleteInvite(inviteID) {
|
|
265
|
+
await this.db
|
|
266
|
+
.deleteFrom("invites")
|
|
267
|
+
.where("inviteID", "=", inviteID)
|
|
268
|
+
.execute();
|
|
269
|
+
}
|
|
270
|
+
async deleteMail(nonce, userID) {
|
|
271
|
+
await this.db
|
|
272
|
+
.deleteFrom("mail")
|
|
273
|
+
.where("nonce", "=", XUtils.encodeHex(nonce))
|
|
274
|
+
.where("recipient", "=", userID)
|
|
275
|
+
.execute();
|
|
276
|
+
}
|
|
277
|
+
async deletePermission(permissionID) {
|
|
278
|
+
await this.db
|
|
279
|
+
.deleteFrom("permissions")
|
|
280
|
+
.where("permissionID", "=", permissionID)
|
|
281
|
+
.execute();
|
|
282
|
+
}
|
|
283
|
+
async deletePermissions(resourceID) {
|
|
284
|
+
await this.db
|
|
285
|
+
.deleteFrom("permissions")
|
|
286
|
+
.where("resourceID", "=", resourceID)
|
|
287
|
+
.execute();
|
|
288
|
+
}
|
|
289
|
+
async deleteServer(serverID) {
|
|
290
|
+
await this.deletePermissions(serverID);
|
|
291
|
+
const channels = await this.retrieveChannels(serverID);
|
|
292
|
+
for (const channel of channels) {
|
|
293
|
+
await this.deleteChannel(channel.channelID);
|
|
294
|
+
}
|
|
295
|
+
await this.db
|
|
296
|
+
.deleteFrom("servers")
|
|
297
|
+
.where("serverID", "=", serverID)
|
|
298
|
+
.execute();
|
|
299
|
+
}
|
|
300
|
+
async getKeyBundle(deviceID) {
|
|
301
|
+
const device = await this.retrieveDevice(deviceID);
|
|
302
|
+
if (!device) {
|
|
303
|
+
throw new Error("DeviceID not found.");
|
|
304
|
+
}
|
|
305
|
+
const otk = (await this.getOTK(deviceID)) || undefined;
|
|
306
|
+
const preKey = await this.getPreKeys(deviceID);
|
|
307
|
+
if (!preKey) {
|
|
308
|
+
throw new Error("Failed to get prekey.");
|
|
309
|
+
}
|
|
310
|
+
const keyBundle = {
|
|
311
|
+
otk,
|
|
312
|
+
preKey,
|
|
313
|
+
signKey: XUtils.decodeHex(device.signKey),
|
|
314
|
+
};
|
|
315
|
+
return keyBundle;
|
|
316
|
+
}
|
|
317
|
+
async getOTK(deviceID) {
|
|
318
|
+
const rows = await this.db
|
|
319
|
+
.selectFrom("oneTimeKeys")
|
|
320
|
+
.selectAll()
|
|
321
|
+
.where("deviceID", "=", deviceID)
|
|
322
|
+
.orderBy("index")
|
|
323
|
+
.limit(1)
|
|
324
|
+
.execute();
|
|
325
|
+
const otkInfo = rows[0];
|
|
326
|
+
if (!otkInfo) {
|
|
204
327
|
return null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// check if it already exists
|
|
259
|
-
const checkPermission = yield this.db
|
|
260
|
-
.from("permissions")
|
|
261
|
-
.select()
|
|
262
|
-
.where({ userID, resourceID });
|
|
263
|
-
if (checkPermission.length > 0) {
|
|
264
|
-
return checkPermission[0];
|
|
265
|
-
}
|
|
266
|
-
const permission = {
|
|
267
|
-
permissionID,
|
|
268
|
-
userID,
|
|
269
|
-
resourceType,
|
|
270
|
-
resourceID,
|
|
271
|
-
powerLevel,
|
|
272
|
-
};
|
|
273
|
-
yield this.db("permissions").insert(permission);
|
|
274
|
-
return permission;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
retrieveInvite(inviteID) {
|
|
278
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
279
|
-
const rows = yield this.db
|
|
280
|
-
.from("invites")
|
|
281
|
-
.select()
|
|
282
|
-
.where({ inviteID });
|
|
283
|
-
if (rows.length === 0) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
return rows[0];
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
retrieveServerInvites(serverID) {
|
|
290
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
-
const rows = yield this.db
|
|
292
|
-
.from("invites")
|
|
293
|
-
.select()
|
|
294
|
-
.where({ serverID });
|
|
295
|
-
return rows.filter((invite) => {
|
|
296
|
-
const valid = new Date(Date.now()).getTime() <
|
|
297
|
-
new Date(invite.expiration).getTime();
|
|
298
|
-
if (!valid) {
|
|
299
|
-
this.deleteInvite(invite.inviteID);
|
|
300
|
-
}
|
|
301
|
-
return valid;
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
deleteInvite(inviteID) {
|
|
306
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
-
yield this.db
|
|
308
|
-
.from("invites")
|
|
309
|
-
.where({ inviteID })
|
|
310
|
-
.delete();
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
createInvite(inviteID, serverID, ownerID, expiration) {
|
|
314
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
-
const invite = {
|
|
316
|
-
inviteID,
|
|
317
|
-
serverID,
|
|
318
|
-
owner: ownerID,
|
|
319
|
-
expiration,
|
|
320
|
-
};
|
|
321
|
-
yield this.db("invites").insert(invite);
|
|
322
|
-
return invite;
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
retrieveGroupMembers(channelID) {
|
|
326
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
327
|
-
const channel = yield this.retrieveChannel(channelID);
|
|
328
|
-
if (!channel) {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
const permissions = yield this.db
|
|
332
|
-
.from("permissions")
|
|
333
|
-
.select()
|
|
334
|
-
.where({ resourceID: channel.serverID });
|
|
335
|
-
const groupMembers = [];
|
|
336
|
-
for (const permission of permissions) {
|
|
337
|
-
const user = yield this.retrieveUser(permission.userID);
|
|
338
|
-
if (user) {
|
|
339
|
-
groupMembers.push(user);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return groupMembers;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
retrieveChannel(channelID) {
|
|
346
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
347
|
-
const channels = yield this.db
|
|
348
|
-
.from("channels")
|
|
349
|
-
.select()
|
|
350
|
-
.where({ channelID })
|
|
351
|
-
.limit(1);
|
|
352
|
-
if (channels.length === 0) {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
return channels[0];
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
retrieveChannels(serverID) {
|
|
359
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
360
|
-
const channels = yield this.db
|
|
361
|
-
.from("channels")
|
|
362
|
-
.select()
|
|
363
|
-
.where({ serverID });
|
|
364
|
-
return channels;
|
|
365
|
-
});
|
|
328
|
+
}
|
|
329
|
+
const otk = {
|
|
330
|
+
deviceID: otkInfo.deviceID,
|
|
331
|
+
index: otkInfo.index,
|
|
332
|
+
publicKey: XUtils.decodeHex(otkInfo.publicKey),
|
|
333
|
+
signature: XUtils.decodeHex(otkInfo.signature),
|
|
334
|
+
};
|
|
335
|
+
// delete the otk
|
|
336
|
+
await this.db
|
|
337
|
+
.deleteFrom("oneTimeKeys")
|
|
338
|
+
.where("deviceID", "=", deviceID)
|
|
339
|
+
.where("index", "=", otk.index)
|
|
340
|
+
.execute();
|
|
341
|
+
return otk;
|
|
342
|
+
}
|
|
343
|
+
async getOTKCount(deviceID) {
|
|
344
|
+
const result = await this.db
|
|
345
|
+
.selectFrom("oneTimeKeys")
|
|
346
|
+
.select((eb) => eb.fn.countAll().as("count"))
|
|
347
|
+
.where("deviceID", "=", deviceID)
|
|
348
|
+
.executeTakeFirst();
|
|
349
|
+
return Number(result?.count ?? 0);
|
|
350
|
+
}
|
|
351
|
+
async getPreKeys(deviceID) {
|
|
352
|
+
const rows = await this.db
|
|
353
|
+
.selectFrom("preKeys")
|
|
354
|
+
.selectAll()
|
|
355
|
+
.where("deviceID", "=", deviceID)
|
|
356
|
+
.execute();
|
|
357
|
+
const preKeyInfo = rows[0];
|
|
358
|
+
if (!preKeyInfo) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const preKey = {
|
|
362
|
+
deviceID: preKeyInfo.deviceID,
|
|
363
|
+
index: preKeyInfo.index,
|
|
364
|
+
publicKey: XUtils.decodeHex(preKeyInfo.publicKey),
|
|
365
|
+
signature: XUtils.decodeHex(preKeyInfo.signature),
|
|
366
|
+
};
|
|
367
|
+
return preKey;
|
|
368
|
+
}
|
|
369
|
+
async getRequestsTotal() {
|
|
370
|
+
const row = await this.db
|
|
371
|
+
.selectFrom("service_metrics")
|
|
372
|
+
.select("metric_value")
|
|
373
|
+
.where("metric_key", "=", "requests_total")
|
|
374
|
+
.executeTakeFirst();
|
|
375
|
+
const raw = row?.metric_value;
|
|
376
|
+
const count = Number(raw);
|
|
377
|
+
if (!Number.isFinite(count) || count < 0) {
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
return count;
|
|
366
381
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
async incrementRequestsTotal(by = 1) {
|
|
383
|
+
if (!Number.isFinite(by) || by <= 0) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
await this.db
|
|
387
|
+
.updateTable("service_metrics")
|
|
388
|
+
.set({
|
|
389
|
+
metric_value: sql `metric_value + ${Math.floor(by)}`,
|
|
390
|
+
})
|
|
391
|
+
.where("metric_key", "=", "requests_total")
|
|
392
|
+
.execute();
|
|
393
|
+
}
|
|
394
|
+
async isHealthy() {
|
|
395
|
+
try {
|
|
396
|
+
await sql `select 1 as ok`.execute(this.db);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
catch (_err) {
|
|
400
|
+
// debugger: health check failed
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
377
403
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
404
|
+
async markDeviceLogin(device) {
|
|
405
|
+
await this.db
|
|
406
|
+
.updateTable("devices")
|
|
407
|
+
.set({ lastLogin: new Date().toISOString() })
|
|
408
|
+
.where("deviceID", "=", device.deviceID)
|
|
409
|
+
.execute();
|
|
410
|
+
}
|
|
411
|
+
async markUserSeen(user) {
|
|
412
|
+
await this.db
|
|
413
|
+
.updateTable("users")
|
|
414
|
+
.set({ lastSeen: new Date().toISOString() })
|
|
415
|
+
.where("userID", "=", user.userID)
|
|
416
|
+
.execute();
|
|
417
|
+
}
|
|
418
|
+
async rehashPassword(userID, newHash) {
|
|
419
|
+
await this.db
|
|
420
|
+
.updateTable("users")
|
|
421
|
+
.set({
|
|
422
|
+
hashAlgo: "argon2id",
|
|
423
|
+
passwordHash: newHash,
|
|
424
|
+
passwordSalt: "",
|
|
425
|
+
})
|
|
426
|
+
.where("userID", "=", userID)
|
|
427
|
+
.execute();
|
|
392
428
|
}
|
|
393
429
|
/**
|
|
394
430
|
* Retrives a list of users that should be notified when a specific resourceID
|
|
@@ -396,414 +432,341 @@ class Database extends events_1.EventEmitter {
|
|
|
396
432
|
*
|
|
397
433
|
* @param resourceID
|
|
398
434
|
*/
|
|
399
|
-
retrieveAffectedUsers(resourceID) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
users.push(user);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return users;
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
retrievePermissionsByResourceID(resourceID) {
|
|
413
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
414
|
-
return this.db
|
|
415
|
-
.from("permissions")
|
|
416
|
-
.select()
|
|
417
|
-
.where({ resourceID });
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
retrievePermissions(userID, resourceType) {
|
|
421
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
422
|
-
if (resourceType === "all") {
|
|
423
|
-
const sList = yield this.db
|
|
424
|
-
.from("permissions")
|
|
425
|
-
.select()
|
|
426
|
-
.where({ userID });
|
|
427
|
-
return sList;
|
|
428
|
-
}
|
|
429
|
-
const serverList = yield this.db
|
|
430
|
-
.from("permissions")
|
|
431
|
-
.select()
|
|
432
|
-
.where({ userID, resourceType });
|
|
433
|
-
return serverList;
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
retrieveServer(serverID) {
|
|
437
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
438
|
-
const rows = yield this.db
|
|
439
|
-
.from("servers")
|
|
440
|
-
.select()
|
|
441
|
-
.where({ serverID })
|
|
442
|
-
.limit(1);
|
|
443
|
-
if (rows.length === 0) {
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
const server = rows[0];
|
|
447
|
-
return server;
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
deletePermissions(resourceID) {
|
|
451
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
452
|
-
yield this.db
|
|
453
|
-
.from("permissions")
|
|
454
|
-
.where({ resourceID })
|
|
455
|
-
.delete();
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
deletePermission(permissionID) {
|
|
459
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
460
|
-
yield this.db
|
|
461
|
-
.from("permissions")
|
|
462
|
-
.where({ permissionID })
|
|
463
|
-
.delete();
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
retrievePermission(permissionID) {
|
|
467
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
468
|
-
const rows = yield this.db
|
|
469
|
-
.from("permissions")
|
|
470
|
-
.where({ permissionID })
|
|
471
|
-
.select();
|
|
472
|
-
if (rows.length === 0) {
|
|
473
|
-
return null;
|
|
435
|
+
async retrieveAffectedUsers(resourceID) {
|
|
436
|
+
const permissionList = await this.retrievePermissionsByResourceID(resourceID);
|
|
437
|
+
const users = [];
|
|
438
|
+
for (const permission of permissionList) {
|
|
439
|
+
const user = await this.retrieveUser(permission.userID);
|
|
440
|
+
if (user) {
|
|
441
|
+
users.push(user);
|
|
474
442
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
.
|
|
500
|
-
.where(
|
|
501
|
-
.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
.
|
|
509
|
-
.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const salt = crypto_1.xMakeNonce();
|
|
557
|
-
const passwordHash = exports.hashPassword(regPayload.password, salt);
|
|
558
|
-
const user = {
|
|
559
|
-
userID: uuid.stringify(regKey),
|
|
560
|
-
username: regPayload.username,
|
|
561
|
-
lastSeen: new Date(Date.now()),
|
|
562
|
-
passwordHash: passwordHash.toString("hex"),
|
|
563
|
-
passwordSalt: crypto_1.XUtils.encodeHex(salt),
|
|
564
|
-
};
|
|
565
|
-
yield this.db("users").insert(user);
|
|
566
|
-
yield this.createDevice(user.userID, regPayload);
|
|
567
|
-
return [user, null];
|
|
568
|
-
}
|
|
569
|
-
catch (err) {
|
|
570
|
-
return [null, err];
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
createFile(file) {
|
|
575
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
576
|
-
return this.db("files").insert(file);
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
retrieveFile(fileID) {
|
|
580
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
581
|
-
const file = yield this.db
|
|
582
|
-
.from("files")
|
|
583
|
-
.select()
|
|
584
|
-
.where({ fileID });
|
|
585
|
-
if (file.length === 0) {
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
return file[0];
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
// the identifier can be username, public key, or userID
|
|
592
|
-
retrieveUser(userIdentifier) {
|
|
593
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
594
|
-
let rows = [];
|
|
595
|
-
if (uuid.validate(userIdentifier)) {
|
|
596
|
-
rows = yield this.db
|
|
597
|
-
.from("users")
|
|
598
|
-
.select()
|
|
599
|
-
.where({ userID: userIdentifier })
|
|
600
|
-
.limit(1);
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
rows = yield this.db
|
|
604
|
-
.from("users")
|
|
605
|
-
.select()
|
|
606
|
-
.where({ username: userIdentifier })
|
|
607
|
-
.limit(1);
|
|
608
|
-
}
|
|
609
|
-
if (rows.length === 0) {
|
|
610
|
-
return null;
|
|
443
|
+
}
|
|
444
|
+
return users;
|
|
445
|
+
}
|
|
446
|
+
async retrieveChannel(channelID) {
|
|
447
|
+
const channels = await this.db
|
|
448
|
+
.selectFrom("channels")
|
|
449
|
+
.selectAll()
|
|
450
|
+
.where("channelID", "=", channelID)
|
|
451
|
+
.limit(1)
|
|
452
|
+
.execute();
|
|
453
|
+
return channels[0] ?? null;
|
|
454
|
+
}
|
|
455
|
+
async retrieveChannels(serverID) {
|
|
456
|
+
const channels = await this.db
|
|
457
|
+
.selectFrom("channels")
|
|
458
|
+
.selectAll()
|
|
459
|
+
.where("serverID", "=", serverID)
|
|
460
|
+
.execute();
|
|
461
|
+
return channels;
|
|
462
|
+
}
|
|
463
|
+
async retrieveDevice(deviceID) {
|
|
464
|
+
if (uuidValidate(deviceID)) {
|
|
465
|
+
const rows = await this.db
|
|
466
|
+
.selectFrom("devices")
|
|
467
|
+
.selectAll()
|
|
468
|
+
.where("deviceID", "=", deviceID)
|
|
469
|
+
.where("deleted", "=", 0)
|
|
470
|
+
.execute();
|
|
471
|
+
const device = rows[0];
|
|
472
|
+
return device ? toDevice(device) : null;
|
|
473
|
+
}
|
|
474
|
+
if (pubkeyRegex.test(deviceID)) {
|
|
475
|
+
const rows = await this.db
|
|
476
|
+
.selectFrom("devices")
|
|
477
|
+
.selectAll()
|
|
478
|
+
.where("signKey", "=", deviceID)
|
|
479
|
+
.where("deleted", "=", 0)
|
|
480
|
+
.execute();
|
|
481
|
+
const device = rows[0];
|
|
482
|
+
return device ? toDevice(device) : null;
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
async retrieveEmoji(emojiID) {
|
|
487
|
+
const rows = await this.db
|
|
488
|
+
.selectFrom("emojis")
|
|
489
|
+
.selectAll()
|
|
490
|
+
.where("emojiID", "=", emojiID)
|
|
491
|
+
.execute();
|
|
492
|
+
return rows[0] ?? null;
|
|
493
|
+
}
|
|
494
|
+
async retrieveEmojiList(userID) {
|
|
495
|
+
return this.db
|
|
496
|
+
.selectFrom("emojis")
|
|
497
|
+
.selectAll()
|
|
498
|
+
.where("owner", "=", userID)
|
|
499
|
+
.execute();
|
|
500
|
+
}
|
|
501
|
+
async retrieveFile(fileID) {
|
|
502
|
+
const file = await this.db
|
|
503
|
+
.selectFrom("files")
|
|
504
|
+
.selectAll()
|
|
505
|
+
.where("fileID", "=", fileID)
|
|
506
|
+
.execute();
|
|
507
|
+
return file[0] ?? null;
|
|
508
|
+
}
|
|
509
|
+
async retrieveGroupMembers(channelID) {
|
|
510
|
+
const channel = await this.retrieveChannel(channelID);
|
|
511
|
+
if (!channel) {
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
const permissions = await this.db
|
|
515
|
+
.selectFrom("permissions")
|
|
516
|
+
.selectAll()
|
|
517
|
+
.where("resourceID", "=", channel.serverID)
|
|
518
|
+
.execute();
|
|
519
|
+
const groupMembers = [];
|
|
520
|
+
for (const permission of permissions) {
|
|
521
|
+
const user = await this.retrieveUser(permission.userID);
|
|
522
|
+
if (user) {
|
|
523
|
+
groupMembers.push(user);
|
|
611
524
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
525
|
+
}
|
|
526
|
+
return groupMembers;
|
|
527
|
+
}
|
|
528
|
+
async retrieveInvite(inviteID) {
|
|
529
|
+
const rows = await this.db
|
|
530
|
+
.selectFrom("invites")
|
|
531
|
+
.selectAll()
|
|
532
|
+
.where("inviteID", "=", inviteID)
|
|
533
|
+
.execute();
|
|
534
|
+
return rows[0] ?? null;
|
|
535
|
+
}
|
|
536
|
+
async retrieveMail(deviceID) {
|
|
537
|
+
const rawRows = await this.db
|
|
538
|
+
.selectFrom("mail")
|
|
539
|
+
.selectAll()
|
|
540
|
+
.where("recipient", "=", deviceID)
|
|
541
|
+
.execute();
|
|
542
|
+
const rows = rawRows.map(toMailSQL);
|
|
543
|
+
const fixMail = (mail) => {
|
|
544
|
+
const msgb = {
|
|
545
|
+
authorID: mail.authorID,
|
|
546
|
+
cipher: XUtils.decodeHex(mail.cipher),
|
|
547
|
+
extra: XUtils.decodeHex(mail.extra),
|
|
548
|
+
forward: mail.forward,
|
|
549
|
+
group: mail.group ? XUtils.decodeHex(mail.group) : null,
|
|
619
550
|
mailID: mail.mailID,
|
|
620
551
|
mailType: mail.mailType,
|
|
621
|
-
|
|
622
|
-
sender: deviceID,
|
|
623
|
-
cipher: crypto_1.XUtils.encodeHex(mail.cipher),
|
|
624
|
-
nonce: crypto_1.XUtils.encodeHex(mail.nonce),
|
|
625
|
-
extra: crypto_1.XUtils.encodeHex(mail.extra),
|
|
626
|
-
header: crypto_1.XUtils.encodeHex(header),
|
|
627
|
-
time: new Date(Date.now()),
|
|
628
|
-
group: mail.group ? crypto_1.XUtils.encodeHex(mail.group) : null,
|
|
629
|
-
forward: mail.forward,
|
|
630
|
-
authorID: userID,
|
|
552
|
+
nonce: XUtils.decodeHex(mail.nonce),
|
|
631
553
|
readerID: mail.readerID,
|
|
554
|
+
recipient: mail.recipient,
|
|
555
|
+
sender: mail.sender,
|
|
632
556
|
};
|
|
633
|
-
|
|
634
|
-
|
|
557
|
+
const msgh = XUtils.decodeHex(mail.header);
|
|
558
|
+
return [msgh, msgb, mail.time];
|
|
559
|
+
};
|
|
560
|
+
const allMail = rows.map(fixMail);
|
|
561
|
+
return allMail;
|
|
562
|
+
}
|
|
563
|
+
async retrievePermission(permissionID) {
|
|
564
|
+
const rows = await this.db
|
|
565
|
+
.selectFrom("permissions")
|
|
566
|
+
.selectAll()
|
|
567
|
+
.where("permissionID", "=", permissionID)
|
|
568
|
+
.execute();
|
|
569
|
+
return rows[0] ?? null;
|
|
570
|
+
}
|
|
571
|
+
async retrievePermissions(userID, resourceType) {
|
|
572
|
+
if (resourceType === "all") {
|
|
573
|
+
const sList = await this.db
|
|
574
|
+
.selectFrom("permissions")
|
|
575
|
+
.selectAll()
|
|
576
|
+
.where("userID", "=", userID)
|
|
577
|
+
.execute();
|
|
578
|
+
return sList;
|
|
579
|
+
}
|
|
580
|
+
const serverList = await this.db
|
|
581
|
+
.selectFrom("permissions")
|
|
582
|
+
.selectAll()
|
|
583
|
+
.where("userID", "=", userID)
|
|
584
|
+
.where("resourceType", "=", resourceType)
|
|
585
|
+
.execute();
|
|
586
|
+
return serverList;
|
|
587
|
+
}
|
|
588
|
+
async retrievePermissionsByResourceID(resourceID) {
|
|
589
|
+
return this.db
|
|
590
|
+
.selectFrom("permissions")
|
|
591
|
+
.selectAll()
|
|
592
|
+
.where("resourceID", "=", resourceID)
|
|
593
|
+
.execute();
|
|
594
|
+
}
|
|
595
|
+
async retrieveServer(serverID) {
|
|
596
|
+
const rows = await this.db
|
|
597
|
+
.selectFrom("servers")
|
|
598
|
+
.selectAll()
|
|
599
|
+
.where("serverID", "=", serverID)
|
|
600
|
+
.limit(1)
|
|
601
|
+
.execute();
|
|
602
|
+
const row = rows[0];
|
|
603
|
+
return row ? toServer(row) : null;
|
|
604
|
+
}
|
|
605
|
+
async retrieveServerInvites(serverID) {
|
|
606
|
+
const rows = await this.db
|
|
607
|
+
.selectFrom("invites")
|
|
608
|
+
.selectAll()
|
|
609
|
+
.where("serverID", "=", serverID)
|
|
610
|
+
.execute();
|
|
611
|
+
return rows.filter((invite) => {
|
|
612
|
+
const valid = new Date(Date.now()).getTime() <
|
|
613
|
+
new Date(invite.expiration).getTime();
|
|
614
|
+
if (!valid) {
|
|
615
|
+
void this.deleteInvite(invite.inviteID);
|
|
616
|
+
}
|
|
617
|
+
return valid;
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
async retrieveServers(userID) {
|
|
621
|
+
const serverPerms = await this.retrievePermissions(userID, "server");
|
|
622
|
+
const serverList = [];
|
|
623
|
+
for (const perm of serverPerms) {
|
|
624
|
+
const server = await this.retrieveServer(perm.resourceID);
|
|
625
|
+
if (server) {
|
|
626
|
+
serverList.push(server);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return serverList;
|
|
635
630
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
.
|
|
642
|
-
.
|
|
643
|
-
.where(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
631
|
+
// the identifier can be username, public key, or userID
|
|
632
|
+
async retrieveUser(userIdentifier) {
|
|
633
|
+
let rows;
|
|
634
|
+
if (uuidValidate(userIdentifier)) {
|
|
635
|
+
rows = await this.db
|
|
636
|
+
.selectFrom("users")
|
|
637
|
+
.selectAll()
|
|
638
|
+
.where("userID", "=", userIdentifier)
|
|
639
|
+
.limit(1)
|
|
640
|
+
.execute();
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
rows = await this.db
|
|
644
|
+
.selectFrom("users")
|
|
645
|
+
.selectAll()
|
|
646
|
+
.where("username", "=", userIdentifier)
|
|
647
|
+
.limit(1)
|
|
648
|
+
.execute();
|
|
649
|
+
}
|
|
650
|
+
const row = rows[0];
|
|
651
|
+
return row ? toUserRecord(row) : null;
|
|
652
|
+
}
|
|
653
|
+
async retrieveUserDeviceList(userIDs) {
|
|
654
|
+
const rows = await this.db
|
|
655
|
+
.selectFrom("devices")
|
|
656
|
+
.selectAll()
|
|
657
|
+
.where("owner", "in", userIDs)
|
|
658
|
+
.where("deleted", "=", 0)
|
|
659
|
+
.execute();
|
|
660
|
+
return rows.map(toDevice);
|
|
661
|
+
}
|
|
662
|
+
async retrieveUsers() {
|
|
663
|
+
const rows = await this.db.selectFrom("users").selectAll().execute();
|
|
664
|
+
return rows.map(toUserRecord);
|
|
665
|
+
}
|
|
666
|
+
async saveMail(mail, header, deviceID, userID) {
|
|
667
|
+
const entry = {
|
|
668
|
+
authorID: userID,
|
|
669
|
+
cipher: XUtils.encodeHex(mail.cipher),
|
|
670
|
+
extra: XUtils.encodeHex(mail.extra),
|
|
671
|
+
forward: mail.forward,
|
|
672
|
+
group: mail.group ? XUtils.encodeHex(mail.group) : null,
|
|
673
|
+
header: XUtils.encodeHex(header),
|
|
674
|
+
mailID: mail.mailID,
|
|
675
|
+
mailType: mail.mailType,
|
|
676
|
+
nonce: XUtils.encodeHex(mail.nonce),
|
|
677
|
+
readerID: mail.readerID,
|
|
678
|
+
recipient: mail.recipient,
|
|
679
|
+
sender: deviceID,
|
|
680
|
+
time: new Date().toISOString(),
|
|
681
|
+
};
|
|
682
|
+
await this.db
|
|
683
|
+
.insertInto("mail")
|
|
684
|
+
.values({
|
|
685
|
+
...entry,
|
|
686
|
+
forward: entry.forward ? 1 : 0,
|
|
687
|
+
time: entry.time,
|
|
688
|
+
})
|
|
689
|
+
.execute();
|
|
690
|
+
}
|
|
691
|
+
async saveOTK(userID, deviceID, otks) {
|
|
692
|
+
for (const otk of otks) {
|
|
693
|
+
const newOTK = {
|
|
694
|
+
deviceID: otk.deviceID,
|
|
695
|
+
index: otk.index ?? 0,
|
|
696
|
+
keyID: crypto.randomUUID(),
|
|
697
|
+
publicKey: XUtils.encodeHex(otk.publicKey),
|
|
698
|
+
signature: XUtils.encodeHex(otk.signature),
|
|
699
|
+
userID,
|
|
660
700
|
};
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
deleteMail(nonce, userID) {
|
|
666
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
667
|
-
yield this.db
|
|
668
|
-
.from("mail")
|
|
669
|
-
.delete()
|
|
670
|
-
.where({ nonce: crypto_1.XUtils.encodeHex(nonce), recipient: userID });
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
markUserSeen(user) {
|
|
674
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
675
|
-
yield this.db("users")
|
|
676
|
-
.where({ userID: user.userID })
|
|
677
|
-
.update({
|
|
678
|
-
lastSeen: new Date(Date.now()),
|
|
679
|
-
});
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
markDeviceLogin(device) {
|
|
683
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
684
|
-
yield this.db("devices")
|
|
685
|
-
.where({ deviceID: device.deviceID })
|
|
686
|
-
.update({
|
|
687
|
-
lastLogin: new Date(Date.now()),
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
close() {
|
|
692
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
693
|
-
this.log.info("Closing database.");
|
|
694
|
-
yield this.db.destroy();
|
|
695
|
-
});
|
|
701
|
+
await this.db.insertInto("oneTimeKeys").values(newOTK).execute();
|
|
702
|
+
}
|
|
696
703
|
}
|
|
697
|
-
init() {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
table.string("inviteID").primary();
|
|
702
|
-
table.string("serverID").index();
|
|
703
|
-
table.string("owner");
|
|
704
|
-
table.string("expiration");
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
if (!(yield this.db.schema.hasTable("users"))) {
|
|
708
|
-
yield this.db.schema.createTable("users", (table) => {
|
|
709
|
-
table.string("userID").primary();
|
|
710
|
-
table.string("username").unique();
|
|
711
|
-
table.string("passwordHash");
|
|
712
|
-
table.string("passwordSalt");
|
|
713
|
-
table.dateTime("lastSeen");
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
if (!(yield this.db.schema.hasTable("devices"))) {
|
|
717
|
-
yield this.db.schema.createTable("devices", (table) => {
|
|
718
|
-
table.string("deviceID").primary();
|
|
719
|
-
table.string("signKey").unique();
|
|
720
|
-
table.string("owner");
|
|
721
|
-
table.string("name");
|
|
722
|
-
table.string("lastLogin");
|
|
723
|
-
table.boolean("deleted");
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
if (!(yield this.db.schema.hasTable("mail"))) {
|
|
727
|
-
yield this.db.schema.createTable("mail", (table) => {
|
|
728
|
-
table.string("nonce").primary();
|
|
729
|
-
table.string("recipient").index();
|
|
730
|
-
table.string("mailID");
|
|
731
|
-
table.string("sender");
|
|
732
|
-
table.string("header");
|
|
733
|
-
table.text("cipher", "mediumtext");
|
|
734
|
-
table.string("group");
|
|
735
|
-
table.text("extra");
|
|
736
|
-
table.integer("mailType");
|
|
737
|
-
table.dateTime("time");
|
|
738
|
-
table.boolean("forward");
|
|
739
|
-
table.string("authorID");
|
|
740
|
-
table.string("readerID");
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
if (!(yield this.db.schema.hasTable("preKeys"))) {
|
|
744
|
-
yield this.db.schema.createTable("preKeys", (table) => {
|
|
745
|
-
table.string("keyID").primary();
|
|
746
|
-
table.string("userID").index();
|
|
747
|
-
table
|
|
748
|
-
.string("deviceID")
|
|
749
|
-
.index()
|
|
750
|
-
.unique();
|
|
751
|
-
table.string("publicKey");
|
|
752
|
-
table.string("signature");
|
|
753
|
-
table.integer("index");
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
if (!(yield this.db.schema.hasTable("oneTimeKeys"))) {
|
|
757
|
-
yield this.db.schema.createTable("oneTimeKeys", (table) => {
|
|
758
|
-
table.string("keyID").primary();
|
|
759
|
-
table.string("userID").index();
|
|
760
|
-
table.string("deviceID").index();
|
|
761
|
-
table.string("publicKey");
|
|
762
|
-
table.string("signature");
|
|
763
|
-
table.integer("index");
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
if (!(yield this.db.schema.hasTable("servers"))) {
|
|
767
|
-
yield this.db.schema.createTable("servers", (table) => {
|
|
768
|
-
table.string("serverID").primary();
|
|
769
|
-
table.string("name");
|
|
770
|
-
table.string("icon");
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
if (!(yield this.db.schema.hasTable("channels"))) {
|
|
774
|
-
yield this.db.schema.createTable("channels", (table) => {
|
|
775
|
-
table.string("channelID").primary();
|
|
776
|
-
table.string("serverID");
|
|
777
|
-
table.string("name");
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
if (!(yield this.db.schema.hasTable("permissions"))) {
|
|
781
|
-
yield this.db.schema.createTable("permissions", (table) => {
|
|
782
|
-
table.string("permissionID").primary();
|
|
783
|
-
table.string("userID").index();
|
|
784
|
-
table.string("resourceType");
|
|
785
|
-
table.string("resourceID").index();
|
|
786
|
-
table.integer("powerLevel");
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
if (!(yield this.db.schema.hasTable("files"))) {
|
|
790
|
-
yield this.db.schema.createTable("files", (table) => {
|
|
791
|
-
table.string("fileID").primary();
|
|
792
|
-
table.string("owner").index();
|
|
793
|
-
table.string("nonce");
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
if (!(yield this.db.schema.hasTable("emojis"))) {
|
|
797
|
-
yield this.db.schema.createTable("emojis", (table) => {
|
|
798
|
-
table.string("emojiID").primary();
|
|
799
|
-
table.string("owner").index();
|
|
800
|
-
table.string("name");
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
this.emit("ready");
|
|
704
|
+
async init() {
|
|
705
|
+
const migrator = new Migrator({
|
|
706
|
+
db: this.db,
|
|
707
|
+
provider: new CrossPlatformMigrationProvider(migrationFolder),
|
|
804
708
|
});
|
|
709
|
+
const { error } = await migrator.migrateToLatest();
|
|
710
|
+
if (error) {
|
|
711
|
+
this.emit("error", error);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
this.emit("ready");
|
|
805
715
|
}
|
|
806
716
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
717
|
+
/**
|
|
718
|
+
* Hash a password with Argon2id (new default).
|
|
719
|
+
* Returns the encoded hash string which embeds salt, params, and digest.
|
|
720
|
+
*/
|
|
721
|
+
export async function hashPasswordArgon2(password) {
|
|
722
|
+
return argon2.hash(password, {
|
|
723
|
+
memoryCost: 65536,
|
|
724
|
+
parallelism: 4,
|
|
725
|
+
timeCost: 3,
|
|
726
|
+
type: argon2.argon2id,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Verify a password against either Argon2id or legacy PBKDF2 storage.
|
|
731
|
+
* Returns `{ valid, needsRehash }` — callers should rehash on success
|
|
732
|
+
* when `needsRehash` is true.
|
|
733
|
+
*/
|
|
734
|
+
export async function verifyPassword(password, stored) {
|
|
735
|
+
if (stored.hashAlgo === "argon2id") {
|
|
736
|
+
const valid = await argon2.verify(stored.passwordHash, password);
|
|
737
|
+
return { needsRehash: false, valid };
|
|
738
|
+
}
|
|
739
|
+
// Legacy PBKDF2 path
|
|
740
|
+
const salt = XUtils.decodeHex(stored.passwordSalt);
|
|
741
|
+
const computed = pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, "sha512");
|
|
742
|
+
const storedBuf = XUtils.decodeHex(stored.passwordHash);
|
|
743
|
+
if (computed.length !== storedBuf.length) {
|
|
744
|
+
return { needsRehash: false, valid: false };
|
|
745
|
+
}
|
|
746
|
+
const { timingSafeEqual } = await import("node:crypto");
|
|
747
|
+
const valid = timingSafeEqual(computed, storedBuf);
|
|
748
|
+
return { needsRehash: valid, valid };
|
|
749
|
+
}
|
|
750
|
+
function toDevice(row) {
|
|
751
|
+
return { ...row, deleted: Boolean(row.deleted) };
|
|
752
|
+
}
|
|
753
|
+
function toMailSQL(row) {
|
|
754
|
+
return {
|
|
755
|
+
...row,
|
|
756
|
+
extra: row.extra ?? "",
|
|
757
|
+
forward: Boolean(row.forward),
|
|
758
|
+
mailType: parseMailType(row.mailType),
|
|
759
|
+
time: row.time,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function toServer(row) {
|
|
763
|
+
return {
|
|
764
|
+
icon: row.icon ?? undefined,
|
|
765
|
+
name: row.name,
|
|
766
|
+
serverID: row.serverID,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function toUserRecord(row) {
|
|
770
|
+
return { ...row };
|
|
771
|
+
}
|
|
772
|
+
//# sourceMappingURL=Database.js.map
|