snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,776 @@
# @rocket.chat/omni-core-ee
## 0.0.25
### Patch Changes
- <details><summary>Updated dependencies [4947601bbf042cd1b2385f8f5dda438e608faea7, b89a8d411ef65f6931a5fd1cd057740bc00cd9ba, 5deefe2766132f13118f38dc42cf1c9895208961, 8984df841e1ecc46198e073982adabc1f069c352, a9f3e6cd3f31eac17bec626287fcf815d802e9b9, 742009a09148e33d141f50691cdec0f7e9818535]:</summary>
- @rocket.chat/models@2.5.0
- @rocket.chat/core-services@0.16.0
- @rocket.chat/omni-core@0.1.4
</details>
## 0.0.25-rc.0
### Patch Changes
- <details><summary>Updated dependencies [4947601bbf042cd1b2385f8f5dda438e608faea7, b89a8d411ef65f6931a5fd1cd057740bc00cd9ba, 5deefe2766132f13118f38dc42cf1c9895208961, 8984df841e1ecc46198e073982adabc1f069c352, a9f3e6cd3f31eac17bec626287fcf815d802e9b9, 742009a09148e33d141f50691cdec0f7e9818535]:</summary>
- @rocket.chat/models@2.5.0-rc.0
- @rocket.chat/core-services@0.16.0-rc.0
- @rocket.chat/omni-core@0.1.4-rc.0
</details>
## 0.0.24
### Patch Changes
- <details><summary>Updated dependencies [c7aff48a40a9a78924cbf27fd38930c536ee11e5, 5f92f9a27dca70d506d919351612bd32dc04241a, 13b4a7b2dc203959b77b3b0c5f154d3e34fe2058, 4b34bd62f2ac8d51efd2f48caea7092e87f30ce7, 4b57346a59b5c9433c25845c886be11af1bf71d4, ffe1b646226eeeda5a4d4697c831e568ec1eec64, 3cd7db677a72521439b564dca7a4ca6d6c3a1c07, 719e3db9734708e812ceb33483ffaa2e064b4d59, 1cc7bbdef9330899a8207d3d55130f48321d68f1]:</summary>
- @rocket.chat/models@2.4.0
- @rocket.chat/core-services@0.15.0
- @rocket.chat/omni-core@0.1.3
</details>
## 0.0.24-rc.0
### Patch Changes
- <details><summary>Updated dependencies [c7aff48a40a9a78924cbf27fd38930c536ee11e5, 5f92f9a27dca70d506d919351612bd32dc04241a, 13b4a7b2dc203959b77b3b0c5f154d3e34fe2058, 4b34bd62f2ac8d51efd2f48caea7092e87f30ce7, 4b57346a59b5c9433c25845c886be11af1bf71d4, ffe1b646226eeeda5a4d4697c831e568ec1eec64, 3cd7db677a72521439b564dca7a4ca6d6c3a1c07, 719e3db9734708e812ceb33483ffaa2e064b4d59, 1cc7bbdef9330899a8207d3d55130f48321d68f1]:</summary>
- @rocket.chat/models@2.4.0-rc.0
- @rocket.chat/core-services@0.15.0-rc.0
- @rocket.chat/omni-core@0.1.3-rc.0
</details>
## 0.0.23
### Patch Changes
- <details><summary>Updated dependencies [73e12e1707baea845395e0582892f65456598672, a7279cebc73edfa4b991eb593730c08e8f5e9001, 7380c44c751eff9ee624d80bf26370411ffed78b]:</summary>
- @rocket.chat/models@2.3.1
- @rocket.chat/core-services@0.14.2
- @rocket.chat/omni-core@0.1.2
</details>
## 0.0.23-rc.0
### Patch Changes
- <details><summary>Updated dependencies [73e12e1707baea845395e0582892f65456598672, a7279cebc73edfa4b991eb593730c08e8f5e9001, 7380c44c751eff9ee624d80bf26370411ffed78b]:</summary>
- @rocket.chat/models@2.3.1-rc.0
- @rocket.chat/core-services@0.14.2-rc.0
- @rocket.chat/omni-core@0.1.2-rc.0
</details>
## 0.0.22
### Patch Changes
- <details><summary>Updated dependencies [f7d47dd3517ec14ca2ec5c3c95fcdf9e1e2fb8b0, b6b04aadfcc8558f888b334e37c46a77e5816237, 4704bf81ca370f120af32185a7c55407a26f8514, 12897e25d0dc25b7373f5264d38f38a5a7444257, e45585b70a3a7b75434c88e4b2ea9af0a0764a76]:</summary>
- @rocket.chat/models@2.3.0
- @rocket.chat/core-services@0.14.1
- @rocket.chat/omni-core@0.1.1
</details>
## 0.0.22-rc.0
### Patch Changes
- <details><summary>Updated dependencies [f7d47dd3517ec14ca2ec5c3c95fcdf9e1e2fb8b0, ae9f740d6af20557eac61b4af902c868b4132b49, b6b04aadfcc8558f888b334e37c46a77e5816237, 4704bf81ca370f120af32185a7c55407a26f8514, 12897e25d0dc25b7373f5264d38f38a5a7444257, e45585b70a3a7b75434c88e4b2ea9af0a0764a76]:</summary>
- @rocket.chat/models@2.3.0-rc.0
- @rocket.chat/core-services@0.14.1-rc.0
- @rocket.chat/omni-core@0.1.1-rc.0
</details>
## 0.0.21
### Patch Changes
- <details><summary>Updated dependencies [21cd54f87de5837fe6c999a44bd15be34b9fe905, 278b84f78360e53792a2e5d7620615039a0e15e9, 12c44d2db65af9f90c741da621164f5738fc19f5, 9713af36f5c0d673f2d2093015f322341706bab0, f3fa3ee2f2e53b777de9abd466f1b76a1ec1b96c, c544b805d1c03f8eae9d061cd48838206207a7c9, 8c0e16ca29b393cfa50b425520db48ba5a74f678, f4dfb8ddc2049692371aeb084110b5768151b5df]:</summary>
- @rocket.chat/core-services@0.14.0
- @rocket.chat/models@2.2.0
- @rocket.chat/omni-core@0.1.0
</details>
## 0.0.21-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.14.0-rc.2
- @rocket.chat/models@2.2.0-rc.2
- @rocket.chat/omni-core@0.1.0-rc.2
</details>
## 0.0.21-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.14.0-rc.1
- @rocket.chat/models@2.2.0-rc.1
- @rocket.chat/omni-core@0.1.0-rc.1
</details>
## 0.0.20
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.4
- @rocket.chat/models@2.1.4
- @rocket.chat/omni-core@0.0.20
</details>
## 0.0.19
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.3
- @rocket.chat/models@2.1.3
- @rocket.chat/omni-core@0.0.19
</details>
## 0.0.21-rc.0
### Patch Changes
- <details><summary>Updated dependencies [21cd54f87de5837fe6c999a44bd15be34b9fe905, 278b84f78360e53792a2e5d7620615039a0e15e9, 12c44d2db65af9f90c741da621164f5738fc19f5, 9713af36f5c0d673f2d2093015f322341706bab0, f3fa3ee2f2e53b777de9abd466f1b76a1ec1b96c, c544b805d1c03f8eae9d061cd48838206207a7c9, 8c0e16ca29b393cfa50b425520db48ba5a74f678, f4dfb8ddc2049692371aeb084110b5768151b5df]:</summary>
- @rocket.chat/core-services@0.14.0-rc.0
- @rocket.chat/models@2.2.0-rc.0
- @rocket.chat/omni-core@0.1.0-rc.0
</details>
## 0.0.18
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
- <details><summary>Updated dependencies [602b20a8c570b895eb296ecfe39c9b7fcb12fabd, e2068892bf1ffc88b15ab71ad743cf84e5d31ed5, cd2fc208d351032c0b729755af4886665dca08b6, 539659af22bc19880eda047dfc0b152472ccb65c, eae3fb3136bd0b48294c050a71b0a36d05ca02b0]:</summary>
- @rocket.chat/models@2.1.2
- @rocket.chat/core-services@0.13.2
- @rocket.chat/omni-core@0.0.18
- @rocket.chat/logger@1.0.1
</details>
## 0.0.18-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.2-rc.4
- @rocket.chat/models@2.1.2-rc.4
- @rocket.chat/omni-core@0.0.18-rc.4
</details>
## 0.0.18-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.2-rc.3
- @rocket.chat/models@2.1.2-rc.3
- @rocket.chat/omni-core@0.0.18-rc.3
</details>
## 0.0.18-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.2-rc.2
- @rocket.chat/models@2.1.2-rc.2
- @rocket.chat/omni-core@0.0.18-rc.2
</details>
## 0.0.18-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.2-rc.1
- @rocket.chat/models@2.1.2-rc.1
- @rocket.chat/omni-core@0.0.18-rc.1
</details>
## 0.0.18-rc.0
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
- <details><summary>Updated dependencies [602b20a8c570b895eb296ecfe39c9b7fcb12fabd, e2068892bf1ffc88b15ab71ad743cf84e5d31ed5, cd2fc208d351032c0b729755af4886665dca08b6, 539659af22bc19880eda047dfc0b152472ccb65c, eae3fb3136bd0b48294c050a71b0a36d05ca02b0]:</summary>
- @rocket.chat/models@2.1.2-rc.0
- @rocket.chat/core-services@0.13.2-rc.0
- @rocket.chat/omni-core@0.0.18-rc.0
- @rocket.chat/logger@1.0.1-rc.0
</details>
## 0.0.17
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.1
- @rocket.chat/models@2.1.1
- @rocket.chat/omni-core@0.0.17
</details>
## 0.0.16
### Patch Changes
- <details><summary>Updated dependencies [d3758a7d57ab602745369ef9d2ccdbf9271cf305, e57f15845e4df048dd2f08f11aa08215780a2c34, 11e1c51f0867a35c69ce9b6eeca25dbbe2c71872, 3b003e6b69c11b280d55bcc8db2f3e4ae7a4a573, 87faec13b3c0efc3e85627f9b70c4561b7231416, 508b4a17d76dc1cd7d3a55bdba826216f51432e2, 123aebec2caa74b17d2b5dcbd2a2db2e687cf3ac]:</summary>
- @rocket.chat/models@2.1.0
- @rocket.chat/core-services@0.13.0
- @rocket.chat/omni-core@0.0.16
</details>
## 0.0.16-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.0-rc.2
- @rocket.chat/models@2.1.0-rc.2
- @rocket.chat/omni-core@0.0.16-rc.2
</details>
## 0.0.16-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.13.0-rc.1
- @rocket.chat/models@2.1.0-rc.1
- @rocket.chat/omni-core@0.0.16-rc.1
</details>
## 0.0.16-rc.0
### Patch Changes
- <details><summary>Updated dependencies [d3758a7d57ab602745369ef9d2ccdbf9271cf305, e57f15845e4df048dd2f08f11aa08215780a2c34, 11e1c51f0867a35c69ce9b6eeca25dbbe2c71872, 3b003e6b69c11b280d55bcc8db2f3e4ae7a4a573, 87faec13b3c0efc3e85627f9b70c4561b7231416, 508b4a17d76dc1cd7d3a55bdba826216f51432e2, 123aebec2caa74b17d2b5dcbd2a2db2e687cf3ac]:</summary>
- @rocket.chat/models@2.1.0-rc.0
- @rocket.chat/core-services@0.13.0-rc.0
- @rocket.chat/omni-core@0.0.16-rc.0
</details>
## 0.0.15
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.3
- @rocket.chat/models@2.0.3
- @rocket.chat/omni-core@0.0.15
</details>
## 0.0.14
### Patch Changes
- <details><summary>Updated dependencies [f4fa393fccb6abefbe6fb6550563e8ca21fc792d, 7b51ba84340480fa28e85c8f35d9d2f947d3250e, 020dfbcab6a940ca2e03d802d0f6b54714a34aa2, 5fa150953b86ff36face25083ed49e8c97a8044d, 05c415b94cb91907de39a39c6d277579258f334e]:</summary>
- @rocket.chat/models@2.0.2
- @rocket.chat/core-services@0.12.2
- @rocket.chat/omni-core@0.0.14
</details>
## 0.0.14-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.2-rc.2
- @rocket.chat/models@2.0.2-rc.2
- @rocket.chat/omni-core@0.0.14-rc.2
</details>
## 0.0.14-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.1-rc.1
- @rocket.chat/models@2.0.1-rc.1
- @rocket.chat/omni-core@0.0.13-rc.1
</details>
## 0.0.14-rc.0
### Patch Changes
- <details><summary>Updated dependencies [f4fa393fccb6abefbe6fb6550563e8ca21fc792d, 7b51ba84340480fa28e85c8f35d9d2f947d3250e, 020dfbcab6a940ca2e03d802d0f6b54714a34aa2, 5fa150953b86ff36face25083ed49e8c97a8044d, 05c415b94cb91907de39a39c6d277579258f334e]:</summary>
- @rocket.chat/models@2.0.1-rc.0
- @rocket.chat/core-services@0.12.1-rc.0
- @rocket.chat/omni-core@0.0.13-rc.0
</details>
## 0.0.13
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.1
- @rocket.chat/models@2.0.1
- @rocket.chat/omni-core@0.0.13
</details>
## 0.0.12
### Patch Changes
- <details><summary>Updated dependencies [ac11ea05ffadeca978c794ff38d5199d9acb2c29, 1baa03cced8f94584da1224ad59cad86f219707a, ac11ea05ffadeca978c794ff38d5199d9acb2c29, ddc935727e9a7275813006d9dcaa7fe866610844, 73d9eb2783176954f42aa2cbeda8abf1d49ac260, be80b724a636877294b5e5baa501d070941131dd, 4aa3634186d97f4144c39f6b42a65107d3d30df0, ec0f8b435dd12c218adffa8892737c7ced4debb8]:</summary>
- @rocket.chat/models@2.0.0
- @rocket.chat/core-services@0.12.0
- @rocket.chat/logger@1.0.0
- @rocket.chat/omni-core@0.0.12
</details>
## 0.0.12-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.0-rc.5
- @rocket.chat/models@2.0.0-rc.5
- @rocket.chat/omni-core@0.0.12-rc.5
</details>
## 0.0.12-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.0-rc.4
- @rocket.chat/models@2.0.0-rc.4
- @rocket.chat/omni-core@0.0.12-rc.4
</details>
## 0.0.12-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.0-rc.3
- @rocket.chat/models@2.0.0-rc.3
- @rocket.chat/omni-core@0.0.12-rc.3
</details>
## 0.0.12-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.0-rc.2
- @rocket.chat/models@2.0.0-rc.2
- @rocket.chat/omni-core@0.0.12-rc.2
</details>
## 0.0.12-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.12.0-rc.1
- @rocket.chat/models@2.0.0-rc.1
- @rocket.chat/omni-core@0.0.12-rc.1
</details>
## 0.0.12-rc.0
### Patch Changes
- <details><summary>Updated dependencies [ac11ea05ffadeca978c794ff38d5199d9acb2c29, 1baa03cced8f94584da1224ad59cad86f219707a, ac11ea05ffadeca978c794ff38d5199d9acb2c29, ddc935727e9a7275813006d9dcaa7fe866610844, 73d9eb2783176954f42aa2cbeda8abf1d49ac260, be80b724a636877294b5e5baa501d070941131dd, 4aa3634186d97f4144c39f6b42a65107d3d30df0, ec0f8b435dd12c218adffa8892737c7ced4debb8]:</summary>
- @rocket.chat/models@2.0.0-rc.0
- @rocket.chat/core-services@0.12.0-rc.0
- @rocket.chat/logger@1.0.0-rc.0
- @rocket.chat/omni-core@0.0.12-rc.0
</details>
## 0.0.11
### Patch Changes
- <details><summary>Updated dependencies [eef2b390a8e4664ad4b974e7ea579982245efce5]:</summary>
- @rocket.chat/core-services@0.11.2
- @rocket.chat/models@1.8.2
- @rocket.chat/omni-core@0.0.11
</details>
## 0.0.10
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.11.1
- @rocket.chat/models@1.8.1
- @rocket.chat/omni-core@0.0.10
</details>
## 0.0.9
### Patch Changes
- <details><summary>Updated dependencies [8596daf01ac84864caa63dd937971e557933d401, 83642cbe499ba399329449fb8cd652a0405c0795, 7f1b834a55b1240c226afde77713262da47f45dc]:</summary>
- @rocket.chat/core-services@0.11.0
- @rocket.chat/models@1.8.0
- @rocket.chat/omni-core@0.0.9
</details>
## 0.0.9-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.11.0-rc.2
- @rocket.chat/models@1.8.0-rc.2
- @rocket.chat/omni-core@0.0.9-rc.2
</details>
## 0.0.9-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.11.0-rc.1
- @rocket.chat/models@1.8.0-rc.1
- @rocket.chat/omni-core@0.0.7-rc.1
</details>
## 0.0.9-rc.0
### Patch Changes
- <details><summary>Updated dependencies [8596daf01ac84864caa63dd937971e557933d401, 83642cbe499ba399329449fb8cd652a0405c0795, 7f1b834a55b1240c226afde77713262da47f45dc]:</summary>
- @rocket.chat/core-services@0.11.0-rc.0
- @rocket.chat/models@1.8.0-rc.0
- @rocket.chat/omni-core@0.0.7-rc.0
</details>
## 0.0.8
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.6
- @rocket.chat/models@1.7.3
- @rocket.chat/omni-core@0.0.8
</details>
## 0.0.7
### Patch Changes
- <details><summary>Updated dependencies [8cbd9bfd0566cbd7d86d8b40ea7d58d6ef382742]:</summary>
- @rocket.chat/models@1.7.2
- @rocket.chat/core-services@0.10.5
- @rocket.chat/omni-core@0.0.7
</details>
## 0.0.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4
- @rocket.chat/models@1.7.1
- @rocket.chat/omni-core@0.0.6
</details>
## 0.0.6-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4-rc.4
- @rocket.chat/models@1.7.1-rc.4
- @rocket.chat/omni-core@0.0.6-rc.4
</details>
## 0.0.6-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4-rc.3
- @rocket.chat/models@1.7.1-rc.3
- @rocket.chat/omni-core@0.0.6-rc.3
</details>
## 0.0.6-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4-rc.2
- @rocket.chat/models@1.7.1-rc.2
- @rocket.chat/omni-core@0.0.6-rc.2
</details>
## 0.0.6-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4-rc.1
- @rocket.chat/models@1.7.1-rc.1
- @rocket.chat/omni-core@0.0.6-rc.1
</details>
## 0.0.6-rc.0
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.4-rc.0
- @rocket.chat/models@1.7.1-rc.0
- @rocket.chat/omni-core@0.0.6-rc.0
</details>
## 0.0.5
### Patch Changes
- <details><summary>Updated dependencies [c102712222cc18c050f45f9279f8eafcff3e7e8a, 3e177dbd0b65d70bce8587287ba73b30170eb0f7, 3dfb557f723a395bc1e00f2647675968c46656bc]:</summary>
- @rocket.chat/models@1.7.0
- @rocket.chat/core-services@0.10.3
- @rocket.chat/omni-core@0.0.5
</details>
## 0.0.5-rc.7
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.3-rc.7
- @rocket.chat/models@1.7.0-rc.7
- @rocket.chat/omni-core@0.0.5-rc.7
</details>
## 0.0.5-rc.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.2-rc.6
- @rocket.chat/models@1.7.0-rc.6
- @rocket.chat/omni-core@0.0.4-rc.6
</details>
## 0.0.4-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.2-rc.5
- @rocket.chat/models@1.7.0-rc.5
- @rocket.chat/omni-core@0.0.4-rc.5
</details>
## 0.0.4-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.2-rc.4
- @rocket.chat/models@1.7.0-rc.4
- @rocket.chat/omni-core@0.0.4-rc.4
</details>
## 0.0.4-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.2-rc.3
- @rocket.chat/models@1.7.0-rc.3
- @rocket.chat/omni-core@0.0.4-rc.3
</details>
## 0.0.4-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.1-rc.2
- @rocket.chat/models@1.7.0-rc.2
- @rocket.chat/omni-core@0.0.3-rc.2
</details>
## 0.0.4-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/core-services@0.10.1-rc.1
- @rocket.chat/models@1.7.0-rc.1
- @rocket.chat/omni-core@0.0.3-rc.1
</details>
## 0.0.4-rc.0
### Patch Changes
- <details><summary>Updated dependencies [c102712222cc18c050f45f9279f8eafcff3e7e8a, 3e177dbd0b65d70bce8587287ba73b30170eb0f7, 3dfb557f723a395bc1e00f2647675968c46656bc]:</summary>
- @rocket.chat/models@1.7.0-rc.0
- @rocket.chat/core-services@0.10.1-rc.0
- @rocket.chat/omni-core@0.0.3-rc.0
</details>
## 0.0.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.2
- @rocket.chat/omni-core@0.0.4
</details>
## 0.0.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.1
- @rocket.chat/omni-core@0.0.3
</details>
## 0.0.2
### Patch Changes
- <details><summary>Updated dependencies [b25f05acd07762387fa45d67a1241b982c192f5d, fd32867fd4949bc2951a22075498ccb551cc6bbc, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]:</summary>
- @rocket.chat/models@1.6.0
- @rocket.chat/omni-core@0.0.2
</details>
## 0.0.2-rc.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.6
- @rocket.chat/omni-core@0.0.2-rc.6
</details>
## 0.0.2-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.5
- @rocket.chat/omni-core@0.0.2-rc.5
</details>
## 0.0.2-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.4
- @rocket.chat/omni-core@0.0.2-rc.4
</details>
## 0.0.2-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.3
- @rocket.chat/omni-core@0.0.2-rc.3
</details>
## 0.0.2-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.2
- @rocket.chat/omni-core@0.0.2-rc.2
</details>
## 0.0.2-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.1
- @rocket.chat/omni-core@0.0.2-rc.1
</details>
## 0.0.2-rc.0
### Patch Changes
- <details><summary>Updated dependencies [b25f05acd07762387fa45d67a1241b982c192f5d, fd32867fd4949bc2951a22075498ccb551cc6bbc, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]:</summary>
- @rocket.chat/models@1.6.0-rc.0
- @rocket.chat/omni-core@0.0.2-rc.0
</details>

View File

@@ -0,0 +1,6 @@
import server from '@rocket.chat/jest-presets/server';
import type { Config } from 'jest';
export default {
preset: server.preset,
} satisfies Config;

View File

@@ -0,0 +1,38 @@
{
"name": "@rocket.chat/omni-core-ee",
"version": "0.0.25",
"private": true,
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
"/dist"
],
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.json",
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": "jest",
"testunit": "jest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@rocket.chat/core-services": "workspace:^",
"@rocket.chat/logger": "workspace:^",
"@rocket.chat/models": "workspace:^",
"@rocket.chat/omni-core": "workspace:^",
"mem": "^8.1.1",
"mongodb": "6.16.0"
},
"devDependencies": {
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/tsconfig": "workspace:*",
"@types/jest": "~30.0.0",
"eslint": "~9.39.5",
"jest": "~30.2.0",
"typescript": "~5.9.3"
},
"volta": {
"extends": "../../../package.json"
}
}

View File

@@ -0,0 +1,10 @@
import { isDepartmentCreationAvailablePatch } from './isDepartmentCreationAvailable';
import { applyDepartmentRestrictionsPatch } from './patches/applyDepartmentRestrictions';
export function patchOmniCore(): void {
isDepartmentCreationAvailablePatch();
applyDepartmentRestrictionsPatch();
}
export * from './outbound-communication';
export * from './units/getUnitsFromUser';

View File

@@ -0,0 +1,45 @@
import { License, MockedLicenseBuilder } from '@rocket.chat/license';
import type { ILivechatDepartmentModel } from '@rocket.chat/model-typings';
import { registerModel } from '@rocket.chat/models';
import { isDepartmentCreationAvailable } from '@rocket.chat/omni-core';
import { isDepartmentCreationAvailablePatch } from './isDepartmentCreationAvailable';
beforeEach(async () => {
await License.setWorkspaceUrl('http://localhost:3000');
License.setLicenseLimitCounter('activeUsers', () => 0);
License.setLicenseLimitCounter('guestUsers', () => 0);
License.setLicenseLimitCounter('roomsPerGuest', async () => 0);
License.setLicenseLimitCounter('privateApps', () => 0);
License.setLicenseLimitCounter('marketplaceApps', () => 0);
License.setLicenseLimitCounter('monthlyActiveContacts', async () => 0);
License.setLicenseLimitCounter('activeUsers', () => 1);
});
beforeAll(async () => {
isDepartmentCreationAvailablePatch();
registerModel('ILivechatDepartmentModel', { countTotal: () => 1 } as unknown as ILivechatDepartmentModel);
});
it('should skip the check if Livechat Enterprise is enabled', async () => {
const license = new MockedLicenseBuilder();
license.withGrantedModules(['livechat-enterprise']);
await License.setLicense(await license.sign());
const isAvailable = await isDepartmentCreationAvailable();
expect(isAvailable).toBe(true);
});
it('should not skip the check if Livechat Enterprise is not enabled', async () => {
const license = new MockedLicenseBuilder();
license.withGrantedModules([]);
await License.setLicense(await license.sign());
const isAvailable = await isDepartmentCreationAvailable();
expect(isAvailable).toBe(false);
});

View File

@@ -0,0 +1,13 @@
import { License } from '@rocket.chat/license';
import { isDepartmentCreationAvailable } from '@rocket.chat/omni-core';
export function isDepartmentCreationAvailablePatch(): void {
isDepartmentCreationAvailable.patch(async (next) => {
// Skip the standard check when Livechat Enterprise is enabled, as it allows unlimited departments
if (License.hasModule('livechat-enterprise')) {
return true;
}
return next();
});
}

View File

@@ -0,0 +1 @@
export * from './validators/canSendMessage';

View File

@@ -0,0 +1,190 @@
import { Authorization } from '@rocket.chat/core-services';
import { LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models';
import { canSendOutboundMessage, validateAgentAssignPermissions } from './canSendMessage';
jest.mock('@rocket.chat/core-services', () => ({
Authorization: {
hasPermission: jest.fn(),
},
}));
jest.mock('@rocket.chat/models', () => ({
LivechatDepartment: {
findOne: jest.fn(),
},
LivechatDepartmentAgents: {
findOneByAgentIdAndDepartmentId: jest.fn(),
},
Users: {
findOneAgentById: jest.fn(),
},
}));
const hasPermissionMock = Authorization.hasPermission as unknown as jest.Mock<
Promise<boolean>,
[userId: string, permission: string, scope?: string]
>;
const findDepartmentMock = LivechatDepartment.findOne as unknown as jest.Mock;
const findDepartmentAgentMock = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId as unknown as jest.Mock;
const findUserAgentMock = Users.findOneAgentById as unknown as jest.Mock;
const setPermissions = (map: Record<string, boolean>) => {
hasPermissionMock.mockImplementation(async (_userId: string, permission: string) => {
return Boolean(map[permission]);
});
};
describe('canSendOutboundMessage', () => {
beforeEach(() => {
jest.clearAllMocks();
// Default: no permissions granted
setPermissions({});
});
describe('when departmentId is provided', () => {
test('resolves when user has assign-queues permission and department is enabled', async () => {
setPermissions({ 'outbound.can-assign-queues': true });
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: true });
await expect(canSendOutboundMessage('u1', undefined, 'dep1')).resolves.toBeUndefined();
// Should not need to check requester department membership
expect(findDepartmentAgentMock).not.toHaveBeenCalled();
});
test('throws error-invalid-department when department is disabled', async () => {
setPermissions({ 'outbound.can-assign-queues': true });
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: false });
await expect(canSendOutboundMessage('u1', undefined, 'dep1')).rejects.toThrow('error-invalid-department');
});
describe('and agentId is provided', () => {
test('throws error-invalid-agent if user lacks any-agent and has self-only but agentId != userId', async () => {
setPermissions({
'outbound.can-assign-queues': true, // so it skips requester membership
'outbound.can-assign-any-agent': false,
'outbound.can-assign-self-only': true,
});
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: true });
await expect(canSendOutboundMessage('me', 'other', 'dep1')).rejects.toThrow('error-invalid-agent');
// Should not check selected agent membership after permission denial
expect(findDepartmentAgentMock).not.toHaveBeenCalled();
});
test('throws error-agent-not-in-department if selected agent is not in the department (any-agent allowed)', async () => {
setPermissions({
'outbound.can-assign-queues': true,
'outbound.can-assign-any-agent': true,
});
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: true });
findDepartmentAgentMock.mockResolvedValueOnce(null); // selected agent not in department
await expect(canSendOutboundMessage('me', 'agentX', 'dep1')).rejects.toThrow('error-agent-not-in-department');
expect(findDepartmentAgentMock).toHaveBeenCalledWith('agentX', 'dep1', { projection: { _id: 1 } });
});
test('resolves if selected agent is in the department (any-agent allowed)', async () => {
setPermissions({
'outbound.can-assign-queues': true,
'outbound.can-assign-any-agent': true,
});
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: true });
findDepartmentAgentMock.mockResolvedValueOnce({ _id: 'relX' });
await expect(canSendOutboundMessage('me', 'agentInDep', 'dep1')).resolves.toBeUndefined();
});
test('throws error-agent-not-in-department when self-only is allowed and agentId == userId but agent is not in department', async () => {
setPermissions({
'outbound.can-assign-queues': true,
'outbound.can-assign-any-agent': false,
'outbound.can-assign-self-only': true,
});
findDepartmentMock.mockResolvedValue({ _id: 'dep1', enabled: true });
findDepartmentAgentMock.mockResolvedValueOnce(null); // self not in department
await expect(canSendOutboundMessage('me', 'me', 'dep1')).rejects.toThrow('error-agent-not-in-department');
});
});
});
describe('when departmentId is not provided and agentId is provided', () => {
test('throws error-invalid-agent if user lacks any-agent and has self-only but agentId != userId', async () => {
setPermissions({
'outbound.can-assign-any-agent': false,
'outbound.can-assign-self-only': true,
});
await expect(canSendOutboundMessage('me', 'other')).rejects.toThrow('error-invalid-agent');
// Should not query Users if permission check fails
expect(findUserAgentMock).not.toHaveBeenCalled();
});
test('throws error-invalid-agent if selected agent is not an agent (any-agent allowed)', async () => {
setPermissions({
'outbound.can-assign-any-agent': true,
});
findUserAgentMock.mockResolvedValueOnce(null);
await expect(canSendOutboundMessage('me', 'notAnAgent')).rejects.toThrow('error-invalid-agent');
expect(findUserAgentMock).toHaveBeenCalledWith('notAnAgent', { projection: { _id: 1 } });
});
test('resolves if selected agent exists (any-agent allowed)', async () => {
setPermissions({
'outbound.can-assign-any-agent': true,
});
findUserAgentMock.mockResolvedValueOnce({ _id: 'agent1' });
await expect(canSendOutboundMessage('me', 'agent1')).resolves.toBeUndefined();
});
});
test('resolves when neither departmentId nor agentId are provided', async () => {
await expect(canSendOutboundMessage('u1')).resolves.toBeUndefined();
expect(hasPermissionMock).not.toHaveBeenCalled();
expect(findDepartmentMock).not.toHaveBeenCalled();
expect(findDepartmentAgentMock).not.toHaveBeenCalled();
expect(findUserAgentMock).not.toHaveBeenCalled();
});
});
describe('validateAgentAssignPermissions', () => {
beforeEach(() => {
jest.clearAllMocks();
setPermissions({});
});
test('resolves when user has any-agent permission', async () => {
setPermissions({ 'outbound.can-assign-any-agent': true });
await expect(validateAgentAssignPermissions('u1', 'other')).resolves.toBeUndefined();
});
test('resolves when user has self-only and agentId equals userId', async () => {
setPermissions({ 'outbound.can-assign-self-only': true });
await expect(validateAgentAssignPermissions('me', 'me')).resolves.toBeUndefined();
});
test('throws error-invalid-agent when self-only and agentId != userId', async () => {
setPermissions({ 'outbound.can-assign-self-only': true });
await expect(validateAgentAssignPermissions('me', 'other')).rejects.toThrow('error-invalid-agent');
});
test('throws error-invalid-agent when user lacks both permissions', async () => {
setPermissions({});
await expect(validateAgentAssignPermissions('me', 'other')).rejects.toThrow('error-invalid-agent');
});
});

View File

@@ -0,0 +1,53 @@
import { Authorization } from '@rocket.chat/core-services';
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import { LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models';
import type { FilterOperators } from 'mongodb';
import { addQueryRestrictionsToDepartmentsModel } from '../../units/addRoleBasedRestrictionsToDepartment';
export async function validateAgentAssignPermissions(userId: string, agentId: string): Promise<void> {
if (await Authorization.hasPermission(userId, 'outbound.can-assign-any-agent')) {
return;
}
if ((await Authorization.hasPermission(userId, 'outbound.can-assign-self-only')) && agentId === userId) {
return;
}
throw new Error('error-invalid-agent');
}
export async function canSendOutboundMessage(userId: string, agentId?: string, departmentId?: string): Promise<void> {
// Case 1: Check department and check if agent is in department
if (departmentId) {
let query: FilterOperators<ILivechatDepartment> = { _id: departmentId };
if (!(await Authorization.hasPermission(userId, 'outbound.can-assign-queues'))) {
query = await addQueryRestrictionsToDepartmentsModel(query, userId);
}
const department = await LivechatDepartment.findOne<Pick<ILivechatDepartment, '_id' | 'enabled'>>(query, {
projection: { _id: 1, enabled: 1 },
});
if (!department?.enabled) {
throw new Error('error-invalid-department');
}
// Case 2: Agent & department: if agent is present, agent must be in department
if (agentId) {
await validateAgentAssignPermissions(userId, agentId);
// On here, we take a shortcut: if the user is here, we assume it's an agent (and we assume the collection is kept up to date :) )
const agent = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(agentId, departmentId, { projection: { _id: 1 } });
if (!agent) {
throw new Error('error-agent-not-in-department');
}
}
// Case 3: Agent & no department: if agent is present and there's no department, agent must be an agent
} else if (agentId) {
await validateAgentAssignPermissions(userId, agentId);
const agent = await Users.findOneAgentById(agentId, { projection: { _id: 1 } });
if (!agent) {
throw new Error('error-invalid-agent');
}
}
}

View File

@@ -0,0 +1,24 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { applyDepartmentRestrictions } from '@rocket.chat/omni-core';
import type { FilterOperators } from 'mongodb';
import { addQueryRestrictionsToDepartmentsModel } from '../units/addRoleBasedRestrictionsToDepartment';
import { hooksLogger } from '../utils/logger';
export const applyDepartmentRestrictionsPatch = () => {
applyDepartmentRestrictions.patch(
async (
prev: (query: FilterOperators<ILivechatDepartment>, userId: string) => FilterOperators<ILivechatDepartment>,
query: FilterOperators<ILivechatDepartment> = {},
userId: string,
) => {
if (!License.hasModule('livechat-enterprise')) {
return prev(query, userId);
}
hooksLogger.debug('Applying department query restrictions');
return addQueryRestrictionsToDepartmentsModel(query, userId);
},
);
};

View File

@@ -0,0 +1,297 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { FilterOperators } from 'mongodb';
import { addQueryRestrictionsToDepartmentsModel } from './addRoleBasedRestrictionsToDepartment';
import { getUnitsFromUser } from './getUnitsFromUser';
import { defaultLogger } from '../utils/logger';
// Mock dependencies
jest.mock('./getUnitsFromUser');
jest.mock('../utils/logger');
const mockedGetUnitsFromUser = jest.mocked(getUnitsFromUser);
const mockedLogger = jest.mocked(defaultLogger);
describe('addQueryRestrictionsToDepartmentsModel', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('when getUnitsFromUser returns an array of units', () => {
it('should add unit restrictions to the query', async () => {
// Arrange
const userId = 'user123';
const units = ['unit1', 'unit2', 'unit3'];
const originalQuery: FilterOperators<ILivechatDepartment> = { name: 'test-department' };
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [
{ name: 'test-department' },
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: ['unit1', 'unit2', 'unit3'] } }, { _id: { $in: ['unit1', 'unit2', 'unit3'] } }],
},
],
});
expect(mockedGetUnitsFromUser).toHaveBeenCalledWith(userId);
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId,
units,
});
});
it('should work with empty original query', async () => {
// Arrange
const userId = 'user456';
const units = ['unit4', 'unit5'];
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel({}, userId);
// Assert
expect(result).toEqual({
$and: [
{},
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: ['unit4', 'unit5'] } }, { _id: { $in: ['unit4', 'unit5'] } }],
},
],
});
});
it('should work with undefined original query', async () => {
// Arrange
const userId = 'user789';
const units = ['unit6'];
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(undefined, userId);
// Assert
expect(result).toEqual({
$and: [
{},
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: ['unit6'] } }, { _id: { $in: ['unit6'] } }],
},
],
});
});
it('should handle single unit in array', async () => {
// Arrange
const userId = 'user101';
const units = ['single-unit'];
const originalQuery: FilterOperators<ILivechatDepartment> = { enabled: true };
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [
{ enabled: true },
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: ['single-unit'] } }, { _id: { $in: ['single-unit'] } }],
},
],
});
});
});
describe('when getUnitsFromUser returns non-array values', () => {
it('should not add unit restrictions when getUnitsFromUser returns null', async () => {
// Arrange
const userId = 'user202';
const originalQuery: FilterOperators<ILivechatDepartment> = { name: 'test' };
mockedGetUnitsFromUser.mockResolvedValue(undefined);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [{ name: 'test' }, { type: { $ne: 'u' } }],
});
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId,
units: undefined,
});
});
it('should not add unit restrictions when getUnitsFromUser returns undefined', async () => {
// Arrange
const userId = 'user303';
const originalQuery: FilterOperators<ILivechatDepartment> = { active: true };
mockedGetUnitsFromUser.mockResolvedValue(undefined);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [{ active: true }, { type: { $ne: 'u' } }],
});
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId,
units: undefined,
});
});
it('should not add unit restrictions when getUnitsFromUser returns a string', async () => {
// Arrange
const userId = 'user404';
const originalQuery: FilterOperators<ILivechatDepartment> = {};
mockedGetUnitsFromUser.mockResolvedValue('not-an-array' as any);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [{}, { type: { $ne: 'u' } }],
});
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId,
units: 'not-an-array',
});
});
it('should not add unit restrictions when getUnitsFromUser returns empty array', async () => {
// Arrange
const userId = 'user505';
const originalQuery: FilterOperators<ILivechatDepartment> = { department: 'support' };
mockedGetUnitsFromUser.mockResolvedValue([]);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [
{ department: 'support' },
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: [] } }, { _id: { $in: [] } }],
},
],
});
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId,
units: [],
});
});
});
describe('error handling', () => {
it('should propagate errors from getUnitsFromUser', async () => {
// Arrange
const userId = 'user606';
const error = new Error('Database connection failed');
mockedGetUnitsFromUser.mockRejectedValue(error);
// Act & Assert
await expect(addQueryRestrictionsToDepartmentsModel({}, userId)).rejects.toThrow('Database connection failed');
expect(mockedGetUnitsFromUser).toHaveBeenCalledWith(userId);
expect(mockedLogger.debug).not.toHaveBeenCalled();
});
});
describe('complex query scenarios', () => {
it('should handle complex original query with nested conditions', async () => {
// Arrange
const userId = 'user707';
const units = ['unit-a', 'unit-b'];
const originalQuery: FilterOperators<ILivechatDepartment> = {
$or: [{ name: { $regex: 'support' } }, { enabled: true }],
createdAt: { $gte: new Date('2023-01-01') },
};
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result).toEqual({
$and: [
{
$or: [{ name: { $regex: 'support' } }, { enabled: true }],
createdAt: { $gte: new Date('2023-01-01') },
},
{ type: { $ne: 'u' } },
{
$or: [{ ancestors: { $in: ['unit-a', 'unit-b'] } }, { _id: { $in: ['unit-a', 'unit-b'] } }],
},
],
});
});
it('should always exclude departments with type "u"', async () => {
// Arrange
const userId = 'user808';
const units = ['unit-x'];
const originalQuery: FilterOperators<ILivechatDepartment> = { type: 'normal' };
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
// Assert
expect(result.$and).toContainEqual({ type: { $ne: 'u' } });
});
});
describe('logging', () => {
it('should always call debug logger with correct parameters', async () => {
// Arrange
const userId = 'user909';
const units = ['unit-test'];
mockedGetUnitsFromUser.mockResolvedValue(units);
// Act
await addQueryRestrictionsToDepartmentsModel({}, userId);
// Assert
expect(mockedLogger.debug).toHaveBeenCalledTimes(1);
expect(mockedLogger.debug).toHaveBeenCalledWith({
msg: 'Applying department query restrictions',
userId: 'user909',
units: ['unit-test'],
});
});
});
});

View File

@@ -0,0 +1,17 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { FilterOperators } from 'mongodb';
import { getUnitsFromUser } from './getUnitsFromUser';
import { defaultLogger } from '../utils/logger';
export const addQueryRestrictionsToDepartmentsModel = async (originalQuery: FilterOperators<ILivechatDepartment> = {}, userId: string) => {
const query: FilterOperators<ILivechatDepartment> = { $and: [originalQuery, { type: { $ne: 'u' } }] };
const units = await getUnitsFromUser(userId);
if (Array.isArray(units)) {
query.$and.push({ $or: [{ ancestors: { $in: units } }, { _id: { $in: units } }] });
}
defaultLogger.debug({ msg: 'Applying department query restrictions', userId, units });
return query;
};

View File

@@ -0,0 +1,230 @@
import { Authorization } from '@rocket.chat/core-services';
import { LivechatUnit, LivechatDepartmentAgents } from '@rocket.chat/models';
import { getUnitsFromUser } from './getUnitsFromUser';
import { defaultLogger } from '../utils/logger';
// Mock the dependencies
jest.mock('@rocket.chat/core-services', () => ({
Authorization: {
hasAnyRole: jest.fn(),
},
}));
jest.mock('@rocket.chat/models', () => ({
LivechatUnit: {
findByMonitorId: jest.fn(),
countUnits: jest.fn(),
},
LivechatDepartmentAgents: {
findByAgentId: jest.fn(),
},
}));
jest.mock('mem', () => (fn: any) => fn);
jest.mock('../utils/logger');
const mockAuthorization = Authorization as jest.Mocked<typeof Authorization>;
const mockLivechatUnit = LivechatUnit as jest.Mocked<typeof LivechatUnit>;
const mockLivechatDepartmentAgents = LivechatDepartmentAgents as jest.Mocked<typeof LivechatDepartmentAgents>;
const mockLogger = defaultLogger as jest.Mocked<typeof defaultLogger>;
describe('getUnitsFromUser', () => {
beforeEach(() => {
jest.resetAllMocks();
// Setup default mock implementations
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]),
} as any);
mockLivechatUnit.countUnits.mockResolvedValue(5);
mockAuthorization.hasAnyRole.mockResolvedValue(false);
mockLogger.debug.mockImplementation(() => {
//
});
});
describe('when userId is not provided', () => {
it('should return undefined for null userId', async () => {
const result = await getUnitsFromUser(null as any);
expect(result).toBeUndefined();
});
it('should return undefined for undefined userId', async () => {
const result = await getUnitsFromUser(undefined);
expect(result).toBeUndefined();
});
it('should return undefined for empty string userId', async () => {
const result = await getUnitsFromUser('');
expect(result).toBeUndefined();
});
});
describe('when there are no units in the system', () => {
it('should return undefined', async () => {
mockLivechatUnit.countUnits.mockResolvedValue(0);
const result = await getUnitsFromUser('user123');
expect(result).toBeUndefined();
expect(mockLivechatUnit.countUnits).toHaveBeenCalled();
});
});
describe('when user has admin role', () => {
it('should return undefined for admin users', async () => {
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(true) // admin/livechat-manager check
.mockResolvedValueOnce(false); // livechat-monitor/agent check
const result = await getUnitsFromUser('admin-user');
expect(mockLivechatUnit.countUnits).toHaveBeenCalled();
expect(result).toBeUndefined();
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('admin-user', ['admin', 'livechat-manager']);
});
});
describe('when user does not have required roles', () => {
it('should return undefined for users without livechat-monitor or livechat-agent roles', async () => {
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(false); // livechat-monitor/agent check
const result = await getUnitsFromUser('regular-user');
expect(result).toBeUndefined();
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('regular-user', ['admin', 'livechat-manager']);
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('regular-user', ['livechat-monitor', 'livechat-agent']);
});
});
describe('when user has livechat-manager role', () => {
it('should return undefined for livechat-manager users', async () => {
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(true) // admin/livechat-manager check
.mockResolvedValueOnce(false); // livechat-monitor/agent check
const result = await getUnitsFromUser('manager-user');
expect(result).toBeUndefined();
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('manager-user', ['admin', 'livechat-manager']);
});
});
describe('when user has livechat-monitor role', () => {
it('should return combined units and departments', async () => {
const userId = 'monitor-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual(['unit1', 'unit2', 'dept1', 'dept2']);
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId);
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId);
expect(mockLogger.debug).toHaveBeenCalledWith({
msg: 'Calculating units for monitor',
user: userId,
unitsAndDepartments: ['unit1', 'unit2', 'dept1', 'dept2'],
});
});
});
describe('when user has livechat-agent role', () => {
it('should return combined units and departments', async () => {
const userId = 'agent-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit3']);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept3' }, { departmentId: 'dept4' }]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual(['unit3', 'dept3', 'dept4']);
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId);
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId);
});
});
describe('edge cases', () => {
it('should handle empty units and departments arrays', async () => {
const userId = 'empty-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue([]);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual([]);
});
it('should handle when only units are returned', async () => {
const userId = 'units-only-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual(['unit1', 'unit2']);
});
it('should handle when only departments are returned', async () => {
const userId = 'departments-only-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue([]);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual(['dept1', 'dept2']);
});
});
describe('memoization', () => {
it('should work correctly with memoization disabled in tests', async () => {
const userId = 'test-user';
mockAuthorization.hasAnyRole
.mockResolvedValueOnce(false) // admin/livechat-manager check
.mockResolvedValueOnce(true); // livechat-monitor/agent check
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1']);
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }]),
} as any);
const result = await getUnitsFromUser(userId);
expect(result).toEqual(['unit1', 'dept1']);
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId);
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId);
});
});
});

View File

@@ -0,0 +1,48 @@
import { Authorization } from '@rocket.chat/core-services';
import { LivechatUnit, LivechatDepartmentAgents } from '@rocket.chat/models';
import mem from 'mem';
import { defaultLogger } from '../utils/logger';
async function getUnitsFromUserRoles(user: string): Promise<string[]> {
return LivechatUnit.findByMonitorId(user);
}
async function getDepartmentsFromUserRoles(user: string): Promise<string[]> {
return (await LivechatDepartmentAgents.findByAgentId(user).toArray()).map((department) => department.departmentId);
}
const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 });
const memoizedGetDepartmentsFromUserRoles = mem(getDepartmentsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 });
async function hasUnits(): Promise<boolean> {
// @ts-expect-error - this prop is injected dynamically on ee license
return (await LivechatUnit.countUnits({ type: 'u' })) > 0;
}
// Units should't change really often, so we can cache the result
const memoizedHasUnits = mem(hasUnits, { maxAge: process.env.TEST_MODE ? 1 : 10000 });
export const getUnitsFromUser = async (userId?: string): Promise<string[] | undefined> => {
if (!userId) {
return;
}
if (!(await memoizedHasUnits())) {
return;
}
// TODO: we can combine these 2 calls into one single query
if (await Authorization.hasAnyRole(userId, ['admin', 'livechat-manager'])) {
return;
}
if (!(await Authorization.hasAnyRole(userId, ['livechat-monitor', 'livechat-agent']))) {
return;
}
const unitsAndDepartments = [...(await memoizedGetUnitFromUserRoles(userId)), ...(await memoizedGetDepartmentsFromUserRoles(userId))];
defaultLogger.debug({ msg: 'Calculating units for monitor', user: userId, unitsAndDepartments });
return unitsAndDepartments;
};

View File

@@ -0,0 +1,4 @@
import { Logger } from '@rocket.chat/logger';
export const defaultLogger = new Logger('OmniCore-ee');
export const hooksLogger = defaultLogger.section('hooks');

View File

@@ -0,0 +1,9 @@
{
"extends": "@rocket.chat/tsconfig/server.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"declaration": true
},
"include": ["./src/**/*"],
}