diff --git a/.changeset/afraid-parents-bake.md b/.changeset/afraid-parents-bake.md
new file mode 100644
index 0000000000000..af2b8a10d4899
--- /dev/null
+++ b/.changeset/afraid-parents-bake.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/rest-typings': minor
+'@rocket.chat/meteor': minor
+---
+
+REST endpoint `/v1/users.createToken` is not deprecated anymore. It now requires a `secret` parameter to generate a token for a user. This change is part of the effort to enhance security by ensuring that tokens are generated with an additional layer of validation. The `secret` parameter is validated against a new environment variable `CREATE_TOKENS_FOR_USERS_SECRET`.
diff --git a/.changeset/beige-seahorses-reply.md b/.changeset/beige-seahorses-reply.md
new file mode 100644
index 0000000000000..bf87249b57e07
--- /dev/null
+++ b/.changeset/beige-seahorses-reply.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/web-ui-registration": major
+---
+
+Removes the deprecated sendConfirmationEmail method
diff --git a/.changeset/blue-beans-check.md b/.changeset/blue-beans-check.md
new file mode 100644
index 0000000000000..f1d66153e3f1f
--- /dev/null
+++ b/.changeset/blue-beans-check.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:removeUnit` method
diff --git a/.changeset/breezy-timers-flow.md b/.changeset/breezy-timers-flow.md
new file mode 100644
index 0000000000000..5894d387b05de
--- /dev/null
+++ b/.changeset/breezy-timers-flow.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes the deprecated meteor method: `livechat:saveTag`
diff --git a/.changeset/brown-carrots-bathe.md b/.changeset/brown-carrots-bathe.md
new file mode 100644
index 0000000000000..9e39e63edc9b9
--- /dev/null
+++ b/.changeset/brown-carrots-bathe.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/meteor': patch
+'@rocket.chat/rest-typings': patch
+---
+
+Adds deprecation warning for `livechat:removeMonitor` and new endpoint replacing it; `livechat/monitor.remove`
diff --git a/.changeset/brown-llamas-worry.md b/.changeset/brown-llamas-worry.md
new file mode 100644
index 0000000000000..9cbf74ba7317f
--- /dev/null
+++ b/.changeset/brown-llamas-worry.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/rest-typings": patch
+---
+
+Adds a deprecation warning for `livechat:saveBusinessHour` and new endpoint replacing it; `livechat/business-hours.save`
diff --git a/.changeset/bump-patch-1766456337926.md b/.changeset/bump-patch-1766456337926.md
new file mode 100644
index 0000000000000..e1eaa7980afb1
--- /dev/null
+++ b/.changeset/bump-patch-1766456337926.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/bump-patch-1767191875055.md b/.changeset/bump-patch-1767191875055.md
new file mode 100644
index 0000000000000..e1eaa7980afb1
--- /dev/null
+++ b/.changeset/bump-patch-1767191875055.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/bump-patch-1767789797202.md b/.changeset/bump-patch-1767789797202.md
new file mode 100644
index 0000000000000..e1eaa7980afb1
--- /dev/null
+++ b/.changeset/bump-patch-1767789797202.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/bump-patch-1767922007435.md b/.changeset/bump-patch-1767922007435.md
new file mode 100644
index 0000000000000..e1eaa7980afb1
--- /dev/null
+++ b/.changeset/bump-patch-1767922007435.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/bump-patch-1768188248094.md b/.changeset/bump-patch-1768188248094.md
new file mode 100644
index 0000000000000..e1eaa7980afb1
--- /dev/null
+++ b/.changeset/bump-patch-1768188248094.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/chatty-dingos-bathe.md b/.changeset/chatty-dingos-bathe.md
new file mode 100644
index 0000000000000..e09c5e1c667e0
--- /dev/null
+++ b/.changeset/chatty-dingos-bathe.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/apps-engine': minor
+'@rocket.chat/apps': minor
+'@rocket.chat/meteor': minor
+---
+
+Adds a new method to the Apps-Engine that allows apps to retrieve multiple rooms from database
diff --git a/.changeset/chatty-lizards-reflect.md b/.changeset/chatty-lizards-reflect.md
new file mode 100644
index 0000000000000..17f269cb70a20
--- /dev/null
+++ b/.changeset/chatty-lizards-reflect.md
@@ -0,0 +1,13 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/ddp-client": major
+---
+
+Removes the deprecated livechat:getTagsList method
+
+Removes the deprecated livechat:getUnitsFromUser method
+
+Removes the deprecated livechat:getFirstRoomMessage method
+
+Removes the deprecated livechat:getDepartmentForwardRestrictions method
+
diff --git a/.changeset/chatty-roses-help.md b/.changeset/chatty-roses-help.md
new file mode 100644
index 0000000000000..3c4a5849c8daa
--- /dev/null
+++ b/.changeset/chatty-roses-help.md
@@ -0,0 +1,7 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/core-typings": patch
+"@rocket.chat/i18n": patch
+---
+
+Adds invitation request support to rooms
diff --git a/.changeset/chilly-cobras-look.md b/.changeset/chilly-cobras-look.md
new file mode 100644
index 0000000000000..9e7bba192bba2
--- /dev/null
+++ b/.changeset/chilly-cobras-look.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/i18n': major
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated VoIP permissions
diff --git a/.changeset/cold-chairs-taste.md b/.changeset/cold-chairs-taste.md
new file mode 100644
index 0000000000000..e12a576569534
--- /dev/null
+++ b/.changeset/cold-chairs-taste.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue where its not being possible to change the password in account security page
diff --git a/.changeset/cold-chefs-rhyme.md b/.changeset/cold-chefs-rhyme.md
new file mode 100644
index 0000000000000..5dc1bbdc43c81
--- /dev/null
+++ b/.changeset/cold-chefs-rhyme.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Disables read receipts indicators in federated rooms. This feature will be re-enabled when fully compatible with federation.
diff --git a/.changeset/cool-turtles-bathe.md b/.changeset/cool-turtles-bathe.md
new file mode 100644
index 0000000000000..ab3f8b80285c6
--- /dev/null
+++ b/.changeset/cool-turtles-bathe.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/meteor': major
+'@rocket.chat/media-calls': minor
+'@rocket.chat/i18n': minor
+---
+
+Makes Voice Calls enabled by default when available
diff --git a/.changeset/cuddly-eels-perform.md b/.changeset/cuddly-eels-perform.md
new file mode 100644
index 0000000000000..bc99f3c96e302
--- /dev/null
+++ b/.changeset/cuddly-eels-perform.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes members tab > add members not removing selected items
diff --git a/.changeset/curly-bats-wink.md b/.changeset/curly-bats-wink.md
new file mode 100644
index 0000000000000..79bd2e7e37918
--- /dev/null
+++ b/.changeset/curly-bats-wink.md
@@ -0,0 +1,16 @@
+---
+'@rocket.chat/model-typings': major
+'@rocket.chat/core-typings': major
+'@rocket.chat/rest-typings': major
+'@rocket.chat/ui-contexts': major
+'@rocket.chat/ui-voip': major
+'@rocket.chat/models': major
+'@rocket.chat/i18n': major
+'@rocket.chat/meteor': major
+'@rocket.chat/apps-engine': minor
+'@rocket.chat/core-services': minor
+'@rocket.chat/message-types': minor
+'@rocket.chat/ddp-client': minor
+---
+
+Removes deprecated VoIP from Omnichannel
diff --git a/.changeset/cyan-mayflies-juggle.md b/.changeset/cyan-mayflies-juggle.md
new file mode 100644
index 0000000000000..593798b63ba1c
--- /dev/null
+++ b/.changeset/cyan-mayflies-juggle.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:saveUnit` method
diff --git a/.changeset/dull-rabbits-add.md b/.changeset/dull-rabbits-add.md
new file mode 100644
index 0000000000000..1cfd27a2c7701
--- /dev/null
+++ b/.changeset/dull-rabbits-add.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `authorization:removeUserFromRole` method
diff --git a/.changeset/dull-tips-look.md b/.changeset/dull-tips-look.md
new file mode 100644
index 0000000000000..8867fc650dc12
--- /dev/null
+++ b/.changeset/dull-tips-look.md
@@ -0,0 +1,7 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/core-services": patch
+"@rocket.chat/federation-matrix": patch
+---
+
+Fixes an issue where cases of invites that were canceled or disinvited were not being handled.
diff --git a/.changeset/empty-buses-walk.md b/.changeset/empty-buses-walk.md
new file mode 100644
index 0000000000000..c24e5acce300e
--- /dev/null
+++ b/.changeset/empty-buses-walk.md
@@ -0,0 +1,8 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/gazzodown": major
+"@rocket.chat/i18n": major
+"@rocket.chat/ui-client": major
+---
+
+Promotes Timestamp Parser from preview state to stable
diff --git a/.changeset/fair-dryers-behave.md b/.changeset/fair-dryers-behave.md
new file mode 100644
index 0000000000000..a9c57c42042e3
--- /dev/null
+++ b/.changeset/fair-dryers-behave.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/rest-typings': major
+'@rocket.chat/meteor': major
+---
+
+Removes ecdh functionality and related settings
diff --git a/.changeset/fast-ligers-unite.md b/.changeset/fast-ligers-unite.md
new file mode 100644
index 0000000000000..eacb88108a0f7
--- /dev/null
+++ b/.changeset/fast-ligers-unite.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates)
diff --git a/.changeset/fast-walls-eat.md b/.changeset/fast-walls-eat.md
new file mode 100644
index 0000000000000..1c82cd4c32513
--- /dev/null
+++ b/.changeset/fast-walls-eat.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes deprecated meteor method `unmuteUserInRoom`
diff --git a/.changeset/few-masks-punch.md b/.changeset/few-masks-punch.md
new file mode 100644
index 0000000000000..cf455b251bd8d
--- /dev/null
+++ b/.changeset/few-masks-punch.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated `canAccessRoom` meteor method
diff --git a/.changeset/fifty-rice-smash.md b/.changeset/fifty-rice-smash.md
new file mode 100644
index 0000000000000..51ca36cdc84d4
--- /dev/null
+++ b/.changeset/fifty-rice-smash.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated Realtime API method: `livechat:getAnalyticsChartData`
diff --git a/.changeset/five-months-shake.md b/.changeset/five-months-shake.md
new file mode 100644
index 0000000000000..b694672c84358
--- /dev/null
+++ b/.changeset/five-months-shake.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:returnAsInquiry` method
diff --git a/.changeset/forty-pears-divide.md b/.changeset/forty-pears-divide.md
new file mode 100644
index 0000000000000..b7c9c57357aff
--- /dev/null
+++ b/.changeset/forty-pears-divide.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes deprecated method `livechat:removeBusinessHour`
diff --git a/.changeset/forty-spiders-sneeze.md b/.changeset/forty-spiders-sneeze.md
new file mode 100644
index 0000000000000..f09f83231e92d
--- /dev/null
+++ b/.changeset/forty-spiders-sneeze.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `removeCustomField` method
diff --git a/.changeset/four-timers-enjoy.md b/.changeset/four-timers-enjoy.md
new file mode 100644
index 0000000000000..8ce7068fa7bb1
--- /dev/null
+++ b/.changeset/four-timers-enjoy.md
@@ -0,0 +1,11 @@
+---
+'@rocket.chat/model-typings': major
+'@rocket.chat/core-typings': major
+'@rocket.chat/rest-typings': major
+'@rocket.chat/models': major
+'@rocket.chat/i18n': major
+'@rocket.chat/meteor': major
+'@rocket.chat/core-services': minor
+---
+
+Removes Deprecated FreeSwitch integration
diff --git a/.changeset/fresh-tables-raise.md b/.changeset/fresh-tables-raise.md
new file mode 100644
index 0000000000000..89978df7f5421
--- /dev/null
+++ b/.changeset/fresh-tables-raise.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes the deprecated `getUserRoles` method in favor of the `/v1/roles.getUsersInPublicRoles` endpoint.
diff --git a/.changeset/fuzzy-plants-hammer.md b/.changeset/fuzzy-plants-hammer.md
new file mode 100644
index 0000000000000..5b4f0a647f889
--- /dev/null
+++ b/.changeset/fuzzy-plants-hammer.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': minor
+---
+
+Changes the position of the buttons in Unique ID change detected modal in order to highlight configuration update instead of new workspace
diff --git a/.changeset/fuzzy-teachers-juggle.md b/.changeset/fuzzy-teachers-juggle.md
new file mode 100644
index 0000000000000..fce7559bbcb6a
--- /dev/null
+++ b/.changeset/fuzzy-teachers-juggle.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/mock-providers': patch
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue that could cause slashcommands to disappear for the user in certain high-availability scenarios
diff --git a/.changeset/gentle-dingos-retire.md b/.changeset/gentle-dingos-retire.md
new file mode 100644
index 0000000000000..526facc864e23
--- /dev/null
+++ b/.changeset/gentle-dingos-retire.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `setAdminStatus` method
diff --git a/.changeset/gold-keys-compare.md b/.changeset/gold-keys-compare.md
new file mode 100644
index 0000000000000..452169781c04c
--- /dev/null
+++ b/.changeset/gold-keys-compare.md
@@ -0,0 +1,4 @@
+---
+"@rocket.chat/meteor": major
+---
+Removes support of MongoDB versions 5.x and 6.x
diff --git a/.changeset/gold-zoos-sneeze.md b/.changeset/gold-zoos-sneeze.md
new file mode 100644
index 0000000000000..a38ff5c5fb604
--- /dev/null
+++ b/.changeset/gold-zoos-sneeze.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated meteor method `muteUserInRoom`
diff --git a/.changeset/good-files-accept.md b/.changeset/good-files-accept.md
new file mode 100644
index 0000000000000..ccc6ce33f5b25
--- /dev/null
+++ b/.changeset/good-files-accept.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes the deprecated method `createToken`
diff --git a/.changeset/grumpy-colts-collect.md b/.changeset/grumpy-colts-collect.md
new file mode 100644
index 0000000000000..9406052d0b132
--- /dev/null
+++ b/.changeset/grumpy-colts-collect.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue where custom status is not updating immediately if the value is empty
diff --git a/.changeset/grumpy-readers-give.md b/.changeset/grumpy-readers-give.md
new file mode 100644
index 0000000000000..085e9e3d88cc0
--- /dev/null
+++ b/.changeset/grumpy-readers-give.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/apps-engine': patch
+'@rocket.chat/meteor': patch
+---
+
+Adds an execution flag to apps-engine runtime that helps prevent the publishing of faulty builds
diff --git a/.changeset/happy-carpets-draw.md b/.changeset/happy-carpets-draw.md
new file mode 100644
index 0000000000000..f59a1a06c7bbf
--- /dev/null
+++ b/.changeset/happy-carpets-draw.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/ui-client': major
+'@rocket.chat/i18n': major
+'@rocket.chat/meteor': major
+---
+
+Promotes Resizable Contextualbars from preview state to stable.
diff --git a/.changeset/happy-news-reflect.md b/.changeset/happy-news-reflect.md
new file mode 100644
index 0000000000000..c69eb08eacdd1
--- /dev/null
+++ b/.changeset/happy-news-reflect.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/fuselage-ui-kit": patch
+---
+
+Fixes an issue with the `action` block inside `Info Card` ui-kit element not dispatching actions.
diff --git a/.changeset/honest-knives-cough.md b/.changeset/honest-knives-cough.md
new file mode 100644
index 0000000000000..2329bec3a251a
--- /dev/null
+++ b/.changeset/honest-knives-cough.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `setReaction` meteor method
diff --git a/.changeset/hungry-fans-wait.md b/.changeset/hungry-fans-wait.md
new file mode 100644
index 0000000000000..7d8594f3470aa
--- /dev/null
+++ b/.changeset/hungry-fans-wait.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': minor
+---
+
+Validates attachment fields to require `title` and `value` properties on APIs `chat.postMessage` and `chat.sendMessage`.
diff --git a/.changeset/large-jobs-smell.md b/.changeset/large-jobs-smell.md
new file mode 100644
index 0000000000000..a04338844742b
--- /dev/null
+++ b/.changeset/large-jobs-smell.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue with the build that prevented Deno runtime to run on air-gapped environments
diff --git a/.changeset/large-planes-destroy.md b/.changeset/large-planes-destroy.md
new file mode 100644
index 0000000000000..e2541e1df68f5
--- /dev/null
+++ b/.changeset/large-planes-destroy.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Fixes create channel modal not validating federated access permission
diff --git a/.changeset/lazy-pianos-care.md b/.changeset/lazy-pianos-care.md
new file mode 100644
index 0000000000000..d5afd4cb714af
--- /dev/null
+++ b/.changeset/lazy-pianos-care.md
@@ -0,0 +1,9 @@
+---
+'@rocket.chat/model-typings': minor
+'@rocket.chat/core-typings': minor
+'@rocket.chat/rest-typings': minor
+'@rocket.chat/models': minor
+'@rocket.chat/meteor': minor
+---
+
+Enhance user's deactivated state handling to correctly distinguish between pending and deactivated users.
diff --git a/.changeset/lemon-garlics-check.md b/.changeset/lemon-garlics-check.md
new file mode 100644
index 0000000000000..6216aaf24dd9a
--- /dev/null
+++ b/.changeset/lemon-garlics-check.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Fixes license add-on validations for federated rooms
diff --git a/.changeset/long-swans-sin.md b/.changeset/long-swans-sin.md
new file mode 100644
index 0000000000000..b5dd7a9c16b4c
--- /dev/null
+++ b/.changeset/long-swans-sin.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Changes the HTTP code of `/api/v1/method.call` and `/api/v1/method.callAnon` in case of internal errors
diff --git a/.changeset/loud-elephants-happen.md b/.changeset/loud-elephants-happen.md
new file mode 100644
index 0000000000000..0f505ff2445dd
--- /dev/null
+++ b/.changeset/loud-elephants-happen.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/media-signaling': patch
+---
+
+Fixes an issue where the client would play the Call Ended sound effect when the user started calling someone on a different session
diff --git a/.changeset/many-walls-cheat.md b/.changeset/many-walls-cheat.md
new file mode 100644
index 0000000000000..190539f09b1f2
--- /dev/null
+++ b/.changeset/many-walls-cheat.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/fuselage-ui-kit": minor
+"@rocket.chat/ui-kit": minor
+---
+
+Introduces a new variation of the `Icon` element to `ui-kit` through the new `framed` optional property.
diff --git a/.changeset/many-walls-impress.md b/.changeset/many-walls-impress.md
new file mode 100644
index 0000000000000..058723b41d421
--- /dev/null
+++ b/.changeset/many-walls-impress.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/rest-typings": patch
+---
+
+Adds deprecation warning on `livechat:addMonitor` with new endpoint replacing it; `livechat/monitors.create`
diff --git a/.changeset/many-windows-perform.md b/.changeset/many-windows-perform.md
new file mode 100644
index 0000000000000..d84dce497937c
--- /dev/null
+++ b/.changeset/many-windows-perform.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated Realtime API method: `livechat:closeRoom`
diff --git a/.changeset/metal-moose-travel.md b/.changeset/metal-moose-travel.md
new file mode 100644
index 0000000000000..71c2f66f5e02c
--- /dev/null
+++ b/.changeset/metal-moose-travel.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes a condition where the `SAML_Custom_Default_default_user_role` setting, used to define the default SAML role when none is provided, would fail when a role name was used instead of an ID.
diff --git a/.changeset/metal-rocks-behave.md b/.changeset/metal-rocks-behave.md
new file mode 100644
index 0000000000000..f6db936b32968
--- /dev/null
+++ b/.changeset/metal-rocks-behave.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes deprecated method `saveUserProfile`
diff --git a/.changeset/nasty-beans-breathe.md b/.changeset/nasty-beans-breathe.md
new file mode 100644
index 0000000000000..8ba5361d40462
--- /dev/null
+++ b/.changeset/nasty-beans-breathe.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `setCustomFields` method
diff --git a/.changeset/nasty-moons-speak.md b/.changeset/nasty-moons-speak.md
new file mode 100644
index 0000000000000..d709bd58c2021
--- /dev/null
+++ b/.changeset/nasty-moons-speak.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Adds invitation badge to room members list
diff --git a/.changeset/neat-spoons-cover.md b/.changeset/neat-spoons-cover.md
new file mode 100644
index 0000000000000..ed5941e748a69
--- /dev/null
+++ b/.changeset/neat-spoons-cover.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated Realtime API method: `livechat:removeAllClosedRooms`
diff --git a/.changeset/nervous-doors-knock.md b/.changeset/nervous-doors-knock.md
new file mode 100644
index 0000000000000..aa4084d7b8ded
--- /dev/null
+++ b/.changeset/nervous-doors-knock.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `getAvatarSuggestion` method
diff --git a/.changeset/nervous-melons-cough.md b/.changeset/nervous-melons-cough.md
new file mode 100644
index 0000000000000..870cdfacdff37
--- /dev/null
+++ b/.changeset/nervous-melons-cough.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes the deprecated meteor method: `livechat:addMonitor`
diff --git a/.changeset/nervous-wombats-look.md b/.changeset/nervous-wombats-look.md
new file mode 100644
index 0000000000000..6a3baaea2f50c
--- /dev/null
+++ b/.changeset/nervous-wombats-look.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:saveDepartment` method
diff --git a/.changeset/ninety-dodos-confess.md b/.changeset/ninety-dodos-confess.md
new file mode 100644
index 0000000000000..b85badf29d0bf
--- /dev/null
+++ b/.changeset/ninety-dodos-confess.md
@@ -0,0 +1,15 @@
+---
+'@rocket.chat/authorization-service': minor
+'@rocket.chat/core-services': minor
+'@rocket.chat/message-types': minor
+'@rocket.chat/model-typings': minor
+'@rocket.chat/core-typings': minor
+'@rocket.chat/apps-engine': minor
+'@rocket.chat/abac': minor
+'@rocket.chat/models': minor
+'@rocket.chat/i18n': minor
+'@rocket.chat/jwt': minor
+'@rocket.chat/meteor': minor
+---
+
+Adds Attribute Based Access Control (ABAC) for private channels & private teams.
diff --git a/.changeset/ninety-hats-swim.md b/.changeset/ninety-hats-swim.md
new file mode 100644
index 0000000000000..43ea75a82d383
--- /dev/null
+++ b/.changeset/ninety-hats-swim.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:setUpConnection` method
diff --git a/.changeset/odd-pigs-hang.md b/.changeset/odd-pigs-hang.md
new file mode 100644
index 0000000000000..b3db5fdbdafaf
--- /dev/null
+++ b/.changeset/odd-pigs-hang.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue where iframe external commands sent via `window.postMessage` were not being handled correctly when Rocket.Chat was embedded inside an iframe.
diff --git a/.changeset/olive-pens-think.md b/.changeset/olive-pens-think.md
new file mode 100644
index 0000000000000..994b14dad0833
--- /dev/null
+++ b/.changeset/olive-pens-think.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes incorrect URL generation in Global Search "Jump to message" feature, resolving navigation issues when jumping to messages across different channels.
diff --git a/.changeset/orange-poets-marry.md b/.changeset/orange-poets-marry.md
new file mode 100644
index 0000000000000..8da7abd288b76
--- /dev/null
+++ b/.changeset/orange-poets-marry.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `getRoomRoles` method
diff --git a/.changeset/pink-months-compare.md b/.changeset/pink-months-compare.md
new file mode 100644
index 0000000000000..27e345823957a
--- /dev/null
+++ b/.changeset/pink-months-compare.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `authorization:deleteRole` method
diff --git a/.changeset/plenty-flowers-help.md b/.changeset/plenty-flowers-help.md
new file mode 100644
index 0000000000000..65f178303d5cc
--- /dev/null
+++ b/.changeset/plenty-flowers-help.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:removeRoom` method
diff --git a/.changeset/poor-apricots-heal.md b/.changeset/poor-apricots-heal.md
new file mode 100644
index 0000000000000..420ed96068488
--- /dev/null
+++ b/.changeset/poor-apricots-heal.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes push notifications continuing after logout due to missing token cleanup.
diff --git a/.changeset/poor-trains-mate.md b/.changeset/poor-trains-mate.md
new file mode 100644
index 0000000000000..90b7882bab726
--- /dev/null
+++ b/.changeset/poor-trains-mate.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `appId` parameter from the `oauth-apps.get` endpoint.
diff --git a/.changeset/popular-items-smash.md b/.changeset/popular-items-smash.md
new file mode 100644
index 0000000000000..84316444911ba
--- /dev/null
+++ b/.changeset/popular-items-smash.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `sendFileLivechatMessage` method
diff --git a/.changeset/pre.json b/.changeset/pre.json
new file mode 100644
index 0000000000000..1363127f09ead
--- /dev/null
+++ b/.changeset/pre.json
@@ -0,0 +1,201 @@
+{
+ "mode": "pre",
+ "tag": "rc",
+ "initialVersions": {
+ "@rocket.chat/meteor": "8.0.0-develop",
+ "rocketchat-services": "2.0.37",
+ "@rocket.chat/uikit-playground": "0.7.2",
+ "@rocket.chat/account-service": "0.4.46",
+ "@rocket.chat/authorization-service": "0.4.46",
+ "@rocket.chat/ddp-streamer": "0.3.46",
+ "@rocket.chat/omnichannel-transcript": "0.4.46",
+ "@rocket.chat/presence-service": "0.4.46",
+ "@rocket.chat/queue-worker": "0.4.46",
+ "@rocket.chat/abac": "0.0.1",
+ "@rocket.chat/federation-matrix": "0.0.8",
+ "@rocket.chat/license": "1.1.6",
+ "@rocket.chat/media-calls": "0.1.2",
+ "@rocket.chat/network-broker": "0.2.25",
+ "@rocket.chat/omni-core-ee": "0.0.11",
+ "@rocket.chat/omnichannel-services": "0.3.43",
+ "@rocket.chat/pdf-worker": "0.3.25",
+ "@rocket.chat/presence": "0.2.46",
+ "@rocket.chat/ui-theming": "0.4.4",
+ "@rocket.chat/account-utils": "0.0.2",
+ "@rocket.chat/agenda": "0.1.0",
+ "@rocket.chat/api-client": "0.2.46",
+ "@rocket.chat/apps": "0.5.25",
+ "@rocket.chat/apps-engine": "1.58.0",
+ "@rocket.chat/base64": "1.0.13",
+ "@rocket.chat/cas-validate": "0.0.3",
+ "@rocket.chat/core-services": "0.11.2",
+ "@rocket.chat/core-typings": "8.0.0-develop",
+ "@rocket.chat/cron": "0.1.46",
+ "@rocket.chat/ddp-client": "0.3.46",
+ "@rocket.chat/desktop-api": "1.1.0",
+ "@rocket.chat/eslint-config": "0.7.0",
+ "@rocket.chat/favicon": "0.0.4",
+ "@rocket.chat/fuselage-ui-kit": "25.0.2",
+ "@rocket.chat/gazzodown": "25.0.2",
+ "@rocket.chat/http-router": "7.9.13",
+ "@rocket.chat/i18n": "1.13.0",
+ "@rocket.chat/instance-status": "0.1.46",
+ "@rocket.chat/jest-presets": "0.0.1",
+ "@rocket.chat/jwt": "0.1.1",
+ "@rocket.chat/livechat": "1.23.17",
+ "@rocket.chat/log-format": "0.0.2",
+ "@rocket.chat/logger": "0.0.2",
+ "@rocket.chat/media-signaling": "0.1.0",
+ "@rocket.chat/message-parser": "0.31.32",
+ "@rocket.chat/message-types": "0.0.1",
+ "@rocket.chat/mock-providers": "0.4.6",
+ "@rocket.chat/model-typings": "1.9.2",
+ "@rocket.chat/models": "1.8.2",
+ "@rocket.chat/mongo-adapter": "0.0.2",
+ "@rocket.chat/poplib": "0.0.2",
+ "@rocket.chat/omni-core": "0.0.11",
+ "@rocket.chat/password-policies": "0.1.0",
+ "@rocket.chat/patch-injection": "0.0.1",
+ "@rocket.chat/peggy-loader": "0.31.27",
+ "@rocket.chat/random": "1.2.2",
+ "@rocket.chat/release-action": "2.2.3",
+ "@rocket.chat/release-changelog": "0.1.0",
+ "@rocket.chat/rest-typings": "8.0.0-develop",
+ "@rocket.chat/server-cloud-communication": "0.0.2",
+ "@rocket.chat/server-fetch": "0.0.3",
+ "@rocket.chat/sha256": "1.0.12",
+ "@rocket.chat/storybook-config": "0.0.2",
+ "@rocket.chat/tools": "0.2.3",
+ "@rocket.chat/tracing": "0.0.1",
+ "@rocket.chat/tsconfig": "0.0.0",
+ "@rocket.chat/ui-avatar": "21.0.2",
+ "@rocket.chat/ui-client": "25.0.2",
+ "@rocket.chat/ui-composer": "0.5.3",
+ "@rocket.chat/ui-contexts": "25.0.2",
+ "@rocket.chat/ui-kit": "0.38.0",
+ "@rocket.chat/ui-video-conf": "25.0.2",
+ "@rocket.chat/ui-voip": "15.0.2",
+ "@rocket.chat/web-ui-registration": "25.0.2"
+ },
+ "changesets": [
+ "afraid-parents-bake",
+ "beige-seahorses-reply",
+ "blue-beans-check",
+ "breezy-timers-flow",
+ "brown-carrots-bathe",
+ "brown-llamas-worry",
+ "bump-patch-1766456337926",
+ "bump-patch-1767191875055",
+ "bump-patch-1767789797202",
+ "bump-patch-1767922007435",
+ "bump-patch-1768188248094",
+ "chatty-dingos-bathe",
+ "chatty-lizards-reflect",
+ "chatty-roses-help",
+ "chilly-cobras-look",
+ "cold-chairs-taste",
+ "cold-chefs-rhyme",
+ "cool-turtles-bathe",
+ "cuddly-eels-perform",
+ "curly-bats-wink",
+ "cyan-mayflies-juggle",
+ "dull-rabbits-add",
+ "dull-tips-look",
+ "empty-buses-walk",
+ "fair-dryers-behave",
+ "fast-ligers-unite",
+ "fast-walls-eat",
+ "few-masks-punch",
+ "fifty-rice-smash",
+ "five-months-shake",
+ "forty-pears-divide",
+ "forty-spiders-sneeze",
+ "four-timers-enjoy",
+ "fresh-tables-raise",
+ "fuzzy-plants-hammer",
+ "fuzzy-teachers-juggle",
+ "gentle-dingos-retire",
+ "gold-keys-compare",
+ "gold-zoos-sneeze",
+ "good-files-accept",
+ "grumpy-colts-collect",
+ "grumpy-readers-give",
+ "happy-carpets-draw",
+ "happy-news-reflect",
+ "honest-knives-cough",
+ "hungry-fans-wait",
+ "large-jobs-smell",
+ "large-planes-destroy",
+ "lazy-pianos-care",
+ "lemon-garlics-check",
+ "long-swans-sin",
+ "loud-elephants-happen",
+ "many-walls-cheat",
+ "many-walls-impress",
+ "many-windows-perform",
+ "metal-moose-travel",
+ "metal-rocks-behave",
+ "nasty-beans-breathe",
+ "nasty-moons-speak",
+ "neat-spoons-cover",
+ "nervous-doors-knock",
+ "nervous-melons-cough",
+ "nervous-wombats-look",
+ "ninety-dodos-confess",
+ "ninety-hats-swim",
+ "odd-pigs-hang",
+ "olive-pens-think",
+ "orange-poets-marry",
+ "pink-months-compare",
+ "plenty-flowers-help",
+ "poor-apricots-heal",
+ "poor-trains-mate",
+ "popular-items-smash",
+ "proud-dingos-sell",
+ "purple-mayflies-approve",
+ "quick-turtles-count",
+ "quiet-bees-turn",
+ "real-grapes-itch",
+ "rich-dogs-wonder",
+ "rotten-bees-behave",
+ "rotten-pugs-trade",
+ "selfish-countries-sleep",
+ "seven-badgers-dress",
+ "seven-otters-turn",
+ "shaggy-otters-think",
+ "sharp-beers-search",
+ "short-pots-lay",
+ "six-squids-pretend",
+ "sixty-bikes-know",
+ "slimy-ads-sing",
+ "slimy-hairs-wink",
+ "smooth-birds-crash",
+ "spicy-nails-design",
+ "stale-shoes-serve",
+ "strange-spiders-act",
+ "strong-bags-train",
+ "strong-bats-swim",
+ "strong-maps-act",
+ "swift-ears-sparkle",
+ "ten-carrots-melt",
+ "tender-wolves-promise",
+ "thick-badgers-grab",
+ "thick-guests-compete",
+ "thick-wasps-turn",
+ "tidy-laws-wink",
+ "tough-baboons-wash",
+ "tricky-trees-tan",
+ "twelve-feet-repeat",
+ "twelve-forks-destroy",
+ "twelve-years-act",
+ "twenty-cars-decide",
+ "two-pets-knock",
+ "two-turtles-try",
+ "violet-cats-lick",
+ "weak-frogs-relax",
+ "wet-papayas-buy",
+ "wet-walls-drive",
+ "wicked-yaks-join",
+ "wild-hairs-carry"
+ ]
+}
diff --git a/.changeset/proud-dingos-sell.md b/.changeset/proud-dingos-sell.md
new file mode 100644
index 0000000000000..17d7910431550
--- /dev/null
+++ b/.changeset/proud-dingos-sell.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes the setting `API_Use_REST_For_DDP_Calls`. Turning this on meant websocket was only used for realtime data/events, and any other meteor method calls goes over method.call endpoint. For microservice deployments, this had to be turned on. Now method calls will always happen over http endpoints.
diff --git a/.changeset/purple-mayflies-approve.md b/.changeset/purple-mayflies-approve.md
new file mode 100644
index 0000000000000..997daff3dce0a
--- /dev/null
+++ b/.changeset/purple-mayflies-approve.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/ui-client': major
+'@rocket.chat/i18n': major
+'@rocket.chat/meteor': major
+---
+
+Promotes quick reactions from preview state to stable
diff --git a/.changeset/quick-turtles-count.md b/.changeset/quick-turtles-count.md
new file mode 100644
index 0000000000000..028ea55d86eb6
--- /dev/null
+++ b/.changeset/quick-turtles-count.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes `/api/v1/banners.getnew` deprecated endpoint
diff --git a/.changeset/quiet-bees-turn.md b/.changeset/quiet-bees-turn.md
new file mode 100644
index 0000000000000..e2312ccb478af
--- /dev/null
+++ b/.changeset/quiet-bees-turn.md
@@ -0,0 +1,10 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/ddp-client": major
+"@rocket.chat/livechat": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes the `livechat:transfer` deprecated method
+Removes the `livechat/room.transfer` deprecated endpoint
+Creates the `livechat/visitor.department.transfer` for visitors department transfer use
diff --git a/.changeset/real-grapes-itch.md b/.changeset/real-grapes-itch.md
new file mode 100644
index 0000000000000..e4eda2815fa45
--- /dev/null
+++ b/.changeset/real-grapes-itch.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Fixes an issue where it‘s not being possible to configure department's `chatClosingTags` without enabling `requestTagBeforeClosingTag`
diff --git a/.changeset/rich-dogs-wonder.md b/.changeset/rich-dogs-wonder.md
new file mode 100644
index 0000000000000..2eeb95649f61d
--- /dev/null
+++ b/.changeset/rich-dogs-wonder.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue that caused Third-party login to not work properly
diff --git a/.changeset/rotten-bees-behave.md b/.changeset/rotten-bees-behave.md
new file mode 100644
index 0000000000000..1a14da3c2cbf0
--- /dev/null
+++ b/.changeset/rotten-bees-behave.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+removes the deprecated meteor method: `livechat:removeMonitor`
diff --git a/.changeset/rotten-pugs-trade.md b/.changeset/rotten-pugs-trade.md
new file mode 100644
index 0000000000000..37515336702d6
--- /dev/null
+++ b/.changeset/rotten-pugs-trade.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/tools": patch
+---
+
+Adds improvements to the push notifications logic; the logic now truncates messages and titles larger than 240, and 65 characters respectively.
diff --git a/.changeset/selfish-countries-sleep.md b/.changeset/selfish-countries-sleep.md
new file mode 100644
index 0000000000000..63227b9df6271
--- /dev/null
+++ b/.changeset/selfish-countries-sleep.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated Realtime API method: `livechat:getRoutingConfig`
diff --git a/.changeset/seven-badgers-dress.md b/.changeset/seven-badgers-dress.md
new file mode 100644
index 0000000000000..3d2b1ef6ad85b
--- /dev/null
+++ b/.changeset/seven-badgers-dress.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes deprecated Realtime API method: `livechat:changeLivechatStatus`
diff --git a/.changeset/seven-otters-turn.md b/.changeset/seven-otters-turn.md
new file mode 100644
index 0000000000000..f67e3d7fe6016
--- /dev/null
+++ b/.changeset/seven-otters-turn.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/rest-typings": patch
+---
+
+Adds deprecation warning for `livechat:saveTag` and new endpoint to replace it; `livechat/tags.save`
diff --git a/.changeset/shaggy-otters-think.md b/.changeset/shaggy-otters-think.md
new file mode 100644
index 0000000000000..961fea652556b
--- /dev/null
+++ b/.changeset/shaggy-otters-think.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes the deprecated param `hideRoomsWithNoActivity` and adjust the api tests accordingly. Endpoint always returns rooms that are not empty.
diff --git a/.changeset/sharp-beers-search.md b/.changeset/sharp-beers-search.md
new file mode 100644
index 0000000000000..0b674b3318eec
--- /dev/null
+++ b/.changeset/sharp-beers-search.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `insertOrUpdateUser` meteor method
diff --git a/.changeset/short-pots-lay.md b/.changeset/short-pots-lay.md
new file mode 100644
index 0000000000000..aa14ad39ed4eb
--- /dev/null
+++ b/.changeset/short-pots-lay.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:removeTag` method
diff --git a/.changeset/six-squids-pretend.md b/.changeset/six-squids-pretend.md
new file mode 100644
index 0000000000000..f150528ffb545
--- /dev/null
+++ b/.changeset/six-squids-pretend.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Fixes role assignment precedence in SAML provisioning, ensuring that SAML-specific default roles take priority over global registration roles, and preventing role merging when both are configured.
diff --git a/.changeset/sixty-bikes-know.md b/.changeset/sixty-bikes-know.md
new file mode 100644
index 0000000000000..dd74fbbc1ff0c
--- /dev/null
+++ b/.changeset/sixty-bikes-know.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue where the client failed to load properly when the “First Channel After Login” setting began with a hash (#), ensuring users are routed to the correct channel.
diff --git a/.changeset/slimy-ads-sing.md b/.changeset/slimy-ads-sing.md
new file mode 100644
index 0000000000000..cad18a974e9c4
--- /dev/null
+++ b/.changeset/slimy-ads-sing.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/model-typings': patch
+'@rocket.chat/models': patch
+'@rocket.chat/meteor': patch
+---
+
+Fixes /v1/users.logout not marking user sessions as logged out, leaving stale sessions active.
diff --git a/.changeset/slimy-hairs-wink.md b/.changeset/slimy-hairs-wink.md
new file mode 100644
index 0000000000000..46357b7cf4418
--- /dev/null
+++ b/.changeset/slimy-hairs-wink.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated setUsername method
+
diff --git a/.changeset/smooth-birds-crash.md b/.changeset/smooth-birds-crash.md
new file mode 100644
index 0000000000000..6dfe28b72b16c
--- /dev/null
+++ b/.changeset/smooth-birds-crash.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `removeCannedResponse` method
diff --git a/.changeset/spicy-nails-design.md b/.changeset/spicy-nails-design.md
new file mode 100644
index 0000000000000..23a5f82e7bb5e
--- /dev/null
+++ b/.changeset/spicy-nails-design.md
@@ -0,0 +1,8 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/core-services": patch
+"@rocket.chat/ddp-streamer": patch
+"@rocket.chat/presence": patch
+---
+
+Ensures presence stays accurate by refreshing connections on heartbeats and removing stale sessions.
\ No newline at end of file
diff --git a/.changeset/stale-shoes-serve.md b/.changeset/stale-shoes-serve.md
new file mode 100644
index 0000000000000..6f1b6fea42ee2
--- /dev/null
+++ b/.changeset/stale-shoes-serve.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/account-service': patch
+---
+
+Fixes an issue where some DDP streamer requests were returning before processing was completed
diff --git a/.changeset/strange-spiders-act.md b/.changeset/strange-spiders-act.md
new file mode 100644
index 0000000000000..5cbdafc2f2ab9
--- /dev/null
+++ b/.changeset/strange-spiders-act.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `saveCannedResponse` method
diff --git a/.changeset/strong-bags-train.md b/.changeset/strong-bags-train.md
new file mode 100644
index 0000000000000..3f53beead4969
--- /dev/null
+++ b/.changeset/strong-bags-train.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/federation-matrix": patch
+---
+
+Fixes an issue where membership updates were not reflected when the user was the first member on their own server.
diff --git a/.changeset/strong-bats-swim.md b/.changeset/strong-bats-swim.md
new file mode 100644
index 0000000000000..d0287981e1e08
--- /dev/null
+++ b/.changeset/strong-bats-swim.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:sendTranscript` method
diff --git a/.changeset/strong-maps-act.md b/.changeset/strong-maps-act.md
new file mode 100644
index 0000000000000..04c8c2c6c64ae
--- /dev/null
+++ b/.changeset/strong-maps-act.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": minor
+"@rocket.chat/ui-voip": minor
+---
+
+Introduces an info button to voice call's in-chat history message, which opens a contextual bar with more detailed information about the voice call.
diff --git a/.changeset/swift-ears-sparkle.md b/.changeset/swift-ears-sparkle.md
new file mode 100644
index 0000000000000..d332456dba3af
--- /dev/null
+++ b/.changeset/swift-ears-sparkle.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes the deprecated `/api/v1/rooms.upload` endpoint
diff --git a/.changeset/ten-carrots-melt.md b/.changeset/ten-carrots-melt.md
new file mode 100644
index 0000000000000..7dcb452a2f92c
--- /dev/null
+++ b/.changeset/ten-carrots-melt.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated livechat:resumeOnHold method
+
diff --git a/.changeset/tender-wolves-promise.md b/.changeset/tender-wolves-promise.md
new file mode 100644
index 0000000000000..234a01290239b
--- /dev/null
+++ b/.changeset/tender-wolves-promise.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes the missing dispatch of `startup` iframe event on client startup.
diff --git a/.changeset/thick-badgers-grab.md b/.changeset/thick-badgers-grab.md
new file mode 100644
index 0000000000000..5cfa0235a6871
--- /dev/null
+++ b/.changeset/thick-badgers-grab.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes `addUserToRole` and `removeUserFromRole` type declaration and deprecation logger
diff --git a/.changeset/thick-guests-compete.md b/.changeset/thick-guests-compete.md
new file mode 100644
index 0000000000000..20b7fb8ca0405
--- /dev/null
+++ b/.changeset/thick-guests-compete.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:saveAgentInfo` method
diff --git a/.changeset/thick-wasps-turn.md b/.changeset/thick-wasps-turn.md
new file mode 100644
index 0000000000000..ea892a87f5556
--- /dev/null
+++ b/.changeset/thick-wasps-turn.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes inconsistency in roomLeft event payload by aligning it to the standard outgoing events signature.
diff --git a/.changeset/tidy-laws-wink.md b/.changeset/tidy-laws-wink.md
new file mode 100644
index 0000000000000..6a745931a3855
--- /dev/null
+++ b/.changeset/tidy-laws-wink.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes the deprecated `GET` Method from `/api/v1/apps`
diff --git a/.changeset/tough-baboons-wash.md b/.changeset/tough-baboons-wash.md
new file mode 100644
index 0000000000000..b8655b4514868
--- /dev/null
+++ b/.changeset/tough-baboons-wash.md
@@ -0,0 +1,8 @@
+---
+'@rocket.chat/rest-typings': major
+'@rocket.chat/ddp-client': major
+'@rocket.chat/logger': major
+'@rocket.chat/meteor': major
+---
+
+Removes stdout logging functionality, related components and settings
diff --git a/.changeset/tricky-trees-tan.md b/.changeset/tricky-trees-tan.md
new file mode 100644
index 0000000000000..f1cb631d720b6
--- /dev/null
+++ b/.changeset/tricky-trees-tan.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Removes sensitive data from outgoing events debug logs.
diff --git a/.changeset/twelve-feet-repeat.md b/.changeset/twelve-feet-repeat.md
new file mode 100644
index 0000000000000..577096ec2b015
--- /dev/null
+++ b/.changeset/twelve-feet-repeat.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated 'e2e.updateGroupKey' method and related type declarations
diff --git a/.changeset/twelve-forks-destroy.md b/.changeset/twelve-forks-destroy.md
new file mode 100644
index 0000000000000..b4cc7321bed9f
--- /dev/null
+++ b/.changeset/twelve-forks-destroy.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/i18n": patch
+---
+
+Adds invitation badge to sidebar
diff --git a/.changeset/twelve-years-act.md b/.changeset/twelve-years-act.md
new file mode 100644
index 0000000000000..d118252c3fffe
--- /dev/null
+++ b/.changeset/twelve-years-act.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Removes deprecated method `livechat:saveBusinessHour`
diff --git a/.changeset/twenty-cars-decide.md b/.changeset/twenty-cars-decide.md
new file mode 100644
index 0000000000000..f7ac52c68a517
--- /dev/null
+++ b/.changeset/twenty-cars-decide.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:saveCustomField` method
diff --git a/.changeset/two-pets-knock.md b/.changeset/two-pets-knock.md
new file mode 100644
index 0000000000000..c568f32c9a6ea
--- /dev/null
+++ b/.changeset/two-pets-knock.md
@@ -0,0 +1,8 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/rest-typings": major
+---
+
+Removes the deprecated roleName parameter from /api/v1/roles.addUserToRole and /api/v1/roles.removeUserFromRole
+
+Removes the ability to pass a role name to the role parameter type from /api/v1/roles.getUsersInRole
diff --git a/.changeset/two-turtles-try.md b/.changeset/two-turtles-try.md
new file mode 100644
index 0000000000000..1d8816caad368
--- /dev/null
+++ b/.changeset/two-turtles-try.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": major
+"@rocket.chat/ddp-client": major
+---
+
+Removes the deprecated `setUserPassword` method
diff --git a/.changeset/violet-cats-lick.md b/.changeset/violet-cats-lick.md
new file mode 100644
index 0000000000000..5e3a68ccaa36a
--- /dev/null
+++ b/.changeset/violet-cats-lick.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/media-signaling': patch
+---
+
+Adds a timeout to ensure transferred calls stop ringing if there's no response from the final user
diff --git a/.changeset/weak-frogs-relax.md b/.changeset/weak-frogs-relax.md
new file mode 100644
index 0000000000000..4a19730ec1462
--- /dev/null
+++ b/.changeset/weak-frogs-relax.md
@@ -0,0 +1,6 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/rest-typings": patch
+---
+
+Adds deprecation warning for `livechat:removeBusinessHour` and new endpoint to replace it; `livechat/business-hours.remove`
diff --git a/.changeset/wet-papayas-buy.md b/.changeset/wet-papayas-buy.md
new file mode 100644
index 0000000000000..1995c1acd95e2
--- /dev/null
+++ b/.changeset/wet-papayas-buy.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/media-calls': patch
+---
+
+Fixes improper handling of errors when SIP integration is configured incorrectly
diff --git a/.changeset/wet-walls-drive.md b/.changeset/wet-walls-drive.md
new file mode 100644
index 0000000000000..b8297000fa903
--- /dev/null
+++ b/.changeset/wet-walls-drive.md
@@ -0,0 +1,6 @@
+---
+'@rocket.chat/ui-client': major
+'@rocket.chat/meteor': major
+---
+
+Promotes Enhanced Navigation from preview state to stable.
diff --git a/.changeset/wicked-yaks-join.md b/.changeset/wicked-yaks-join.md
new file mode 100644
index 0000000000000..92946e56e2a8e
--- /dev/null
+++ b/.changeset/wicked-yaks-join.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': major
+---
+
+Removes deprecated `livechat:takeInquiry` method
diff --git a/.changeset/wild-hairs-carry.md b/.changeset/wild-hairs-carry.md
new file mode 100644
index 0000000000000..9a7c69766ee80
--- /dev/null
+++ b/.changeset/wild-hairs-carry.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": major
+---
+
+Removes the deprecated `authorization:addUserToRole` method
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b98cde5f50299..699185914302b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -17,7 +17,6 @@
/apps/meteor/tests/unit/server @RocketChat/backend
/apps/meteor/app/apps/ @RocketChat/apps
/apps/meteor/app/livechat @RocketChat/omnichannel
-/apps/meteor/app/voip @RocketChat/omnichannel
/apps/meteor/app/sms @RocketChat/omnichannel
/apps/meteor/app/api @RocketChat/backend
/apps/meteor/app/federation @RocketChat/backend
@@ -27,12 +26,9 @@
/packages/models @RocketChat/Architecture
apps/meteor/server/startup/migrations @RocketChat/Architecture
/apps/meteor/packages/rocketchat-livechat @RocketChat/omnichannel
-/apps/meteor/server/services/voip-asterisk @RocketChat/omnichannel
-/apps/meteor/server/services/omnichannel-voip @RocketChat/omnichannel
/apps/meteor/server/features/EmailInbox @RocketChat/omnichannel
/apps/meteor/ee/app/canned-responses @RocketChat/omnichannel
/apps/meteor/ee/app/livechat @RocketChat/omnichannel
/apps/meteor/ee/app/livechat-enterprise @RocketChat/omnichannel
/apps/meteor/client/omnichannel @RocketChat/omnichannel
/apps/meteor/client/components/omnichannel @RocketChat/omnichannel
-/apps/meteor/client/components/voip @RocketChat/omnichannel
diff --git a/.github/actions/docker-image-size-tracker/action.yml b/.github/actions/docker-image-size-tracker/action.yml
index 6c43c3844aa90..ea132c8a99c49 100644
--- a/.github/actions/docker-image-size-tracker/action.yml
+++ b/.github/actions/docker-image-size-tracker/action.yml
@@ -6,6 +6,9 @@ inputs:
github-token:
description: 'GitHub token for commenting on PRs'
required: true
+ ci-pat:
+ description: 'GitHub token for committing to history branch'
+ required: true
registry:
description: 'Container registry (e.g., ghcr.io)'
required: false
@@ -258,6 +261,9 @@ runs:
cd /tmp/history-worktree
git add "history/${timestamp}.json"
git commit -m "Add measurement for ${timestamp} (${commit_sha:0:7})"
+ git config --global user.email "ci@rocket.chat"
+ git config --global user.name "rocketchat-ci[bot]"
+ git config --global url.https://${{ inputs.ci-pat }}@github.com/.insteadOf https://github.com/
git push origin image-size-history
cd -
diff --git a/.github/actions/meteor-build/action.yml b/.github/actions/meteor-build/action.yml
index 934e51bc2ffc4..13fe7da5b302a 100644
--- a/.github/actions/meteor-build/action.yml
+++ b/.github/actions/meteor-build/action.yml
@@ -34,7 +34,7 @@ runs:
id: cache-build
with:
path: /tmp/Rocket.Chat.tar.gz
- key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.type }}-rc-build-${{ inputs.source-hash }}
+ key: ${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-rc-build-${{ inputs.source-hash }}
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
@@ -73,27 +73,27 @@ runs:
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ./node_modules/.vite
- key: vite-local-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('package.json') }}
+ key: vite-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('package.json') }}
restore-keys: |
- vite-local-cache-${{ runner.os }}-${{ runner.arch }}-
+ vite-local-cache-${{ runner.arch }}-${{ runner.os }}-
- name: Cache meteor local
uses: actions/cache@v3
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ./apps/meteor/.meteor/local
- key: meteor-local-cache-${{ runner.os }}-${{ runner.arch }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/versions') }}
+ key: meteor-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/versions') }}
restore-keys: |
- meteor-local-cache-${{ runner.os }}-${{ runner.arch }}-${{ inputs.type }}-
+ meteor-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-
- name: Cache meteor
uses: actions/cache@v3
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ~/.meteor
- key: meteor-cache-${{ runner.os }}-${{ runner.arch }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/release') }}
+ key: meteor-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/release') }}
restore-keys: |
- meteor-cache-${{ runner.os }}-${{ runner.arch }}-${{ inputs.type }}-
+ meteor-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-
- name: Install Meteor
shell: bash
diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml
index faa3f6d7ed17d..16eb1317327e5 100644
--- a/.github/actions/setup-node/action.yml
+++ b/.github/actions/setup-node/action.yml
@@ -69,6 +69,14 @@ runs:
with:
deno-version: ${{ inputs.deno-version }}
+ - name: Configure mongodb-memory-server cache
+ shell: bash
+ run: |
+ CACHE_DIR="${GITHUB_WORKSPACE}/node_modules/.cache/mongodb-memory-server"
+ mkdir -p "$CACHE_DIR"
+ echo "MONGOMS_DOWNLOAD_DIR=$CACHE_DIR" >> $GITHUB_ENV
+ echo "MONGOMS_PREFER_GLOBAL_PATH=false" >> $GITHUB_ENV
+
- name: yarn login
shell: bash
if: inputs.NPM_TOKEN
@@ -90,3 +98,8 @@ runs:
if: inputs.install && inputs.type == 'production'
shell: bash
run: YARN_ENABLE_HARDENED_MODE=${{ inputs.HARDENED_MODE }} yarn workspaces focus --all --production
+
+ - name: restore yarn config file
+ if: inputs.install
+ shell: bash
+ run: git checkout -- .yarnrc.yml
diff --git a/.github/actions/setup-playwright/action.yml b/.github/actions/setup-playwright/action.yml
index 4231e139bddb1..1d71e06e2c473 100644
--- a/.github/actions/setup-playwright/action.yml
+++ b/.github/actions/setup-playwright/action.yml
@@ -11,7 +11,7 @@ runs:
path: |
~/.cache/ms-playwright
# This is the version of Playwright that we are using, if you are willing to upgrade, you should update this.
- key: playwright-${{ runner.os }}-${{ runner.arch }}-1.52.0
+ key: playwright-${{ runner.arch }}-${{ runner.os }}-1.52.0
- name: Install Playwright
shell: bash
diff --git a/.github/actions/update-version-durability/package-lock.json b/.github/actions/update-version-durability/package-lock.json
index 8f686758cf82a..6fb692c311a1d 100644
--- a/.github/actions/update-version-durability/package-lock.json
+++ b/.github/actions/update-version-durability/package-lock.json
@@ -208,13 +208,13 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
- "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
+ "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
+ "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
diff --git a/.github/workflows/ci-code-check.yml b/.github/workflows/ci-code-check.yml
index c6368e5574db3..52e3b7c085d9d 100644
--- a/.github/workflows/ci-code-check.yml
+++ b/.github/workflows/ci-code-check.yml
@@ -30,7 +30,7 @@ jobs:
with:
swap-size-gb: 4
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup NodeJS
uses: ./.github/actions/setup-node
@@ -44,7 +44,7 @@ jobs:
- uses: rharkor/caching-for-turbo@v1.8
- name: Restore packages build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: packages-build
path: /tmp
@@ -55,7 +55,7 @@ jobs:
tar -xzf /tmp/RocketChat-packages-build.tar.gz -C .
- name: Cache TypeCheck
- uses: actions/cache@v4
+ uses: actions/cache@v5
if: matrix.check == 'ts'
with:
path: ./apps/meteor/tsconfig.typecheck.tsbuildinfo
@@ -91,7 +91,7 @@ jobs:
run: yarn turbo run typecheck
- name: Cache eslint
- uses: actions/cache@v4
+ uses: actions/cache@v5
if: matrix.check == 'lint'
with:
path: ./apps/meteor/.eslintcache
diff --git a/.github/workflows/ci-deploy-gh-pages.yml b/.github/workflows/ci-deploy-gh-pages.yml
index 2494a819c1197..b8d92d6413158 100644
--- a/.github/workflows/ci-deploy-gh-pages.yml
+++ b/.github/workflows/ci-deploy-gh-pages.yml
@@ -11,7 +11,7 @@ jobs:
deploy-preview:
runs-on: ubuntu-24.04-arm
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: rharkor/caching-for-turbo@v1.8
- name: Setup NodeJS
diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml
index 828a50aaa4040..1b6a73a04944a 100644
--- a/.github/workflows/ci-test-e2e.yml
+++ b/.github/workflows/ci-test-e2e.yml
@@ -44,10 +44,6 @@ on:
coverage:
required: false
type: string
- db-watcher-disabled:
- default: 'true'
- required: false
- type: string
secrets:
CR_USER:
required: true
@@ -88,7 +84,7 @@ jobs:
mongodb-version: ${{ fromJSON(inputs.mongodb-version) }}
shard: ${{ fromJSON(inputs.shard) }}
- name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.db-watcher-disabled == 'false' && ' [legacy watchers]' || '' }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})
+ name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})
steps:
- name: Collect Workflow Telemetry
@@ -115,7 +111,7 @@ jobs:
username: ${{ secrets.CR_USER }}
password: ${{ secrets.CR_PAT }}
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup NodeJS
uses: ./.github/actions/setup-node
@@ -129,7 +125,7 @@ jobs:
- uses: rharkor/caching-for-turbo@v1.8
- name: Restore packages build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: packages-build
path: /tmp
@@ -178,7 +174,6 @@ jobs:
env:
ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }}
TRANSPORTER: ${{ inputs.transporter }}
- DISABLE_DB_WATCHERS: ${{ inputs.db-watcher-disabled }}
run: |
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait
@@ -253,13 +248,13 @@ jobs:
if: inputs.type == 'ui' && always()
uses: actions/upload-artifact@v5
with:
- name: playwright-test-trace-${{ inputs.release }}-${{ matrix.mongodb-version }}-${{ matrix.shard }}
+ name: playwright-test-trace-${{ inputs.release }}-${{ matrix.mongodb-version }}-${{ matrix.shard }}${{ inputs.db-watcher-disabled == 'true' && '-no-watcher' || '' }}
path: ./apps/meteor/tests/e2e/.playwright*
include-hidden-files: true
- name: Show server logs if E2E test failed
if: failure()
- run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service stream-hub-service omnichannel-transcript-service
+ run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service
- name: Show mongo logs if E2E test failed
if: failure()
diff --git a/.github/workflows/ci-test-storybook.yml b/.github/workflows/ci-test-storybook.yml
index 6729088ba107e..0606607121db3 100644
--- a/.github/workflows/ci-test-storybook.yml
+++ b/.github/workflows/ci-test-storybook.yml
@@ -24,7 +24,7 @@ jobs:
name: Test Storybook
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup NodeJS
uses: ./.github/actions/setup-node
@@ -38,7 +38,7 @@ jobs:
- uses: rharkor/caching-for-turbo@v1.8
- name: Restore packages build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: packages-build
path: /tmp
@@ -55,7 +55,7 @@ jobs:
env:
STORYBOOK_DISABLE_TELEMETRY: 1
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v5
with:
flags: unit
verbose: true
diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml
index 6f1d0096e0be6..5d35f7ef31bef 100644
--- a/.github/workflows/ci-test-unit.yml
+++ b/.github/workflows/ci-test-unit.yml
@@ -34,7 +34,7 @@ jobs:
theme: dark
job_summary: true
comment_on_pr: false
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup NodeJS
uses: ./.github/actions/setup-node
@@ -48,7 +48,7 @@ jobs:
- uses: rharkor/caching-for-turbo@v1.8
- name: Restore packages build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: packages-build
path: /tmp
@@ -61,7 +61,7 @@ jobs:
- name: Unit Test
run: yarn testunit
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v5
with:
flags: unit
verbose: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7c077b50b820f..5f0d8829ff24f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
echo "github.event_name: ${{ github.event_name }}"
cat $GITHUB_EVENT_PATH
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
# with:
# sparse-checkout: |
# package.json
@@ -124,7 +124,7 @@ jobs:
runs-on: ubuntu-24.04-arm
needs: [release-versions]
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
sparse-checkout: |
package.json
@@ -158,7 +158,7 @@ jobs:
fi;
curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \
- "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\", \"compatibleMongoVersions\": [\"5\", \"6\", \"7\", \"8\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"draft\", \"draftAs\": \"$RC_RELEASE\"}" \
+ "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\",\"compatibleMongoVersions\": [\"8.2\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"draft\", \"draftAs\": \"$RC_RELEASE\"}" \
https://releases.rocket.chat/update
packages-build:
@@ -167,12 +167,12 @@ jobs:
runs-on: ubuntu-24.04-arm
steps:
- name: Cache build
- uses: actions/cache@v4
+ uses: actions/cache@v5
id: packages-cache-build
with:
path: |
/tmp/RocketChat-packages-build.tar.gz
- key: ${{ runner.OS }}-packages-build-${{ needs.release-versions.outputs.source-hash }}
+ key: ${{ runner.arch }}-${{ runner.os }}-packages-build-${{ needs.release-versions.outputs.source-hash }}
- name: Debug cache-hit
run: echo "cache-hit=${{ steps.packages-cache-build.outputs.cache-hit }}"
@@ -183,7 +183,7 @@ jobs:
with:
swap-size-gb: 4
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
if: steps.packages-cache-build.outputs.cache-hit != 'true'
- name: Setup NodeJS
@@ -198,13 +198,13 @@ jobs:
HARDENED_MODE: '1'
- name: Cache vite
- uses: actions/cache@v4
+ uses: actions/cache@v5
if: steps.packages-cache-build.outputs.cache-hit != 'true'
with:
path: ./node_modules/.vite
- key: vite-local-cache-${{ runner.OS }}-${{ hashFiles('package.json') }}
+ key: vite-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('package.json') }}
restore-keys: |
- vite-local-cache-${{ runner.os }}-
+ vite-local-cache-${{ runner.arch }}-${{ runner.os }}-
- uses: rharkor/caching-for-turbo@v1.8
if: steps.packages-cache-build.outputs.cache-hit != 'true'
@@ -257,7 +257,7 @@ jobs:
job_summary: true
comment_on_pr: false
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: ./.github/actions/meteor-build
with:
@@ -282,7 +282,7 @@ jobs:
service:
[
[authorization-service, queue-worker-service, ddp-streamer-service],
- [account-service, presence-service, stream-hub-service, omnichannel-transcript-service],
+ [account-service, presence-service, omnichannel-transcript-service],
[rocketchat],
]
type:
@@ -298,10 +298,10 @@ jobs:
type: coverage
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Restore packages build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: packages-build
path: /tmp
@@ -378,7 +378,7 @@ jobs:
LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
with:
sparse-checkout: |
@@ -396,7 +396,7 @@ jobs:
- name: Download manifests
if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: manifests-*
path: /tmp/manifests
@@ -448,12 +448,13 @@ jobs:
contents: write
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Track Docker image sizes
uses: ./.github/actions/docker-image-size-tracker
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+ ci-pat: ${{ secrets.CI_PAT }}
registry: ghcr.io
repository: ${{ needs.release-versions.outputs.lowercase-repo }}
tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
@@ -542,7 +543,7 @@ jobs:
release: ee
transporter: 'nats://nats:4222'
enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }}
- mongodb-version: "['5.0', '8.2']"
+ mongodb-version: "['8.2']"
coverage: '8.2'
node-version: ${{ needs.release-versions.outputs.node-version }}
deno-version: ${{ needs.release-versions.outputs.deno-version }}
@@ -556,33 +557,6 @@ jobs:
name: 🔨 Test UI (EE)
needs: [checks, build-gh-docker-publish, release-versions]
- uses: ./.github/workflows/ci-test-e2e.yml
- with:
- type: ui
- release: ee
- transporter: 'nats://nats:4222'
- enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }}
- shard: '[1, 2, 3, 4, 5]'
- total-shard: 5
- mongodb-version: "['5.0']"
- node-version: ${{ needs.release-versions.outputs.node-version }}
- deno-version: ${{ needs.release-versions.outputs.deno-version }}
- lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }}
- gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
- retries: ${{ (github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }}
- secrets:
- CR_USER: ${{ secrets.CR_USER }}
- CR_PAT: ${{ secrets.CR_PAT }}
- QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }}
- REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }}
- REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }}
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }}
-
- test-ui-ee-watcher:
- name: 🔨 Test UI (EE)
- needs: [checks, build-gh-docker-publish, release-versions]
-
uses: ./.github/workflows/ci-test-e2e.yml
with:
type: ui
@@ -598,7 +572,6 @@ jobs:
lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }}
gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
retries: ${{ (github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }}
- db-watcher-disabled: 'false'
secrets:
CR_USER: ${{ secrets.CR_USER }}
CR_PAT: ${{ secrets.CR_PAT }}
@@ -607,14 +580,13 @@ jobs:
REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }}
-
test-federation-matrix:
name: 🔨 Test Federation Matrix
needs: [checks, build-gh-docker-publish, packages-build, release-versions]
runs-on: ubuntu-24.04-arm
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup NodeJS
uses: ./.github/actions/setup-node
@@ -627,7 +599,7 @@ jobs:
- uses: rharkor/caching-for-turbo@v1.8
- name: Restore turbo build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
continue-on-error: true
with:
name: turbo-build
@@ -653,25 +625,26 @@ jobs:
working-directory: ./ee/packages/federation-matrix
env:
ROCKETCHAT_IMAGE: ghcr.io/${{ needs.release-versions.outputs.lowercase-repo }}/rocket.chat:${{ needs.release-versions.outputs.gh-docker-tag }}
- ENTERPRISE_LICENSE_RC1: ${{ secrets.ENTERPRISE_LICENSE_RC1 }}
+ ENTERPRISE_LICENSE_RC1: ZAikY+LLaal7mT6RNYxpyWEmMQyucrl50/7pYBXqHczc90j+RLwF+T0xuCT2pIpKMC5DxcZ1TtkV6MYJk5whrwmap+mQ0FV+VpILJlL0i4T21K4vMfzZXTWm/pzcAy2fMTUNH+mUA9HTBD6lYYh40KnbGXPAd80VbZk0MO/WbWBm2dOT0YCwfvlRyurRqkDAQrftLaffzCNUsMKk0fh+MKs73UDHZQDp1yvs7WoGpPu5ZVi5mTBOt3ZKVz5KjGfClLwJptFPmW1w6nKelAiJBDPpjcX1ylfjxpnBoixko7uN52zlyaeoAYwfRcdDLnZ8k0Ou6tui/vTQUXjGIjHw2AhMaKwonn4E9LYpuA1KEXt08qJL5J3ZtjSCV1T+A9Z3zFhhLgp5dxP/PPUbxDn/P8XKp7nXM9duIfcCMlnea7V8ixEyCHwwvKQaXVVidcsUGtB8CwS0GlsAEBLOzqMehuQUK2rdQ4WgEz3AYveikeVvSzgBHvyXsxssWAThc0Mht0eEJqdDhUB2QeZ2WmPsaSSD639Z4WgjSUoR0zh8bfqepH+2XRcUryXe2yN+iU+3POzi9wfg0k65MxXT8pBg3PD5RHnR8oflEP0tpZts33JiBhYRxX3MKplAFm4dMuphTsDJTh+e534pT7IPuZF79QSVaLEWZfVVVb7nGFtmMwA=
QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }}
+ PR_NUMBER: ${{ github.event.number }}
run: yarn test:integration --image "${ROCKETCHAT_IMAGE}"
report-coverage:
name: 📊 Report Coverage
runs-on: ubuntu-24.04
- needs: [release-versions, test-api-ee, test-ui-ee, test-ui-ee-watcher]
+ needs: [release-versions, test-api-ee, test-ui-ee]
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Use Node.js
- uses: actions/setup-node@v6.0.0
+ uses: actions/setup-node@v6.1.0
with:
node-version: ${{ needs.release-versions.outputs.node-version }}
- name: Restore coverage folder
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: coverage-*
path: /tmp/coverage
@@ -692,7 +665,7 @@ jobs:
include-hidden-files: true
- name: Report API coverage
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v5
with:
files: /tmp/coverage_report/api/lcov.info
working-directory: .
@@ -701,7 +674,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Report UI coverage
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v5
with:
files: /tmp/coverage_report/ui/lcov.info
working-directory: .
@@ -712,7 +685,7 @@ jobs:
tests-done:
name: ✅ Tests Done
runs-on: ubuntu-24.04-arm
- needs: [checks, test-unit, test-api, test-ui, test-api-ee, test-ui-ee, test-ui-ee-watcher, test-federation-matrix]
+ needs: [checks, test-unit, test-api, test-ui, test-api-ee, test-ui-ee, test-federation-matrix]
if: always()
steps:
- name: Test finish aggregation
@@ -741,10 +714,6 @@ jobs:
exit 1
fi
- if [[ '${{ needs.test-ui-ee-watcher.result }}' != 'success' ]]; then
- exit 1
- fi
-
if [[ '${{ needs.test-federation-matrix.result }}' != 'success' ]]; then
exit 1
fi
@@ -758,7 +727,7 @@ jobs:
needs: [build-gh-docker-publish, release-versions]
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
sparse-checkout: |
package.json
@@ -766,7 +735,7 @@ jobs:
ref: ${{ github.ref }}
- name: Restore build
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: build-production
path: /tmp/build
@@ -815,7 +784,7 @@ jobs:
LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
sparse-checkout: |
docker-compose-ci.yml
@@ -837,7 +806,7 @@ jobs:
- name: Download manifests
if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: manifests-*
path: /tmp/manifests
@@ -928,7 +897,7 @@ jobs:
- docker-image-publish
- release-versions
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
sparse-checkout: |
package.json
@@ -961,7 +930,7 @@ jobs:
fi;
curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \
- "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\", \"compatibleMongoVersions\": [\"5\", \"6\", \"7\", \"8\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \
+ "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\", \"compatibleMongoVersions\": [\"8.2\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \
https://releases.rocket.chat/update
# Makes build fail if the release isn't there
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 6ab011a49e0d8..3fdfebd584f75 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
diff --git a/.github/workflows/new-release.yml b/.github/workflows/new-release.yml
index 9df9526ff1aa6..49266b3dd5042 100644
--- a/.github/workflows/new-release.yml
+++ b/.github/workflows/new-release.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.base-ref }}
fetch-depth: 0
diff --git a/.github/workflows/pr-update-description.yml b/.github/workflows/pr-update-description.yml
index fdb7c40f93963..eb1ecbf6b9497 100644
--- a/.github/workflows/pr-update-description.yml
+++ b/.github/workflows/pr-update-description.yml
@@ -13,7 +13,7 @@ jobs:
if: startsWith(github.head_ref, 'release-')
steps:
- name: Checkout Repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.CI_PAT }}
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index 4dbc8d10d32fd..0465375a1727e 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.CI_PAT }}
diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml
index 6c1b556809a39..ab9af0347da01 100644
--- a/.github/workflows/release-candidate.yml
+++ b/.github/workflows/release-candidate.yml
@@ -1,12 +1,12 @@
name: Release candidate cut
on:
schedule:
- - cron: '28 17 20 * *' # run at minute 28 to avoid the chance of delay due to high load on GH
+ - cron: '28 17 21 * *' # run at minute 28 to avoid the chance of delay due to high load on GH
jobs:
new-release:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 89fb59d0c5845..552b8b5b6917d 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -9,7 +9,7 @@ jobs:
permissions:
issues: write
steps:
- - uses: actions/stale@v5
+ - uses: actions/stale@v10
with:
days-before-issue-stale: 14
days-before-issue-close: 14
diff --git a/.github/workflows/update-version-durability.yml b/.github/workflows/update-version-durability.yml
index 5eb69ad3effbb..00e1ee8125ad6 100644
--- a/.github/workflows/update-version-durability.yml
+++ b/.github/workflows/update-version-durability.yml
@@ -18,10 +18,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Use Node.js
- uses: actions/setup-node@v6.0.0
+ uses: actions/setup-node@v6.1.0
with:
node-version: 22.16.0
diff --git a/.yarnrc.yml b/.yarnrc.yml
index 128ce3e5464c8..6543b343613d7 100644
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -10,16 +10,6 @@ nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-engines.cjs
- spec: "https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js"
-
-supportedArchitectures:
- cpu:
- - arm64
- - x64
- libc:
- - glibc
- - musl
- os:
- - linux
+ spec: 'https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js'
yarnPath: .yarn/releases/yarn-4.11.0.cjs
diff --git a/apps/meteor/.eslintignore b/apps/meteor/.eslintignore
deleted file mode 100644
index f7051767d7727..0000000000000
--- a/apps/meteor/.eslintignore
+++ /dev/null
@@ -1,16 +0,0 @@
-/node_modules/
-#/tests/e2e/
-/packages/
-/app/emoji-emojione/generateEmojiIndex.js
-/public/
-/private/moment-locales/
-/imports/
-/ee/server/services/dist/
-!/.mocharc.js
-!/.mocharc.*.js
-!/.scripts/
-!/.storybook/
-!/client/.eslintrc.js
-!/ee/client/.eslintrc.js
-/storybook-static/
-/packages/
diff --git a/apps/meteor/.eslintrc.json b/apps/meteor/.eslintrc.json
index 41b02ad3a4520..84af970d6b52f 100644
--- a/apps/meteor/.eslintrc.json
+++ b/apps/meteor/.eslintrc.json
@@ -1,5 +1,5 @@
{
- "extends": ["@rocket.chat/eslint-config", "@rocket.chat/eslint-config/react", "plugin:you-dont-need-lodash-underscore/compatible"],
+ "extends": ["@rocket.chat/eslint-config", "@rocket.chat/eslint-config/react", "plugin:you-dont-need-lodash-underscore/compatible", "plugin:storybook/recommended"],
"globals": {
"__meteor_bootstrap__": false,
"__meteor_runtime_config__": false,
@@ -22,6 +22,21 @@
}
]
},
+ "ignorePatterns": [
+ "app/emoji-emojione/generateEmojiIndex.js",
+ "public",
+ "private/moment-locales",
+ "imports",
+ "ee/server/services/dist",
+ "!.mocharc.js",
+ "!.mocharc.*.js",
+ "!.scripts",
+ "!.storybook",
+ "!client/.eslintrc.js",
+ "!ee/client/.eslintrc.js",
+ "storybook-static",
+ "packages"
+ ],
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
diff --git a/apps/meteor/.meteor/packages b/apps/meteor/.meteor/packages
index 0dce43f784ee4..4242b98f6bed3 100644
--- a/apps/meteor/.meteor/packages
+++ b/apps/meteor/.meteor/packages
@@ -3,7 +3,6 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
-rocketchat:ddp
rocketchat:mongo-config
rocketchat:livechat
rocketchat:streamer
diff --git a/apps/meteor/.meteor/versions b/apps/meteor/.meteor/versions
index 04d79cb550212..04fcb4d920bdd 100644
--- a/apps/meteor/.meteor/versions
+++ b/apps/meteor/.meteor/versions
@@ -70,7 +70,6 @@ reactive-dict@1.3.2
reactive-var@1.0.13
reload@1.3.2
retry@1.1.1
-rocketchat:ddp@0.0.1
rocketchat:livechat@0.0.1
rocketchat:mongo-config@0.0.1
rocketchat:streamer@1.1.0
diff --git a/apps/meteor/.storybook/main.ts b/apps/meteor/.storybook/main.ts
index 4e358f72d3b52..1cceaff3ff602 100644
--- a/apps/meteor/.storybook/main.ts
+++ b/apps/meteor/.storybook/main.ts
@@ -9,7 +9,7 @@ export default {
addons: [
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
- getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
+ getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
getAbsolutePath('@storybook/addon-styling-webpack'),
getAbsolutePath('@storybook/addon-a11y'),
],
@@ -28,6 +28,7 @@ export default {
'react$': require.resolve('../../../node_modules/react'),
// 'react/jsx-runtime': require.resolve('../../../node_modules/react/jsx-runtime'),
'@tanstack/react-query': require.resolve('../../../node_modules/@tanstack/react-query'),
+ '@rocket.chat/fuselage$': require.resolve('../../../node_modules/@rocket.chat/fuselage'),
'swiper/swiper.css$': 'swiper/css',
'swiper/modules/navigation/navigation.min.css$': 'swiper/css/navigation',
'swiper/modules/keyboard/keyboard.min.css$': 'swiper/css/keyboard',
@@ -45,7 +46,6 @@ export default {
config.plugins?.push(
new webpack.NormalModuleReplacementPlugin(/^meteor/, require.resolve('./mocks/meteor.js')),
new webpack.NormalModuleReplacementPlugin(/(app)\/*.*\/(server)\/*/, require.resolve('./mocks/empty.ts')),
- new webpack.NormalModuleReplacementPlugin(/^sip.js/, require.resolve('./mocks/empty.ts')),
);
return config;
diff --git a/apps/meteor/CHANGELOG.md b/apps/meteor/CHANGELOG.md
index df458e70638a2..a91e61835c003 100644
--- a/apps/meteor/CHANGELOG.md
+++ b/apps/meteor/CHANGELOG.md
@@ -1,5 +1,486 @@
# @rocket.chat/meteor
+## 8.0.0-rc.5
+
+### Patch Changes
+
+- Bump @rocket.chat/meteor version.
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@8.0.0-rc.5
+ - @rocket.chat/rest-typings@8.0.0-rc.5
+ - @rocket.chat/abac@0.1.0-rc.5
+ - @rocket.chat/federation-matrix@0.0.9-rc.5
+ - @rocket.chat/license@1.1.7-rc.5
+ - @rocket.chat/media-calls@0.2.0-rc.5
+ - @rocket.chat/omnichannel-services@0.3.44-rc.5
+ - @rocket.chat/pdf-worker@0.3.26-rc.5
+ - @rocket.chat/presence@0.2.47-rc.5
+ - @rocket.chat/api-client@0.2.47-rc.5
+ - @rocket.chat/apps@0.6.0-rc.5
+ - @rocket.chat/core-services@0.12.0-rc.5
+ - @rocket.chat/cron@0.1.47-rc.5
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.5
+ - @rocket.chat/gazzodown@26.0.0-rc.5
+ - @rocket.chat/http-router@7.9.14-rc.5
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.5
+ - @rocket.chat/ui-avatar@22.0.0-rc.5
+ - @rocket.chat/ui-client@26.0.0-rc.5
+ - @rocket.chat/ui-contexts@26.0.0-rc.5
+ - @rocket.chat/ui-voip@16.0.0-rc.5
+ - @rocket.chat/web-ui-registration@26.0.0-rc.5
+ - @rocket.chat/models@2.0.0-rc.5
+ - @rocket.chat/server-cloud-communication@0.0.2
+ - @rocket.chat/network-broker@0.2.26-rc.5
+ - @rocket.chat/omni-core-ee@0.0.12-rc.5
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/ui-video-conf@26.0.0-rc.5
+ - @rocket.chat/instance-status@0.1.47-rc.5
+ - @rocket.chat/omni-core@0.0.12-rc.5
+
+
+## 8.0.0-rc.4
+
+### Patch Changes
+
+- Bump @rocket.chat/meteor version.
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@8.0.0-rc.4
+ - @rocket.chat/rest-typings@8.0.0-rc.4
+ - @rocket.chat/abac@0.1.0-rc.4
+ - @rocket.chat/federation-matrix@0.0.9-rc.4
+ - @rocket.chat/license@1.1.7-rc.4
+ - @rocket.chat/media-calls@0.2.0-rc.4
+ - @rocket.chat/omnichannel-services@0.3.44-rc.4
+ - @rocket.chat/pdf-worker@0.3.26-rc.4
+ - @rocket.chat/presence@0.2.47-rc.4
+ - @rocket.chat/api-client@0.2.47-rc.4
+ - @rocket.chat/apps@0.6.0-rc.4
+ - @rocket.chat/core-services@0.12.0-rc.4
+ - @rocket.chat/cron@0.1.47-rc.4
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.4
+ - @rocket.chat/gazzodown@26.0.0-rc.4
+ - @rocket.chat/http-router@7.9.14-rc.4
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.4
+ - @rocket.chat/ui-avatar@22.0.0-rc.4
+ - @rocket.chat/ui-client@26.0.0-rc.4
+ - @rocket.chat/ui-contexts@26.0.0-rc.4
+ - @rocket.chat/ui-voip@16.0.0-rc.4
+ - @rocket.chat/web-ui-registration@26.0.0-rc.4
+ - @rocket.chat/models@2.0.0-rc.4
+ - @rocket.chat/server-cloud-communication@0.0.2
+ - @rocket.chat/network-broker@0.2.26-rc.4
+ - @rocket.chat/omni-core-ee@0.0.12-rc.4
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/ui-video-conf@26.0.0-rc.4
+ - @rocket.chat/instance-status@0.1.47-rc.4
+ - @rocket.chat/omni-core@0.0.12-rc.4
+
+
+## 8.0.0-rc.3
+
+### Patch Changes
+
+- Bump @rocket.chat/meteor version.
+
+- Updated dependencies [ae1e2faaeb6f7a086f8affde4c8a81e55e2a0e04]:
+
+ - @rocket.chat/media-signaling@0.1.1-rc.1
+ - @rocket.chat/media-calls@0.2.0-rc.3
+ - @rocket.chat/core-services@0.12.0-rc.3
+ - @rocket.chat/ui-voip@16.0.0-rc.3
+ - @rocket.chat/abac@0.1.0-rc.3
+ - @rocket.chat/federation-matrix@0.0.9-rc.3
+ - @rocket.chat/network-broker@0.2.26-rc.3
+ - @rocket.chat/omni-core-ee@0.0.12-rc.3
+ - @rocket.chat/omnichannel-services@0.3.44-rc.3
+ - @rocket.chat/presence@0.2.47-rc.3
+ - @rocket.chat/ui-contexts@26.0.0-rc.3
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.3
+ - @rocket.chat/gazzodown@26.0.0-rc.3
+ - @rocket.chat/ui-avatar@22.0.0-rc.3
+ - @rocket.chat/ui-client@26.0.0-rc.3
+ - @rocket.chat/ui-video-conf@26.0.0-rc.3
+ - @rocket.chat/web-ui-registration@26.0.0-rc.3
+ - @rocket.chat/core-typings@8.0.0-rc.3
+ - @rocket.chat/rest-typings@8.0.0-rc.3
+ - @rocket.chat/license@1.1.7-rc.3
+ - @rocket.chat/pdf-worker@0.3.26-rc.3
+ - @rocket.chat/api-client@0.2.47-rc.3
+ - @rocket.chat/apps@0.6.0-rc.3
+ - @rocket.chat/cron@0.1.47-rc.3
+ - @rocket.chat/http-router@7.9.14-rc.3
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.3
+ - @rocket.chat/models@2.0.0-rc.3
+ - @rocket.chat/server-cloud-communication@0.0.2
+ - @rocket.chat/instance-status@0.1.47-rc.3
+ - @rocket.chat/omni-core@0.0.12-rc.3
+
+
+## 8.0.0-rc.2
+
+### Patch Changes
+
+- Bump @rocket.chat/meteor version.
+
+- ([#38007](https://github.com/RocketChat/Rocket.Chat/pull/38007)) Changes the HTTP code of `/api/v1/method.call` and `/api/v1/method.callAnon` in case of internal errors
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@8.0.0-rc.2
+ - @rocket.chat/rest-typings@8.0.0-rc.2
+ - @rocket.chat/abac@0.1.0-rc.2
+ - @rocket.chat/federation-matrix@0.0.9-rc.2
+ - @rocket.chat/license@1.1.7-rc.2
+ - @rocket.chat/media-calls@0.2.0-rc.2
+ - @rocket.chat/omnichannel-services@0.3.44-rc.2
+ - @rocket.chat/pdf-worker@0.3.26-rc.2
+ - @rocket.chat/presence@0.2.47-rc.2
+ - @rocket.chat/api-client@0.2.47-rc.2
+ - @rocket.chat/apps@0.6.0-rc.2
+ - @rocket.chat/core-services@0.12.0-rc.2
+ - @rocket.chat/cron@0.1.47-rc.2
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.2
+ - @rocket.chat/gazzodown@26.0.0-rc.2
+ - @rocket.chat/http-router@7.9.14-rc.2
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.2
+ - @rocket.chat/ui-avatar@22.0.0-rc.2
+ - @rocket.chat/ui-client@26.0.0-rc.2
+ - @rocket.chat/ui-contexts@26.0.0-rc.2
+ - @rocket.chat/ui-voip@16.0.0-rc.2
+ - @rocket.chat/web-ui-registration@26.0.0-rc.2
+ - @rocket.chat/models@2.0.0-rc.2
+ - @rocket.chat/server-cloud-communication@0.0.2
+ - @rocket.chat/network-broker@0.2.26-rc.2
+ - @rocket.chat/omni-core-ee@0.0.12-rc.2
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/ui-video-conf@26.0.0-rc.2
+ - @rocket.chat/instance-status@0.1.47-rc.2
+ - @rocket.chat/omni-core@0.0.12-rc.2
+
+
+## 8.0.0-rc.1
+
+### Patch Changes
+
+- Bump @rocket.chat/meteor version.
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@8.0.0-rc.1
+ - @rocket.chat/rest-typings@8.0.0-rc.1
+ - @rocket.chat/abac@0.1.0-rc.1
+ - @rocket.chat/federation-matrix@0.0.9-rc.1
+ - @rocket.chat/license@1.1.7-rc.1
+ - @rocket.chat/media-calls@0.2.0-rc.1
+ - @rocket.chat/omnichannel-services@0.3.44-rc.1
+ - @rocket.chat/pdf-worker@0.3.26-rc.1
+ - @rocket.chat/presence@0.2.47-rc.1
+ - @rocket.chat/api-client@0.2.47-rc.1
+ - @rocket.chat/apps@0.6.0-rc.1
+ - @rocket.chat/core-services@0.12.0-rc.1
+ - @rocket.chat/cron@0.1.47-rc.1
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.1
+ - @rocket.chat/gazzodown@26.0.0-rc.1
+ - @rocket.chat/http-router@7.9.14-rc.1
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.1
+ - @rocket.chat/ui-avatar@22.0.0-rc.1
+ - @rocket.chat/ui-client@26.0.0-rc.1
+ - @rocket.chat/ui-contexts@26.0.0-rc.1
+ - @rocket.chat/ui-voip@16.0.0-rc.1
+ - @rocket.chat/web-ui-registration@26.0.0-rc.1
+ - @rocket.chat/models@2.0.0-rc.1
+ - @rocket.chat/server-cloud-communication@0.0.2
+ - @rocket.chat/network-broker@0.2.26-rc.1
+ - @rocket.chat/omni-core-ee@0.0.12-rc.1
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/ui-video-conf@26.0.0-rc.1
+ - @rocket.chat/instance-status@0.1.47-rc.1
+ - @rocket.chat/omni-core@0.0.12-rc.1
+
+
+## 8.0.0-rc.0
+
+### Major Changes
+
+- ([#36829](https://github.com/RocketChat/Rocket.Chat/pull/36829)) Removes the deprecated sendConfirmationEmail method
+
+- ([#37460](https://github.com/RocketChat/Rocket.Chat/pull/37460)) Removes deprecated `livechat:removeUnit` method
+
+- ([#36836](https://github.com/RocketChat/Rocket.Chat/pull/36836)) Removes the deprecated livechat:getTagsList method
+
+ Removes the deprecated livechat:getUnitsFromUser method
+
+ Removes the deprecated livechat:getFirstRoomMessage method
+
+ Removes the deprecated livechat:getDepartmentForwardRestrictions method
+
+- ([#37672](https://github.com/RocketChat/Rocket.Chat/pull/37672)) Removes deprecated VoIP permissions
+
+- ([#36941](https://github.com/RocketChat/Rocket.Chat/pull/36941)) Makes Voice Calls enabled by default when available
+
+- ([#37672](https://github.com/RocketChat/Rocket.Chat/pull/37672)) Removes deprecated VoIP from Omnichannel
+
+- ([#37461](https://github.com/RocketChat/Rocket.Chat/pull/37461)) Removes deprecated `livechat:saveUnit` method
+
+- ([#36864](https://github.com/RocketChat/Rocket.Chat/pull/36864)) Removes the deprecated `authorization:removeUserFromRole` method
+
+- ([#36976](https://github.com/RocketChat/Rocket.Chat/pull/36976)) Promotes Timestamp Parser from preview state to stable
+
+- ([#37123](https://github.com/RocketChat/Rocket.Chat/pull/37123)) Removes ecdh functionality and related settings
+
+- ([#35436](https://github.com/RocketChat/Rocket.Chat/pull/35436) by [@blackmamba1231](https://github.com/blackmamba1231)) Removes deprecated `canAccessRoom` meteor method
+
+- ([#36925](https://github.com/RocketChat/Rocket.Chat/pull/36925)) Removes deprecated Realtime API method: `livechat:getAnalyticsChartData`
+
+- ([#37392](https://github.com/RocketChat/Rocket.Chat/pull/37392)) Removes deprecated `livechat:returnAsInquiry` method
+
+- ([#37390](https://github.com/RocketChat/Rocket.Chat/pull/37390)) Removes deprecated `removeCustomField` method
+
+- ([#37672](https://github.com/RocketChat/Rocket.Chat/pull/37672)) Removes Deprecated FreeSwitch integration
+
+- ([#36851](https://github.com/RocketChat/Rocket.Chat/pull/36851)) Removes the deprecated `getUserRoles` method in favor of the `/v1/roles.getUsersInPublicRoles` endpoint.
+
+- ([#37388](https://github.com/RocketChat/Rocket.Chat/pull/37388)) Removes deprecated `setAdminStatus` method
+
+- ([#35961](https://github.com/RocketChat/Rocket.Chat/pull/35961)) Removes support of MongoDB versions 5.x and 6.x
+
+- ([#36823](https://github.com/RocketChat/Rocket.Chat/pull/36823)) Removes deprecated meteor method `muteUserInRoom`
+
+- ([#36825](https://github.com/RocketChat/Rocket.Chat/pull/36825)) Removes the deprecated method `createToken`
+
+- ([#37022](https://github.com/RocketChat/Rocket.Chat/pull/37022)) Promotes Resizable Contextualbars from preview state to stable.
+
+- ([#36837](https://github.com/RocketChat/Rocket.Chat/pull/36837)) Removes the deprecated `setReaction` meteor method
+
+- ([#36824](https://github.com/RocketChat/Rocket.Chat/pull/36824)) Removes deprecated Realtime API method: `livechat:closeRoom`
+
+- ([#36832](https://github.com/RocketChat/Rocket.Chat/pull/36832)) Removes the deprecated `setCustomFields` method
+
+- ([#36931](https://github.com/RocketChat/Rocket.Chat/pull/36931)) Removes deprecated Realtime API method: `livechat:removeAllClosedRooms`
+
+- ([#36830](https://github.com/RocketChat/Rocket.Chat/pull/36830)) Removes the deprecated `getAvatarSuggestion` method
+
+- ([#37462](https://github.com/RocketChat/Rocket.Chat/pull/37462)) Removes deprecated `livechat:saveDepartment` method
+
+- ([#37397](https://github.com/RocketChat/Rocket.Chat/pull/37397)) Removes deprecated `livechat:setUpConnection` method
+
+- Removes the deprecated `getRoomRoles` method
+
+- Removes the deprecated `authorization:deleteRole` method
+
+- ([#37391](https://github.com/RocketChat/Rocket.Chat/pull/37391)) Removes deprecated `livechat:removeRoom` method
+
+- ([#36849](https://github.com/RocketChat/Rocket.Chat/pull/36849)) Removes deprecated `appId` parameter from the `oauth-apps.get` endpoint.
+
+- ([#37463](https://github.com/RocketChat/Rocket.Chat/pull/37463)) Removes deprecated `sendFileLivechatMessage` method
+
+- ([#32590](https://github.com/RocketChat/Rocket.Chat/pull/32590)) Removes the setting `API_Use_REST_For_DDP_Calls`. Turning this on meant websocket was only used for realtime data/events, and any other meteor method calls goes over method.call endpoint. For microservice deployments, this had to be turned on. Now method calls will always happen over http endpoints.
+
+- ([#36966](https://github.com/RocketChat/Rocket.Chat/pull/36966)) Promotes quick reactions from preview state to stable
+
+- ([#36821](https://github.com/RocketChat/Rocket.Chat/pull/36821)) Removes `/api/v1/banners.getnew` deprecated endpoint
+
+- ([#36871](https://github.com/RocketChat/Rocket.Chat/pull/36871)) Removes the `livechat:transfer` deprecated method
+ Removes the `livechat/room.transfer` deprecated endpoint
+ Creates the `livechat/visitor.department.transfer` for visitors department transfer use
+- ([#36924](https://github.com/RocketChat/Rocket.Chat/pull/36924)) Removes deprecated Realtime API method: `livechat:getRoutingConfig`
+
+- ([#36809](https://github.com/RocketChat/Rocket.Chat/pull/36809)) Removes deprecated Realtime API method: `livechat:changeLivechatStatus`
+
+- ([#36951](https://github.com/RocketChat/Rocket.Chat/pull/36951)) Removes the deprecated param `hideRoomsWithNoActivity` and adjust the api tests accordingly. Endpoint always returns rooms that are not empty.
+
+- ([#36838](https://github.com/RocketChat/Rocket.Chat/pull/36838)) Removes the deprecated `insertOrUpdateUser` meteor method
+
+- ([#37406](https://github.com/RocketChat/Rocket.Chat/pull/37406)) Removes deprecated `livechat:removeTag` method
+
+- ([#37810](https://github.com/RocketChat/Rocket.Chat/pull/37810)) Fixes role assignment precedence in SAML provisioning, ensuring that SAML-specific default roles take priority over global registration roles, and preventing role merging when both are configured.
+
+- ([#36831](https://github.com/RocketChat/Rocket.Chat/pull/36831)) Removes the deprecated setUsername method
+
+- ([#37446](https://github.com/RocketChat/Rocket.Chat/pull/37446)) Removes deprecated `removeCannedResponse` method
+
+- ([#37405](https://github.com/RocketChat/Rocket.Chat/pull/37405)) Removes deprecated `saveCannedResponse` method
+
+- ([#37396](https://github.com/RocketChat/Rocket.Chat/pull/37396)) Removes deprecated `livechat:sendTranscript` method
+
+- ([#36857](https://github.com/RocketChat/Rocket.Chat/pull/36857)) Removes the deprecated `/api/v1/rooms.upload` endpoint
+
+- ([#36908](https://github.com/RocketChat/Rocket.Chat/pull/36908)) Removes the deprecated livechat:resumeOnHold method
+
+- ([#36935](https://github.com/RocketChat/Rocket.Chat/pull/36935)) Removes `addUserToRole` and `removeUserFromRole` type declaration and deprecation logger
+
+- ([#37393](https://github.com/RocketChat/Rocket.Chat/pull/37393)) Removes deprecated `livechat:saveAgentInfo` method
+
+- ([#36907](https://github.com/RocketChat/Rocket.Chat/pull/36907)) Removes the deprecated `GET` Method from `/api/v1/apps`
+
+- ([#37114](https://github.com/RocketChat/Rocket.Chat/pull/37114)) Removes stdout logging functionality, related components and settings
+
+- ([#36647](https://github.com/RocketChat/Rocket.Chat/pull/36647)) Removes deprecated 'e2e.updateGroupKey' method and related type declarations
+
+- ([#37421](https://github.com/RocketChat/Rocket.Chat/pull/37421)) Removes deprecated `livechat:saveCustomField` method
+
+- ([#36896](https://github.com/RocketChat/Rocket.Chat/pull/36896)) Removes the deprecated roleName parameter from /api/v1/roles.addUserToRole and /api/v1/roles.removeUserFromRole
+
+ Removes the ability to pass a role name to the role parameter type from /api/v1/roles.getUsersInRole
+
+- ([#36828](https://github.com/RocketChat/Rocket.Chat/pull/36828)) Removes the deprecated `setUserPassword` method
+
+- ([#37285](https://github.com/RocketChat/Rocket.Chat/pull/37285)) Promotes Enhanced Navigation from preview state to stable.
+
+- ([#37464](https://github.com/RocketChat/Rocket.Chat/pull/37464)) Removes deprecated `livechat:takeInquiry` method
+
+- ([#36865](https://github.com/RocketChat/Rocket.Chat/pull/36865)) Removes the deprecated `authorization:addUserToRole` method
+
+### Minor Changes
+
+- ([#36570](https://github.com/RocketChat/Rocket.Chat/pull/36570)) REST endpoint `/v1/users.createToken` is not deprecated anymore. It now requires a `secret` parameter to generate a token for a user. This change is part of the effort to enhance security by ensuring that tokens are generated with an additional layer of validation. The `secret` parameter is validated against a new environment variable `CREATE_TOKENS_FOR_USERS_SECRET`.
+
+- ([#37719](https://github.com/RocketChat/Rocket.Chat/pull/37719)) Adds a new method to the Apps-Engine that allows apps to retrieve multiple rooms from database
+
+- ([#37659](https://github.com/RocketChat/Rocket.Chat/pull/37659)) Changes the position of the buttons in Unique ID change detected modal in order to highlight configuration update instead of new workspace
+
+- ([#37233](https://github.com/RocketChat/Rocket.Chat/pull/37233)) Validates attachment fields to require `title` and `value` properties on APIs `chat.postMessage` and `chat.sendMessage`.
+
+- ([#37224](https://github.com/RocketChat/Rocket.Chat/pull/37224)) Enhance user's deactivated state handling to correctly distinguish between pending and deactivated users.
+
+- ([#37091](https://github.com/RocketChat/Rocket.Chat/pull/37091)) Adds Attribute Based Access Control (ABAC) for private channels & private teams.
+
+- ([#37771](https://github.com/RocketChat/Rocket.Chat/pull/37771)) Introduces an info button to voice call's in-chat history message, which opens a contextual bar with more detailed information about the voice call.
+
+### Patch Changes
+
+- ([#37663](https://github.com/RocketChat/Rocket.Chat/pull/37663)) Removes the deprecated meteor method: `livechat:saveTag`
+
+- ([#37688](https://github.com/RocketChat/Rocket.Chat/pull/37688)) Adds deprecation warning for `livechat:removeMonitor` and new endpoint replacing it; `livechat/monitor.remove`
+
+- ([#37690](https://github.com/RocketChat/Rocket.Chat/pull/37690)) Adds a deprecation warning for `livechat:saveBusinessHour` and new endpoint replacing it; `livechat/business-hours.save`
+
+- ([#37612](https://github.com/RocketChat/Rocket.Chat/pull/37612)) Adds invitation request support to rooms
+
+- ([#37745](https://github.com/RocketChat/Rocket.Chat/pull/37745)) Fixes an issue where its not being possible to change the password in account security page
+
+- ([#37721](https://github.com/RocketChat/Rocket.Chat/pull/37721)) Disables read receipts indicators in federated rooms. This feature will be re-enabled when fully compatible with federation.
+
+- ([#37823](https://github.com/RocketChat/Rocket.Chat/pull/37823)) Fixes members tab > add members not removing selected items
+
+- ([#37791](https://github.com/RocketChat/Rocket.Chat/pull/37791)) Fixes an issue where cases of invites that were canceled or disinvited were not being handled.
+
+- ([#37874](https://github.com/RocketChat/Rocket.Chat/pull/37874)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates)
+
+- ([#36822](https://github.com/RocketChat/Rocket.Chat/pull/36822)) Removes deprecated meteor method `unmuteUserInRoom`
+
+- ([#37819](https://github.com/RocketChat/Rocket.Chat/pull/37819)) Removes deprecated method `livechat:removeBusinessHour`
+
+- ([#37654](https://github.com/RocketChat/Rocket.Chat/pull/37654)) Fixes an issue that could cause slashcommands to disappear for the user in certain high-availability scenarios
+
+- ([#37443](https://github.com/RocketChat/Rocket.Chat/pull/37443)) Fixes an issue where custom status is not updating immediately if the value is empty
+
+- ([#37730](https://github.com/RocketChat/Rocket.Chat/pull/37730)) Adds an execution flag to apps-engine runtime that helps prevent the publishing of faulty builds
+
+- ([#37722](https://github.com/RocketChat/Rocket.Chat/pull/37722)) Fixes an issue with the build that prevented Deno runtime to run on air-gapped environments
+
+- ([#37504](https://github.com/RocketChat/Rocket.Chat/pull/37504)) Fixes create channel modal not validating federated access permission
+
+- ([#37523](https://github.com/RocketChat/Rocket.Chat/pull/37523)) Fixes license add-on validations for federated rooms
+
+- ([#37061](https://github.com/RocketChat/Rocket.Chat/pull/37061)) Adds deprecation warning on `livechat:addMonitor` with new endpoint replacing it; `livechat/monitors.create`
+
+- ([#37713](https://github.com/RocketChat/Rocket.Chat/pull/37713)) Fixes a condition where the `SAML_Custom_Default_default_user_role` setting, used to define the default SAML role when none is provided, would fail when a role name was used instead of an ID.
+
+- ([#36827](https://github.com/RocketChat/Rocket.Chat/pull/36827)) Removes deprecated method `saveUserProfile`
+
+- ([#37643](https://github.com/RocketChat/Rocket.Chat/pull/37643)) Adds invitation badge to room members list
+
+- ([#37664](https://github.com/RocketChat/Rocket.Chat/pull/37664)) Removes the deprecated meteor method: `livechat:addMonitor`
+
+- ([#37829](https://github.com/RocketChat/Rocket.Chat/pull/37829)) Fixes an issue where iframe external commands sent via `window.postMessage` were not being handled correctly when Rocket.Chat was embedded inside an iframe.
+
+- ([#37717](https://github.com/RocketChat/Rocket.Chat/pull/37717)) Fixes incorrect URL generation in Global Search "Jump to message" feature, resolving navigation issues when jumping to messages across different channels.
+
+- ([#37845](https://github.com/RocketChat/Rocket.Chat/pull/37845)) Fixes push notifications continuing after logout due to missing token cleanup.
+
+- ([#37822](https://github.com/RocketChat/Rocket.Chat/pull/37822)) Fixes an issue where it‘s not being possible to configure department's `chatClosingTags` without enabling `requestTagBeforeClosingTag`
+
+- ([#37707](https://github.com/RocketChat/Rocket.Chat/pull/37707)) Fixes an issue that caused Third-party login to not work properly
+
+- ([#37662](https://github.com/RocketChat/Rocket.Chat/pull/37662)) removes the deprecated meteor method: `livechat:removeMonitor`
+
+- ([#37852](https://github.com/RocketChat/Rocket.Chat/pull/37852)) Adds improvements to the push notifications logic; the logic now truncates messages and titles larger than 240, and 65 characters respectively.
+
+- ([#37281](https://github.com/RocketChat/Rocket.Chat/pull/37281)) Adds deprecation warning for `livechat:saveTag` and new endpoint to replace it; `livechat/tags.save`
+
+- ([#37656](https://github.com/RocketChat/Rocket.Chat/pull/37656)) Fixes an issue where the client failed to load properly when the “First Channel After Login” setting began with a hash (#), ensuring users are routed to the correct channel.
+
+- ([#37846](https://github.com/RocketChat/Rocket.Chat/pull/37846)) Fixes /v1/users.logout not marking user sessions as logged out, leaving stale sessions active.
+
+- ([#37551](https://github.com/RocketChat/Rocket.Chat/pull/37551)) Ensures presence stays accurate by refreshing connections on heartbeats and removing stale sessions.
+
+- ([#37677](https://github.com/RocketChat/Rocket.Chat/pull/37677)) Fixes an issue where membership updates were not reflected when the user was the first member on their own server.
+
+- ([#37718](https://github.com/RocketChat/Rocket.Chat/pull/37718)) Fixes the missing dispatch of `startup` iframe event on client startup.
+
+- ([#37729](https://github.com/RocketChat/Rocket.Chat/pull/37729)) Fixes inconsistency in roomLeft event payload by aligning it to the standard outgoing events signature.
+
+- ([#37729](https://github.com/RocketChat/Rocket.Chat/pull/37729)) Removes sensitive data from outgoing events debug logs.
+
+- ([#37635](https://github.com/RocketChat/Rocket.Chat/pull/37635)) Adds invitation badge to sidebar
+
+- ([#37772](https://github.com/RocketChat/Rocket.Chat/pull/37772)) Removes deprecated method `livechat:saveBusinessHour`
+
+- ([#37775](https://github.com/RocketChat/Rocket.Chat/pull/37775)) Adds deprecation warning for `livechat:removeBusinessHour` and new endpoint to replace it; `livechat/business-hours.remove`
+
+- Updated dependencies [347b8f973440f3e2239f79c00c2d9b430859eef2, 04d24848bd8733caefc45d42c53f004177865a53, b802430fbfdc7fa69a976468dc6dee6f3c4de26f, 872da49986436d2efa65fc42e416b45d706fd59c, 0ccb9692b434bc88b4bc5009d39e024c03f01b86, 176d5eae3fb249d7d20c3e260d9fadc1a56a2fca, ac11ea05ffadeca978c794ff38d5199d9acb2c29, 2de4547580c472f4458568629d7bf98fd5faf342, ac11ea05ffadeca978c794ff38d5199d9acb2c29, 1baa03cced8f94584da1224ad59cad86f219707a, 0c0258604632342f42fc36cabac2d6cfe0e477c4, f3f0b273ab49e9d1048ba43d52eb36005274905e, ac11ea05ffadeca978c794ff38d5199d9acb2c29, a1d65f493fdb039d34ef4f65d243a97931763f4b, 70872896b912004dc016297b6e875d873d81dc3a, bd5edfc2993c93bd77f42dcd30d38b57eeb50481, 94b87d9ef40647d77fe83f3f84dca46a94515b39, ddc935727e9a7275813006d9dcaa7fe866610844, 733c94b996204151f580de2dd7f3402124b70977, d3538e7045c41f91b8c561d44e5485ff93b93745, a5a7343a835b04812c70699be1b13e54f0e10d48, 73d9eb2783176954f42aa2cbeda8abf1d49ac260, 611e4cdfa04849416a58071646b853b95e9b817b, 476a070b0099b95e4c463ee85960c4dcfbd87120, 239f4b1171bcf448cfba345cc90c4b5cd7c21afc, 9e03ed5c5ea829c62c2da2de9413a27a4696f8a3, dc67590d14d510b069dee074c55314c56f74bb11, be80b724a636877294b5e5baa501d070941131dd, 4aa3634186d97f4144c39f6b42a65107d3d30df0, 4793aca8796d8a3b4c645a2ed685028067119d8d, 0b660a5933b137ae142d78318d8c4022f1f4f1ca, dccdcc5b4a0da4814f72a020bc4eccb8ea2497d8, 5ac1863be4c6e82666989f4b569928c0805691ff, ec0f8b435dd12c218adffa8892737c7ced4debb8, f056c451c2926e849f52b95fed957945398ef5f6, cb3c5e3455606a045f95f168dae6ed32a387697c, 5b3f93c47a03b628d613a77005e92021cd6cee4b, 8bf0bab1eb84f903976b7833691d17236eac8dcd, 55dc368f3f679e93bffb9f04efe3944832cf3336]:
+
+ - @rocket.chat/rest-typings@8.0.0-rc.0
+ - @rocket.chat/web-ui-registration@26.0.0-rc.0
+ - @rocket.chat/apps-engine@1.59.0-rc.0
+ - @rocket.chat/apps@0.6.0-rc.0
+ - @rocket.chat/core-typings@8.0.0-rc.0
+ - @rocket.chat/i18n@2.0.0-rc.0
+ - @rocket.chat/media-calls@0.2.0-rc.0
+ - @rocket.chat/model-typings@2.0.0-rc.0
+ - @rocket.chat/ui-contexts@26.0.0-rc.0
+ - @rocket.chat/ui-voip@16.0.0-rc.0
+ - @rocket.chat/models@2.0.0-rc.0
+ - @rocket.chat/core-services@0.12.0-rc.0
+ - @rocket.chat/message-types@0.1.0-rc.0
+ - @rocket.chat/federation-matrix@0.0.9-rc.0
+ - @rocket.chat/gazzodown@26.0.0-rc.0
+ - @rocket.chat/ui-client@26.0.0-rc.0
+ - @rocket.chat/fuselage-ui-kit@26.0.0-rc.0
+ - @rocket.chat/media-signaling@0.1.1-rc.0
+ - @rocket.chat/ui-kit@0.39.0-rc.0
+ - @rocket.chat/abac@0.1.0-rc.0
+ - @rocket.chat/jwt@0.2.0-rc.0
+ - @rocket.chat/tools@0.2.4-rc.0
+ - @rocket.chat/presence@0.2.47-rc.0
+ - @rocket.chat/logger@1.0.0-rc.0
+ - @rocket.chat/omnichannel-services@0.3.44-rc.0
+ - @rocket.chat/api-client@0.2.47-rc.0
+ - @rocket.chat/http-router@7.9.14-rc.0
+ - @rocket.chat/license@1.1.7-rc.0
+ - @rocket.chat/pdf-worker@0.3.26-rc.0
+ - @rocket.chat/cron@0.1.47-rc.0
+ - @rocket.chat/ui-avatar@22.0.0-rc.0
+ - @rocket.chat/ui-theming@0.4.4
+ - @rocket.chat/ui-video-conf@26.0.0-rc.0
+ - @rocket.chat/omni-core-ee@0.0.12-rc.0
+ - @rocket.chat/instance-status@0.1.47-rc.0
+ - @rocket.chat/omni-core@0.0.12-rc.0
+ - @rocket.chat/network-broker@0.2.26-rc.0
+ - @rocket.chat/server-cloud-communication@0.0.2
+
+
## 7.13.2
### Patch Changes
diff --git a/apps/meteor/app/2fa/server/code/index.ts b/apps/meteor/app/2fa/server/code/index.ts
index d4be080259cf4..cfe7b8ab7d4d4 100644
--- a/apps/meteor/app/2fa/server/code/index.ts
+++ b/apps/meteor/app/2fa/server/code/index.ts
@@ -9,6 +9,7 @@ import { EmailCheck } from './EmailCheck';
import type { ICodeCheck } from './ICodeCheck';
import { PasswordCheckFallback } from './PasswordCheckFallback';
import { TOTPCheck } from './TOTPCheck';
+import { normalizeHeaders } from '../../../lib/server/functions/getModifiedHttpHeaders';
import { settings } from '../../../settings/server';
export interface ITwoFactorOptions {
@@ -184,9 +185,11 @@ export async function checkCodeForUser({ user, code, method, options = {}, conne
throw new Meteor.Error('totp-user-not-found', 'TOTP User not found');
}
- if (!code && !method && connection?.httpHeaders?.['x-2fa-code'] && connection.httpHeaders['x-2fa-method']) {
- code = connection.httpHeaders['x-2fa-code'];
- method = connection.httpHeaders['x-2fa-method'];
+ const headers = normalizeHeaders(connection?.httpHeaders);
+
+ if (!code && !method && headers?.['x-2fa-code'] && headers['x-2fa-method']) {
+ code = headers['x-2fa-code'];
+ method = headers['x-2fa-method'];
}
if (connection && isAuthorizedForToken(connection, existingUser, options)) {
diff --git a/apps/meteor/app/2fa/server/loginHandler.ts b/apps/meteor/app/2fa/server/loginHandler.ts
index 1d039c7a89e4a..772e3cff93696 100644
--- a/apps/meteor/app/2fa/server/loginHandler.ts
+++ b/apps/meteor/app/2fa/server/loginHandler.ts
@@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor';
import { OAuth } from 'meteor/oauth';
import { checkCodeForUser } from './code/index';
-import { callbacks } from '../../../lib/callbacks';
+import { callbacks } from '../../../server/lib/callbacks';
const isMeteorError = (error: any): error is Meteor.Error => {
return error?.meteorError !== undefined;
diff --git a/apps/meteor/app/api/server/ApiClass.ts b/apps/meteor/app/api/server/ApiClass.ts
index 24fc53910edb3..413a7de4bf1e9 100644
--- a/apps/meteor/app/api/server/ApiClass.ts
+++ b/apps/meteor/app/api/server/ApiClass.ts
@@ -28,7 +28,6 @@ import type {
NotFoundResult,
Operations,
Options,
- PartialThis,
SuccessResult,
TypedThis,
TypedAction,
@@ -37,6 +36,8 @@ import type {
RedirectStatusCodes,
RedirectResult,
UnavailableResult,
+ GenericRouteExecutionContext,
+ TooManyRequestsResult,
} from './definition';
import { getUserInfo } from './helpers/getUserInfo';
import { parseJsonQuery } from './helpers/parseJsonQuery';
@@ -156,12 +157,7 @@ const generateConnection = (
clientAddress: ipAddress,
});
-export class APIClass<
- TBasePath extends string = '',
- TOperations extends {
- [x: string]: unknown;
- } = {},
-> {
+export class APIClass = Record> {
public typedRoutes: Record> = {};
protected apiPath?: string;
@@ -170,7 +166,7 @@ export class APIClass<
private _routes: { path: string; options: Options; endpoints: Record }[] = [];
- public authMethods: ((...args: any[]) => any)[];
+ public authMethods: ((routeContext: GenericRouteExecutionContext) => Promise)[];
protected helperMethods: Map any> = new Map();
@@ -248,11 +244,11 @@ export class APIClass<
};
}
- async parseJsonQuery(this: PartialThis) {
- return parseJsonQuery(this);
+ async parseJsonQuery(routeContext: GenericRouteExecutionContext) {
+ return parseJsonQuery(routeContext);
}
- public addAuthMethod(func: (this: PartialThis, ...args: any[]) => any): void {
+ public addAuthMethod(func: (routeContext: GenericRouteExecutionContext) => Promise): void {
this.authMethods.push(func);
}
@@ -388,7 +384,7 @@ export class APIClass<
};
}
- public tooManyRequests(msg?: string): { statusCode: number; body: Record & { success?: boolean } } {
+ public tooManyRequests(msg?: T): TooManyRequestsResult {
return {
statusCode: 429,
body: {
@@ -818,12 +814,21 @@ export class APIClass<
}
// Add a try/catch for each endpoint
const originalAction = (operations[method as keyof Operations] as Record).action;
+
+ if (options.deprecation && shouldBreakInVersion(options.deprecation.version)) {
+ throw new Meteor.Error('error-deprecated', `The endpoint ${route} should be removed`);
+ }
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
const api = this;
(operations[method as keyof Operations] as Record).action =
async function _internalRouteActionHandler() {
+ this.queryOperations = options.queryOperations;
+ this.queryFields = options.queryFields;
+ this.logger = logger;
+
if (options.authRequired || options.authOrAnonRequired) {
- const user = await api.authenticatedRoute.call(this, this.request);
+ const user = await api.authenticatedRoute(this);
this.user = user!;
this.userId = this.user?._id;
const authToken = this.request.headers.get('x-auth-token');
@@ -918,9 +923,7 @@ export class APIClass<
connection: connection as unknown as IMethodConnection,
}));
- this.queryOperations = options.queryOperations;
- (this as any).queryFields = options.queryFields;
- this.parseJsonQuery = api.parseJsonQuery.bind(this as unknown as PartialThis);
+ this.parseJsonQuery = () => api.parseJsonQuery(this);
result = (await DDP._CurrentInvocation.withValue(invocation as any, async () => originalAction.apply(this))) || api.success();
} catch (e: any) {
@@ -972,12 +975,9 @@ export class APIClass<
});
}
- protected async authenticatedRoute(req: Request): Promise {
- const headers = Object.fromEntries(req.headers.entries());
-
- const { 'x-user-id': userId } = headers;
-
- const userToken = String(headers['x-auth-token']);
+ protected async authenticatedRoute(routeContext: GenericRouteExecutionContext): Promise {
+ const userId = routeContext.request.headers.get('x-user-id');
+ const userToken = routeContext.request.headers.get('x-auth-token');
if (userId && userToken) {
return Users.findOne(
@@ -990,6 +990,16 @@ export class APIClass<
},
);
}
+
+ for (const method of this.authMethods) {
+ // eslint-disable-next-line no-await-in-loop -- we want serial execution
+ const user = await method(routeContext);
+
+ if (user) {
+ return user;
+ }
+ }
+
return null;
}
diff --git a/apps/meteor/app/api/server/default/info.ts b/apps/meteor/app/api/server/default/info.ts
index 8297f90fffd98..9f2a9d79e4a54 100644
--- a/apps/meteor/app/api/server/default/info.ts
+++ b/apps/meteor/app/api/server/default/info.ts
@@ -12,19 +12,3 @@ API.default.addRoute(
},
},
);
-
-API.default.addRoute(
- 'ecdh_proxy/initEncryptedSession',
- { authRequired: false },
- {
- post() {
- return {
- statusCode: 200,
- body: {
- success: false,
- error: 'Not Acceptable',
- },
- };
- },
- },
-);
diff --git a/apps/meteor/app/api/server/default/openApi.ts b/apps/meteor/app/api/server/default/openApi.ts
index 28188302f570a..d59e6b8b08c22 100644
--- a/apps/meteor/app/api/server/default/openApi.ts
+++ b/apps/meteor/app/api/server/default/openApi.ts
@@ -6,8 +6,8 @@ import { WebApp } from 'meteor/webapp';
import swaggerUi from 'swagger-ui-express';
import { settings } from '../../../settings/server';
-import { Info } from '../../../utils/rocketchat.info';
import { API } from '../api';
+import { getTrimmedServerVersion } from '../lib/getTrimmedServerVersion';
const app = express();
@@ -46,7 +46,7 @@ const makeOpenAPIResponse = (paths: Record>) => ({
info: {
title: 'Rocket.Chat API',
description: 'Rocket.Chat API',
- version: Info.version,
+ version: getTrimmedServerVersion(),
},
servers: [
{
diff --git a/apps/meteor/app/api/server/definition.ts b/apps/meteor/app/api/server/definition.ts
index 029c3ac223391..9684b99c798c8 100644
--- a/apps/meteor/app/api/server/definition.ts
+++ b/apps/meteor/app/api/server/definition.ts
@@ -56,6 +56,14 @@ export type ForbiddenResult = {
};
};
+export type TooManyRequestsResult = {
+ statusCode: 429;
+ body: {
+ success: false;
+ error: T | 'Too many requests';
+ };
+};
+
export type InternalError = {
statusCode: StatusCode;
body: {
@@ -142,21 +150,16 @@ export type SharedOptions = (
};
};
-export type PartialThis = {
- user(bodyParams: Record, user: any): Promise;
- readonly request: Request & { query: Record };
- readonly response: Response;
- readonly userId: string;
- readonly bodyParams: Record;
- readonly path: string;
- readonly queryParams: Record;
- readonly queryOperations?: string[];
- readonly queryFields?: string[];
- readonly logger: Logger;
- readonly route: string;
-};
+export type GenericRouteExecutionContext = ActionThis;
+
+export type RouteExecutionContext = ActionThis<
+ TMethod,
+ TPathPattern,
+ TOptions
+>;
export type ActionThis = {
+ readonly logger: Logger;
route: string;
readonly requestIp: string;
urlParams: UrlParams;
@@ -183,6 +186,8 @@ export type ActionThis;
/**
diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts
index ea307c9f59844..bd7fc4f673dc0 100644
--- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts
+++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts
@@ -1,10 +1,11 @@
import ejson from 'ejson';
import { Meteor } from 'meteor/meteor';
+import { isPlainObject } from '../../../../lib/utils/isPlainObject';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { API } from '../api';
-import type { PartialThis } from '../definition';
+import type { GenericRouteExecutionContext } from '../definition';
import { clean } from '../lib/cleanQuery';
import { isValidQuery } from '../lib/isValidQuery';
@@ -13,7 +14,7 @@ const pathAllowConf = {
'def': ['$or', '$and', '$regex'],
};
-export async function parseJsonQuery(api: PartialThis): Promise<{
+export async function parseJsonQuery(api: GenericRouteExecutionContext): Promise<{
sort: Record;
/**
* @deprecated To access "fields" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
@@ -24,10 +25,14 @@ export async function parseJsonQuery(api: PartialThis): Promise<{
*/
query: Record;
}> {
- const { userId, queryParams: params, logger, queryFields, queryOperations, response, route } = api;
+ const { userId = '', response, route, logger } = api;
+
+ const params = isPlainObject(api.queryParams) ? api.queryParams : {};
+ const queryFields = Array.isArray(api.queryFields) ? (api.queryFields as string[]) : [];
+ const queryOperations = Array.isArray(api.queryOperations) ? (api.queryOperations as string[]) : [];
let sort;
- if (params.sort) {
+ if (typeof params?.sort === 'string') {
try {
sort = JSON.parse(params.sort);
Object.entries(sort).forEach(([key, value]) => {
@@ -50,9 +55,9 @@ export async function parseJsonQuery(api: PartialThis): Promise<{
`The usage of the "${parameter}" parameter in endpoint "${endpoint}" breaks the security of the API and can lead to data exposure. It has been deprecated and will be removed in the version ${version}.`;
let fields: Record | undefined;
- if (params.fields && isUnsafeQueryParamsAllowed) {
+ if (typeof params?.fields === 'string' && isUnsafeQueryParamsAllowed) {
try {
- apiDeprecationLogger.parameter(route, 'fields', '8.0.0', response, messageGenerator);
+ apiDeprecationLogger.parameter(route, 'fields', '9.0.0', response, messageGenerator);
fields = JSON.parse(params.fields) as Record;
Object.entries(fields).forEach(([key, value]) => {
if (value !== 1 && value !== 0) {
@@ -100,8 +105,8 @@ export async function parseJsonQuery(api: PartialThis): Promise<{
}
let query: Record = {};
- if (params.query && isUnsafeQueryParamsAllowed) {
- apiDeprecationLogger.parameter(route, 'query', '8.0.0', response, messageGenerator);
+ if (typeof params?.query === 'string' && isUnsafeQueryParamsAllowed) {
+ apiDeprecationLogger.parameter(route, 'query', '9.0.0', response, messageGenerator);
try {
query = ejson.parse(params.query);
query = clean(query, pathAllowConf.def);
diff --git a/apps/meteor/app/api/server/index.ts b/apps/meteor/app/api/server/index.ts
index 971c0476ad5b0..77114be0b196e 100644
--- a/apps/meteor/app/api/server/index.ts
+++ b/apps/meteor/app/api/server/index.ts
@@ -9,6 +9,7 @@ import './helpers/parseJsonQuery';
import './default/info';
import './v1/assets';
import './v1/calendar';
+import './v1/call-history';
import './v1/channels';
import './v1/chat';
import './v1/cloud';
@@ -42,10 +43,6 @@ import './v1/banners';
import './v1/email-inbox';
import './v1/mailer';
import './v1/teams';
-import './v1/voip/extensions';
-import './v1/voip/queues';
-import './v1/voip/omnichannel';
-import './v1/voip';
import './v1/moderation';
// This has to come last so all endpoints are registered before generating the OpenAPI documentation
diff --git a/apps/meteor/app/api/server/lib/getServerInfo.spec.ts b/apps/meteor/app/api/server/lib/getServerInfo.spec.ts
index 574519d928581..7337d70609755 100644
--- a/apps/meteor/app/api/server/lib/getServerInfo.spec.ts
+++ b/apps/meteor/app/api/server/lib/getServerInfo.spec.ts
@@ -1,29 +1,36 @@
import { expect } from 'chai';
-import { describe, it } from 'mocha';
+import { describe, it, before } from 'mocha';
import proxyquire from 'proxyquire';
import sinon from 'sinon';
const hasAllPermissionAsyncMock = sinon.stub();
const getCachedSupportedVersionsTokenMock = sinon.stub();
-const { getServerInfo } = proxyquire.noCallThru().load('./getServerInfo', {
- '../../../utils/rocketchat.info': {
- Info: {
- version: '3.0.1',
- },
- },
- '../../../authorization/server/functions/hasPermission': {
- hasPermissionAsync: hasAllPermissionAsyncMock,
- },
- '../../../cloud/server/functions/supportedVersionsToken/supportedVersionsToken': {
- getCachedSupportedVersionsToken: getCachedSupportedVersionsTokenMock,
- },
- '../../../settings/server': {
- settings: new Map(),
- },
-});
// #ToDo: Fix those tests in a separate PR
describe.skip('#getServerInfo()', () => {
+ let getServerInfo: any;
+
+ before(() => {
+ const { getServerInfo: importedGetServerInfo } = proxyquire.noCallThru().load('./getServerInfo', {
+ '../../../utils/rocketchat.info': {
+ Info: {
+ version: '3.0.1',
+ },
+ },
+ '../../../authorization/server/functions/hasPermission': {
+ hasPermissionAsync: hasAllPermissionAsyncMock,
+ },
+ '../../../cloud/server/functions/supportedVersionsToken/supportedVersionsToken': {
+ getCachedSupportedVersionsToken: getCachedSupportedVersionsTokenMock,
+ },
+ '../../../settings/server': {
+ settings: new Map(),
+ },
+ });
+
+ getServerInfo = importedGetServerInfo;
+ });
+
beforeEach(() => {
hasAllPermissionAsyncMock.reset();
getCachedSupportedVersionsTokenMock.reset();
diff --git a/apps/meteor/app/api/server/lib/getServerInfo.ts b/apps/meteor/app/api/server/lib/getServerInfo.ts
index 020988b0aca42..4c65847c86ae6 100644
--- a/apps/meteor/app/api/server/lib/getServerInfo.ts
+++ b/apps/meteor/app/api/server/lib/getServerInfo.ts
@@ -1,5 +1,6 @@
import type { IWorkspaceInfo } from '@rocket.chat/core-typings';
+import { getTrimmedServerVersion } from './getTrimmedServerVersion';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import {
getCachedSupportedVersionsToken,
@@ -8,15 +9,13 @@ import {
import { settings } from '../../../settings/server';
import { Info, minimumClientVersions } from '../../../utils/rocketchat.info';
-const removePatchInfo = (version: string): string => version.replace(/(\d+\.\d+).*/, '$1');
-
export async function getServerInfo(userId?: string): Promise {
const hasPermissionToViewStatistics = userId && (await hasPermissionAsync(userId, 'view-statistics'));
const supportedVersionsToken = await wrapPromise(getCachedSupportedVersionsToken());
const cloudWorkspaceId = settings.get('Cloud_Workspace_Id');
return {
- version: removePatchInfo(Info.version),
+ version: getTrimmedServerVersion(),
...(hasPermissionToViewStatistics && {
info: {
...Info,
diff --git a/apps/meteor/app/api/server/lib/getTrimmedServerVersion.ts b/apps/meteor/app/api/server/lib/getTrimmedServerVersion.ts
new file mode 100644
index 0000000000000..c0ceb9e880d5c
--- /dev/null
+++ b/apps/meteor/app/api/server/lib/getTrimmedServerVersion.ts
@@ -0,0 +1,4 @@
+import { Info } from '../../../utils/rocketchat.info';
+
+// Removes the patch version from the server version string
+export const getTrimmedServerVersion = (): string => Info.version.replace(/(\d+\.\d+).*/, '$1');
diff --git a/apps/meteor/app/api/server/lib/users.ts b/apps/meteor/app/api/server/lib/users.ts
index e1451f4dd6698..00dfc3e8f5292 100644
--- a/apps/meteor/app/api/server/lib/users.ts
+++ b/apps/meteor/app/api/server/lib/users.ts
@@ -131,6 +131,7 @@ type FindPaginatedUsersByStatusProps = {
searchTerm: string;
hasLoggedIn: boolean;
type: string;
+ inactiveReason?: ('deactivated' | 'pending_approval' | 'idle_too_long')[];
};
export async function findPaginatedUsersByStatus({
@@ -143,6 +144,7 @@ export async function findPaginatedUsersByStatus({
searchTerm,
hasLoggedIn,
type,
+ inactiveReason,
}: FindPaginatedUsersByStatusProps) {
const actualSort: Record = sort || { username: 1 };
if (sort?.status) {
@@ -167,7 +169,6 @@ export async function findPaginatedUsersByStatus({
}
const canSeeAllUserInfo = await hasPermissionAsync(uid, 'view-full-other-user-info');
- const canSeeExtension = canSeeAllUserInfo || (await hasPermissionAsync(uid, 'view-user-voip-extension'));
const projection = {
name: 1,
@@ -181,7 +182,7 @@ export async function findPaginatedUsersByStatus({
type: 1,
reason: 1,
federated: 1,
- ...(canSeeExtension ? { freeSwitchExtension: 1 } : {}),
+ freeSwitchExtension: 1,
};
if (searchTerm?.trim()) {
@@ -199,6 +200,26 @@ export async function findPaginatedUsersByStatus({
match.roles = { $in: roles };
}
+ if (inactiveReason) {
+ const inactiveReasonCondition = {
+ $or: [
+ { inactiveReason: { $in: inactiveReason } },
+ // This condition is to make it backward compatible with the old behavior
+ // The deactivated users not having the inactiveReason field should be returned as well
+ ...(inactiveReason.includes('deactivated') || inactiveReason.includes('idle_too_long')
+ ? [{ inactiveReason: { $exists: false } }]
+ : []),
+ ],
+ };
+
+ if (match.$or) {
+ match.$and = [{ $or: match.$or }, inactiveReasonCondition];
+ delete match.$or;
+ } else {
+ Object.assign(match, inactiveReasonCondition);
+ }
+ }
+
const { cursor, totalCount } = Users.findPaginated(
{
...match,
diff --git a/apps/meteor/app/api/server/middlewares/authentication.ts b/apps/meteor/app/api/server/middlewares/authentication.ts
index e38da22fa793d..f2c7028a02601 100644
--- a/apps/meteor/app/api/server/middlewares/authentication.ts
+++ b/apps/meteor/app/api/server/middlewares/authentication.ts
@@ -27,7 +27,10 @@ export function authenticationMiddleware(
if (userId && authToken) {
req.user = (await Users.findOneByIdAndLoginToken(userId as string, hashLoginToken(authToken as string))) || undefined;
} else {
- req.user = (await oAuth2ServerAuth(req))?.user;
+ req.user = await oAuth2ServerAuth({
+ headers: req.headers as Record,
+ query: req.query as Record,
+ });
}
if (config.rejectUnauthorized && !req.user) {
diff --git a/apps/meteor/app/api/server/router.ts b/apps/meteor/app/api/server/router.ts
index 5921932804b02..1f18ea4c47228 100644
--- a/apps/meteor/app/api/server/router.ts
+++ b/apps/meteor/app/api/server/router.ts
@@ -31,7 +31,7 @@ export class RocketChatAPIRouter<
[x: string]: unknown;
} = NonNullable,
> extends Router {
- protected convertActionToHandler(action: APIActionHandler): (c: HonoContext) => Promise> {
+ protected override convertActionToHandler(action: APIActionHandler): (c: HonoContext) => Promise> {
return async (c: HonoContext): Promise> => {
const { req, res } = c;
const queryParams = this.parseQueryParams(req);
diff --git a/apps/meteor/app/api/server/v1/banners.ts b/apps/meteor/app/api/server/v1/banners.ts
index cecf9d4d9bad1..ff344362df6c5 100644
--- a/apps/meteor/app/api/server/v1/banners.ts
+++ b/apps/meteor/app/api/server/v1/banners.ts
@@ -1,73 +1,8 @@
import { Banner } from '@rocket.chat/core-services';
-import { isBannersDismissProps, isBannersGetNewProps, isBannersProps } from '@rocket.chat/rest-typings';
+import { isBannersDismissProps, isBannersProps } from '@rocket.chat/rest-typings';
import { API } from '../api';
-/**
- * @deprecated
- * @openapi
- * /api/v1/banners.getNew:
- * get:
- * description: Gets the banners to be shown to the authenticated user
- * deprecated: true
- * security:
- * $ref: '#/security/authenticated'
- * parameters:
- * - name: platform
- * in: query
- * description: The platform rendering the banner
- * required: true
- * schema:
- * type: string
- * enum: [web, mobile]
- * example: web
- * - name: bid
- * in: query
- * description: The id of a single banner
- * required: false
- * schema:
- * type: string
- * example: ByehQjC44FwMeiLbX
- * responses:
- * 200:
- * description: The banners matching the criteria
- * content:
- * application/json:
- * schema:
- * allOf:
- * - $ref: '#/components/schemas/ApiSuccessV1'
- * - type: object
- * properties:
- * banners:
- * type: array
- * items:
- * $ref: '#/components/schemas/IBanner'
- * default:
- * description: Unexpected error
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/ApiFailureV1'
- */
-API.v1.addRoute(
- 'banners.getNew',
- {
- authRequired: true,
- validateParams: isBannersGetNewProps,
- deprecation: { version: '8.0.0', alternatives: ['/v1/banners/:id', '/v1/banners'] },
- },
- {
- // deprecated
- async get() {
- const { platform, bid: bannerId } = this.queryParams;
-
- const banners = await Banner.getBannersForUser(this.userId, platform, bannerId ?? undefined);
-
- return API.v1.success({ banners });
- },
- },
-);
-
/**
* @openapi
* /api/v1/banners/{id}:
diff --git a/apps/meteor/app/api/server/v1/call-history.ts b/apps/meteor/app/api/server/v1/call-history.ts
new file mode 100644
index 0000000000000..606632571ac91
--- /dev/null
+++ b/apps/meteor/app/api/server/v1/call-history.ts
@@ -0,0 +1,263 @@
+import type { CallHistoryItem, CallHistoryItemState, IMediaCall } from '@rocket.chat/core-typings';
+import { CallHistory, MediaCalls } from '@rocket.chat/models';
+import type { PaginatedRequest, PaginatedResult } from '@rocket.chat/rest-typings';
+import {
+ ajv,
+ validateNotFoundErrorResponse,
+ validateBadRequestErrorResponse,
+ validateUnauthorizedErrorResponse,
+ validateForbiddenErrorResponse,
+} from '@rocket.chat/rest-typings';
+import { escapeRegExp } from '@rocket.chat/string-helpers';
+
+import { ensureArray } from '../../../../lib/utils/arrayUtils';
+import type { ExtractRoutesFromAPI } from '../ApiClass';
+import { API } from '../api';
+import { getPaginationItems } from '../helpers/getPaginationItems';
+
+type CallHistoryList = PaginatedRequest<{
+ filter?: string;
+ direction?: CallHistoryItem['direction'];
+ state?: CallHistoryItemState[] | CallHistoryItemState;
+}>;
+
+const CallHistoryListSchema = {
+ type: 'object',
+ properties: {
+ count: {
+ type: 'number',
+ },
+ offset: {
+ type: 'number',
+ },
+ sort: {
+ type: 'string',
+ },
+ filter: {
+ type: 'string',
+ },
+ direction: {
+ type: 'string',
+ enum: ['inbound', 'outbound'],
+ },
+ state: {
+ // our clients serialize arrays as `state=value1&state=value2`, but if there's a single value the parser doesn't know it is an array, so we need to support both arrays and direct values
+ // if a client tries to send a JSON array, our parser will treat it as a string and the type validation will reject it
+ // This means this param won't work from Swagger UI
+ oneOf: [
+ {
+ type: 'array',
+ items: {
+ $ref: '#/components/schemas/CallHistoryItemState',
+ },
+ },
+ {
+ $ref: '#/components/schemas/CallHistoryItemState',
+ },
+ ],
+ },
+ },
+ required: [],
+ additionalProperties: false,
+};
+
+export const isCallHistoryListProps = ajv.compile(CallHistoryListSchema);
+
+const callHistoryListEndpoints = API.v1.get(
+ 'call-history.list',
+ {
+ response: {
+ 200: ajv.compile<
+ PaginatedResult<{
+ items: CallHistoryItem[];
+ }>
+ >({
+ additionalProperties: false,
+ type: 'object',
+ properties: {
+ count: {
+ type: 'number',
+ description: 'The number of history items returned in this response.',
+ },
+ offset: {
+ type: 'number',
+ description: 'The number of history items that were skipped in this response.',
+ },
+ total: {
+ type: 'number',
+ description: 'The total number of history items that match the query.',
+ },
+ success: {
+ type: 'boolean',
+ description: 'Indicates if the request was successful.',
+ },
+ items: {
+ type: 'array',
+ items: {
+ $ref: '#/components/schemas/CallHistoryItem',
+ },
+ },
+ },
+ required: ['count', 'offset', 'total', 'items', 'success'],
+ }),
+ 400: validateBadRequestErrorResponse,
+ 401: validateUnauthorizedErrorResponse,
+ 403: validateForbiddenErrorResponse,
+ },
+ query: isCallHistoryListProps,
+ authRequired: true,
+ },
+ async function action() {
+ const { offset, count } = await getPaginationItems(this.queryParams as Record);
+ const { sort } = await this.parseJsonQuery();
+
+ const { direction, state, filter } = this.queryParams;
+
+ const filterText = typeof filter === 'string' && filter.trim();
+
+ const stateFilter = state && ensureArray(state);
+ const query = {
+ uid: this.userId,
+ ...(direction && { direction }),
+ ...(stateFilter?.length && { state: { $in: stateFilter } }),
+ ...(filterText && {
+ $or: [
+ {
+ external: false,
+ contactName: { $regex: escapeRegExp(filterText), $options: 'i' },
+ },
+ {
+ external: false,
+ contactUsername: { $regex: escapeRegExp(filterText), $options: 'i' },
+ },
+ {
+ external: true,
+ contactExtension: { $regex: escapeRegExp(filterText), $options: 'i' },
+ },
+ ],
+ }),
+ };
+
+ const { cursor, totalCount } = CallHistory.findPaginated(query, {
+ sort: sort || { ts: -1 },
+ skip: offset,
+ limit: count,
+ });
+ const [items, total] = await Promise.all([cursor.toArray(), totalCount]);
+
+ return API.v1.success({
+ items,
+ count: items.length,
+ offset,
+ total,
+ });
+ },
+);
+
+type CallHistoryListEndpoints = ExtractRoutesFromAPI;
+
+type CallHistoryInfo = { historyId: string } | { callId: string };
+
+const CallHistoryInfoSchema = {
+ oneOf: [
+ {
+ type: 'object',
+ properties: {
+ historyId: {
+ type: 'string',
+ nullable: false,
+ },
+ },
+ required: ['historyId'],
+ additionalProperties: false,
+ },
+ {
+ type: 'object',
+ properties: {
+ callId: {
+ type: 'string',
+ nullable: false,
+ },
+ },
+ required: ['callId'],
+ additionalProperties: false,
+ },
+ ],
+};
+
+export const isCallHistoryInfoProps = ajv.compile(CallHistoryInfoSchema);
+
+const callHistoryInfoEndpoints = API.v1.get(
+ 'call-history.info',
+ {
+ response: {
+ 200: ajv.compile<{
+ item: CallHistoryItem;
+ call?: IMediaCall;
+ }>({
+ additionalProperties: false,
+ type: 'object',
+ properties: {
+ item: {
+ $ref: '#/components/schemas/CallHistoryItem',
+ description: 'The requested call history item.',
+ },
+ call: {
+ type: 'object',
+ $ref: '#/components/schemas/IMediaCall',
+ description: 'The call information for the requested call history item.',
+ nullable: true,
+ },
+ success: {
+ type: 'boolean',
+ description: 'Indicates if the request was successful.',
+ },
+ },
+ required: ['item', 'success'],
+ }),
+ 400: validateBadRequestErrorResponse,
+ 401: validateUnauthorizedErrorResponse,
+ 403: validateForbiddenErrorResponse,
+ 404: validateNotFoundErrorResponse,
+ },
+ query: isCallHistoryInfoProps,
+ authRequired: true,
+ },
+ async function action() {
+ const { historyId, callId } = this.queryParams as Record & typeof this.queryParams;
+
+ if (!historyId && !callId) {
+ return API.v1.failure();
+ }
+
+ const item = await (historyId
+ ? CallHistory.findOneByIdAndUid(historyId, this.userId)
+ : CallHistory.findOneByCallIdAndUid(callId, this.userId));
+
+ if (!item) {
+ return API.v1.notFound();
+ }
+
+ if (item.type === 'media-call' && item.callId) {
+ const call = await MediaCalls.findOneById(item.callId);
+ if (call) {
+ return API.v1.success({
+ item,
+ call,
+ });
+ }
+ }
+
+ return API.v1.success({ item });
+ },
+);
+
+type CallHistoryInfoEndpoints = ExtractRoutesFromAPI;
+
+declare module '@rocket.chat/rest-typings' {
+ // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
+ interface Endpoints extends CallHistoryListEndpoints {}
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
+ interface Endpoints extends CallHistoryInfoEndpoints {}
+}
diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts
index 64dc7d1f6f2e0..25978aefce3e8 100644
--- a/apps/meteor/app/api/server/v1/chat.ts
+++ b/apps/meteor/app/api/server/v1/chat.ts
@@ -1,4 +1,3 @@
-import { Message } from '@rocket.chat/core-services';
import type { IMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
import { MessageTypes } from '@rocket.chat/message-types';
import { Messages, Users, Rooms, Subscriptions } from '@rocket.chat/models';
@@ -21,7 +20,6 @@ import {
isChatFollowMessageProps,
isChatUnfollowMessageProps,
isChatGetMentionedMessagesProps,
- isChatOTRProps,
isChatReactProps,
isChatGetDeletedMessagesProps,
isChatSyncThreadsListProps,
@@ -41,7 +39,6 @@ import { messageSearch } from '../../../../server/methods/messageSearch';
import { getMessageHistory } from '../../../../server/publications/messages';
import { roomAccessAttributes } from '../../../authorization/server';
import { canAccessRoomAsync, canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
-import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { deleteMessageValidatingPermission } from '../../../lib/server/functions/deleteMessage';
import { processWebhookMessage } from '../../../lib/server/functions/processWebhookMessage';
@@ -437,7 +434,7 @@ API.v1.addRoute(
}
const sent = await applyAirGappedRestrictionsValidation(() =>
- executeSendMessage(this.userId, this.bodyParams.message as Pick, this.bodyParams.previewUrls),
+ executeSendMessage(this.userId, this.bodyParams.message as Pick, { previewUrls: this.bodyParams.previewUrls }),
);
const [message] = await normalizeMessagesForUser([sent], this.userId);
@@ -459,7 +456,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.');
}
- await starMessage(this.userId, {
+ await starMessage(this.user, {
_id: msg._id,
rid: msg.rid,
starred: true,
@@ -481,7 +478,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.');
}
- await starMessage(this.userId, {
+ await starMessage(this.user, {
_id: msg._id,
rid: msg.rid,
starred: false,
@@ -906,28 +903,6 @@ API.v1.addRoute(
},
);
-API.v1.addRoute(
- 'chat.otr',
- { authRequired: true, validateParams: isChatOTRProps },
- {
- async post() {
- const { roomId, type: otrType } = this.bodyParams;
-
- const { username, type } = this.user;
-
- if (!username) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user');
- }
-
- await canSendMessageAsync(roomId, { uid: this.userId, username, type });
-
- await Message.saveSystemMessage(otrType, roomId, username, { _id: this.userId, username });
-
- return API.v1.success();
- },
- },
-);
-
API.v1.addRoute(
'chat.getURLPreview',
{ authRequired: true, validateParams: isChatGetURLPreviewProps },
diff --git a/apps/meteor/app/api/server/v1/e2e.ts b/apps/meteor/app/api/server/v1/e2e.ts
index 3686f2a7b9007..bfc70ba8b31a1 100644
--- a/apps/meteor/app/api/server/v1/e2e.ts
+++ b/apps/meteor/app/api/server/v1/e2e.ts
@@ -201,9 +201,6 @@ API.v1.addRoute(
{
authRequired: true,
validateParams: ise2eUpdateGroupKeyParamsPOST,
- deprecation: {
- version: '8.0.0',
- },
},
{
async post() {
diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts
index 099d17141f8b4..b7251e67b1829 100644
--- a/apps/meteor/app/api/server/v1/im.ts
+++ b/apps/meteor/app/api/server/v1/im.ts
@@ -22,7 +22,7 @@ import { openRoom } from '../../../../server/lib/openRoom';
import { createDirectMessage } from '../../../../server/methods/createDirectMessage';
import { hideRoomMethod } from '../../../../server/methods/hideRoom';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
-import { hasAtLeastOnePermissionAsync, hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
+import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings';
import { getRoomByNameOrIdWithOptionToJoin } from '../../../lib/server/functions/getRoomByNameOrIdWithOptionToJoin';
import { getChannelHistory } from '../../../lib/server/methods/getChannelHistory';
@@ -385,12 +385,6 @@ API.v1.addRoute(
...(status && { status: { $in: status } }),
};
- const canSeeExtension = await hasAtLeastOnePermissionAsync(
- this.userId,
- ['view-full-other-user-info', 'view-user-voip-extension'],
- room._id,
- );
-
const options: FindOptions = {
projection: {
_id: 1,
@@ -400,7 +394,7 @@ API.v1.addRoute(
statusText: 1,
utcOffset: 1,
federated: 1,
- ...(canSeeExtension && { freeSwitchExtension: 1 }),
+ freeSwitchExtension: 1,
},
skip: offset,
limit: count,
@@ -416,8 +410,26 @@ API.v1.addRoute(
const [members, total] = await Promise.all([cursor.toArray(), totalCount]);
+ // find subscriptions of those users
+ const subs = await Subscriptions.findByRoomIdAndUserIds(
+ room._id,
+ members.map((member) => member._id),
+ { projection: { u: 1, status: 1, ts: 1, roles: 1 } },
+ ).toArray();
+
+ const membersWithSubscriptionInfo = members.map((member) => {
+ const sub = subs.find((sub) => sub.u._id === member._id);
+
+ const { u: _u, ...subscription } = sub || {};
+
+ return {
+ ...member,
+ subscription,
+ };
+ });
+
return API.v1.success({
- members,
+ members: membersWithSubscriptionInfo,
count: members.length,
offset,
total,
diff --git a/apps/meteor/app/api/server/v1/integrations.ts b/apps/meteor/app/api/server/v1/integrations.ts
index 5fb1781aa1411..78d77fe007f39 100644
--- a/apps/meteor/app/api/server/v1/integrations.ts
+++ b/apps/meteor/app/api/server/v1/integrations.ts
@@ -36,9 +36,9 @@ API.v1.addRoute(
return API.v1.success({ integration: await addOutgoingIntegration(this.userId, this.bodyParams as INewOutgoingIntegration) });
case 'webhook-incoming':
return API.v1.success({ integration: await addIncomingIntegration(this.userId, this.bodyParams as INewIncomingIntegration) });
+ default:
+ return API.v1.failure('Invalid integration type.');
}
-
- return API.v1.failure('Invalid integration type.');
},
},
);
diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts
index ecdd8c58b052c..403ffcc29b4ce 100644
--- a/apps/meteor/app/api/server/v1/misc.ts
+++ b/apps/meteor/app/api/server/v1/misc.ts
@@ -23,7 +23,6 @@ import { SystemLogger } from '../../../../server/lib/logger/system';
import { browseChannelsMethod } from '../../../../server/methods/browseChannels';
import { spotlightMethod } from '../../../../server/publications/spotlight';
import { resetAuditedSettingByUser, updateAuditedByUser } from '../../../../server/settings/lib/auditedSettingUpdates';
-import { getLogs } from '../../../../server/stream/stdout';
import { passwordPolicy } from '../../../lib/server';
import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
@@ -437,15 +436,6 @@ API.v1.addRoute(
* schema:
* $ref: '#/components/schemas/ApiFailureV1'
*/
-API.v1.addRoute(
- 'stdout.queue',
- { authRequired: true, permissionsRequired: ['view-logs'] },
- {
- async get() {
- return API.v1.success({ queue: getLogs() });
- },
- },
-);
declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -535,7 +525,8 @@ API.v1.addRoute(
if (settings.get('Log_Level') === '2') {
Meteor._debug(`Exception while invoking method ${method}`, err);
}
- return API.v1.success(mountResult({ id, error: err }));
+
+ return API.v1.failure(mountResult({ id, error: err }));
}
},
},
@@ -590,7 +581,7 @@ API.v1.addRoute(
if (settings.get('Log_Level') === '2') {
Meteor._debug(`Exception while invoking method ${method}`, err);
}
- return API.v1.success(mountResult({ id, error: err }));
+ return API.v1.failure(mountResult({ id, error: err }));
}
},
},
diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts
index 3af8b8002b532..26a5bd0cfd482 100644
--- a/apps/meteor/app/api/server/v1/oauthapps.ts
+++ b/apps/meteor/app/api/server/v1/oauthapps.ts
@@ -8,7 +8,6 @@ import {
} from '@rocket.chat/rest-typings';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
-import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { addOAuthApp } from '../../../oauth2-server-config/server/admin/functions/addOAuthApp';
import { deleteOAuthApp } from '../../../oauth2-server-config/server/admin/methods/deleteOAuthApp';
import { updateOAuthApp } from '../../../oauth2-server-config/server/admin/methods/updateOAuthApp';
@@ -88,7 +87,7 @@ const UpdateOAuthAppParamsSchema = {
const isUpdateOAuthAppParams = ajv.compile(UpdateOAuthAppParamsSchema);
-type OauthAppsGetParams = { clientId: string } | { appId: string } | { _id: string };
+type OauthAppsGetParams = { clientId: string } | { _id: string };
const oauthAppsGetParamsSchema = {
oneOf: [
@@ -112,16 +111,6 @@ const oauthAppsGetParamsSchema = {
required: ['clientId'],
additionalProperties: false,
},
- {
- type: 'object',
- properties: {
- appId: {
- type: 'string',
- },
- },
- required: ['appId'],
- additionalProperties: false,
- },
],
};
@@ -292,10 +281,6 @@ const oauthAppsEndpoints = API.v1
return API.v1.failure('OAuth app not found.');
}
- if ('appId' in this.queryParams) {
- apiDeprecationLogger.parameter(this.route, 'appId', '7.0.0', this.response);
- }
-
return API.v1.success({
oauthApp,
});
diff --git a/apps/meteor/app/api/server/v1/roles.ts b/apps/meteor/app/api/server/v1/roles.ts
index 10c3a9fd78389..8985e48c0aff5 100644
--- a/apps/meteor/app/api/server/v1/roles.ts
+++ b/apps/meteor/app/api/server/v1/roles.ts
@@ -10,7 +10,6 @@ import { getUsersInRolePaginated } from '../../../authorization/server/functions
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { hasRoleAsync, hasAnyRoleAsync } from '../../../authorization/server/functions/hasRole';
import { addUserToRole } from '../../../authorization/server/methods/addUserToRole';
-import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { notifyOnRoleChanged } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server/index';
import type { ExtractRoutesFromAPI } from '../ApiClass';
@@ -64,17 +63,13 @@ API.v1.addRoute(
}
const user = await getUserFromParams(this.bodyParams);
- const { roleId, roleName, roomId } = this.bodyParams;
+ const { roleId, roomId } = this.bodyParams;
if (!roleId) {
- if (!roleName) {
- return API.v1.failure('error-invalid-role-properties');
- }
-
- apiDeprecationLogger.parameter(this.route, 'roleName', '7.0.0', this.response);
+ return API.v1.failure('error-invalid-role-properties');
}
- const role = roleId ? await Roles.findOneById(roleId) : await Roles.findOneByIdOrName(roleName as string);
+ const role = await Roles.findOneById(roleId);
if (!role) {
return API.v1.failure('error-role-not-found', 'Role not found');
}
@@ -117,21 +112,10 @@ API.v1.addRoute(
}
const options = { projection: { _id: 1 } };
- let roleData = await Roles.findOneById>(role, options);
- if (!roleData) {
- roleData = await Roles.findOneByName>(role, options);
- if (!roleData) {
- throw new Meteor.Error('error-invalid-roleId');
- }
+ const roleData = await Roles.findOneById>(role, options);
- apiDeprecationLogger.deprecatedParameterUsage(
- this.route,
- 'role',
- '7.0.0',
- this.response,
- ({ parameter, endpoint, version }) =>
- `Querying \`${parameter}\` by name is deprecated in ${endpoint} and will be removed on the removed on version ${version}`,
- );
+ if (!roleData) {
+ throw new Meteor.Error('error-invalid-roleId');
}
const { cursor, totalCount } = await getUsersInRolePaginated(roleData._id, roomId, {
@@ -191,14 +175,10 @@ API.v1.addRoute(
throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.');
}
- const { roleId, roleName, username, scope } = bodyParams;
+ const { roleId, username, scope } = bodyParams;
if (!roleId) {
- if (!roleName) {
- return API.v1.failure('error-invalid-role-properties');
- }
-
- apiDeprecationLogger.parameter(this.route, 'roleName', '7.0.0', this.response);
+ return API.v1.failure('error-invalid-role-properties');
}
const user = await Users.findOneByUsername(username);
@@ -207,7 +187,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-invalid-user', 'There is no user with this username');
}
- const role = roleId ? await Roles.findOneById(roleId) : await Roles.findOneByIdOrName(roleName as string);
+ const role = await Roles.findOneById(roleId);
if (!role) {
throw new Meteor.Error('error-invalid-roleId', 'This role does not exist');
diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts
index 88558dc9a62df..068bcdf266500 100644
--- a/apps/meteor/app/api/server/v1/rooms.ts
+++ b/apps/meteor/app/api/server/v1/rooms.ts
@@ -1,4 +1,4 @@
-import { Media, MeteorError, Team } from '@rocket.chat/core-services';
+import { FederationMatrix, Media, MeteorError, Team } from '@rocket.chat/core-services';
import type { IRoom, IUpload } from '@rocket.chat/core-typings';
import { isPrivateRoom, isPublicRoom } from '@rocket.chat/core-typings';
import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models';
@@ -15,10 +15,14 @@ import {
isRoomsMembersOrderedByRoleProps,
isRoomsChangeArchivationStateProps,
isRoomsHideProps,
+ isRoomsInviteProps,
+ validateBadRequestErrorResponse,
+ validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';
import { isTruthy } from '../../../../lib/isTruthy';
+import { adminFields } from '../../../../lib/rooms/adminFields';
import { omit } from '../../../../lib/utils/omit';
import * as dataExport from '../../../../server/lib/dataExport';
import { eraseRoom } from '../../../../server/lib/eraseRoom';
@@ -184,74 +188,6 @@ API.v1.addRoute(
},
);
-API.v1.addRoute(
- 'rooms.upload/:rid',
- {
- authRequired: true,
- deprecation: {
- version: '8.0.0',
- alternatives: ['/v1/rooms.media/:rid'],
- },
- },
- {
- async post() {
- if (!(await canAccessRoomIdAsync(this.urlParams.rid, this.userId))) {
- return API.v1.forbidden();
- }
-
- const file = await getUploadFormData(
- {
- request: this.request,
- },
- { field: 'file', sizeLimit: settings.get('FileUpload_MaxFileSize') },
- );
-
- if (!file) {
- throw new Meteor.Error('invalid-field');
- }
-
- const { fields } = file;
- let { fileBuffer } = file;
-
- const details = {
- name: file.filename,
- size: fileBuffer.length,
- type: file.mimetype,
- rid: this.urlParams.rid,
- userId: this.userId,
- };
-
- const stripExif = settings.get('Message_Attachments_Strip_Exif');
- if (stripExif) {
- // No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc)
- fileBuffer = await Media.stripExifFromBuffer(fileBuffer);
- details.size = fileBuffer.length;
- }
-
- const fileStore = FileUpload.getStore('Uploads');
- const uploadedFile = await fileStore.insert(details, fileBuffer);
-
- if ((fields.description?.length ?? 0) > settings.get('Message_MaxAllowedSize')) {
- throw new Meteor.Error('error-message-size-exceeded');
- }
-
- uploadedFile.description = fields.description;
-
- delete fields.description;
-
- await applyAirGappedRestrictionsValidation(() =>
- sendFileMessage(this.userId, { roomId: this.urlParams.rid, file: uploadedFile, msgData: fields }),
- );
-
- const message = await Messages.getMessageByFileIdAndUsername(uploadedFile._id, this.userId);
-
- return API.v1.success({
- message,
- });
- },
- },
-);
-
API.v1.addRoute(
'rooms.media/:rid',
{ authRequired: true },
@@ -1029,51 +965,152 @@ const isRoomGetRolesPropsSchema = {
additionalProperties: false,
required: ['rid'],
};
-export const roomEndpoints = API.v1.get(
- 'rooms.roles',
- {
- authRequired: true,
- query: ajv.compile<{
- rid: string;
- }>(isRoomGetRolesPropsSchema),
- response: {
- 200: ajv.compile<{
- roles: RoomRoles[];
+export const roomEndpoints = API.v1
+ .get(
+ 'rooms.roles',
+ {
+ authRequired: true,
+ query: ajv.compile<{
+ rid: string;
+ }>(isRoomGetRolesPropsSchema),
+ response: {
+ 200: ajv.compile<{
+ roles: RoomRoles[];
+ }>({
+ type: 'object',
+ properties: {
+ roles: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ rid: { type: 'string' },
+ u: {
+ type: 'object',
+ properties: { _id: { type: 'string' }, username: { type: 'string' } },
+ required: ['_id', 'username'],
+ },
+ roles: { type: 'array', items: { type: 'string' } },
+ },
+ required: ['rid', 'u', 'roles'],
+ },
+ },
+ },
+ required: ['roles'],
+ }),
+ },
+ },
+ async function () {
+ const { rid } = this.queryParams;
+ const roles = await executeGetRoomRoles(rid, this.userId);
+
+ return API.v1.success({
+ roles,
+ });
+ },
+ )
+ .get(
+ 'rooms.adminRooms.privateRooms',
+ {
+ authRequired: true,
+ permissionsRequired: ['view-room-administration'],
+ query: ajv.compile<{
+ filter?: string;
+ offset?: number;
+ count?: number;
+ sort?: string;
}>({
type: 'object',
properties: {
- roles: {
- type: 'array',
- items: {
- type: 'object',
- properties: {
- rid: { type: 'string' },
- u: {
- type: 'object',
- properties: { _id: { type: 'string' }, username: { type: 'string' } },
- required: ['_id', 'username'],
- },
- roles: { type: 'array', items: { type: 'string' } },
- },
- required: ['rid', 'u', 'roles'],
+ filter: { type: 'string' },
+ offset: { type: 'number' },
+ count: { type: 'number' },
+ sort: { type: 'string' },
+ },
+ additionalProperties: true,
+ }),
+ response: {
+ 400: validateBadRequestErrorResponse,
+ 401: validateUnauthorizedErrorResponse,
+ 403: validateUnauthorizedErrorResponse,
+ 200: ajv.compile<{
+ rooms: IRoom[];
+ count: number;
+ offset: number;
+ total: number;
+ }>({
+ type: 'object',
+ properties: {
+ rooms: {
+ type: 'array',
+ items: { type: 'object' },
},
+ count: { type: 'number' },
+ offset: { type: 'number' },
+ total: { type: 'number' },
+ success: { type: 'boolean', enum: [true] },
},
+ required: ['rooms', 'count', 'offset', 'total', 'success'],
+ additionalProperties: false,
+ }),
+ },
+ },
+ async function action() {
+ const { offset, count } = await getPaginationItems(this.queryParams);
+ const { sort } = await this.parseJsonQuery();
+ const { filter } = this.queryParams;
+
+ const name = (filter || '').trim();
+
+ const { cursor, totalCount } = Rooms.findPrivateRoomsAndTeamsPaginated(name, {
+ skip: offset,
+ limit: count,
+ sort: sort || { default: -1, name: 1 },
+ projection: adminFields,
+ });
+
+ const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
+
+ return API.v1.success({
+ rooms,
+ count: rooms.length,
+ offset,
+ total,
+ });
+ },
+ );
+
+const roomInviteEndpoints = API.v1.post(
+ 'rooms.invite',
+ {
+ authRequired: true,
+ body: isRoomsInviteProps,
+ response: {
+ 400: validateBadRequestErrorResponse,
+ 401: validateUnauthorizedErrorResponse,
+ 200: ajv.compile({
+ type: 'object',
+ properties: {
+ success: { type: 'boolean', enum: [true] },
},
- required: ['roles'],
+ required: ['success'],
+ additionalProperties: false,
}),
},
},
- async function () {
- const { rid } = this.queryParams;
- const roles = await executeGetRoomRoles(rid, this.userId);
+ async function action() {
+ const { roomId, action } = this.bodyParams;
- return API.v1.success({
- roles,
- });
+ try {
+ await FederationMatrix.handleInvite(roomId, this.userId, action);
+ return API.v1.success();
+ } catch (error) {
+ return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` });
+ }
},
);
-type RoomEndpoints = ExtractRoutesFromAPI;
+type RoomEndpoints = ExtractRoutesFromAPI & ExtractRoutesFromAPI;
declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts
index a3192aeb9ec14..b6e406bbfc969 100644
--- a/apps/meteor/app/api/server/v1/subscriptions.ts
+++ b/apps/meteor/app/api/server/v1/subscriptions.ts
@@ -1,4 +1,4 @@
-import { Subscriptions } from '@rocket.chat/models';
+import { Rooms, Subscriptions } from '@rocket.chat/models';
import {
isSubscriptionsGetProps,
isSubscriptionsGetOneProps,
@@ -85,7 +85,12 @@ API.v1.addRoute(
const { readThreads = false } = this.bodyParams;
const roomId = 'rid' in this.bodyParams ? this.bodyParams.rid : this.bodyParams.roomId;
- await readMessages(roomId, this.userId, readThreads);
+ const room = await Rooms.findOneById(roomId);
+ if (!room) {
+ throw new Error('error-invalid-subscription');
+ }
+
+ await readMessages(room, this.userId, readThreads);
return API.v1.success();
},
diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts
index 9ce577522a2d9..f1893def4a7a2 100644
--- a/apps/meteor/app/api/server/v1/teams.ts
+++ b/apps/meteor/app/api/server/v1/teams.ts
@@ -20,6 +20,7 @@ import { eraseRoom } from '../../../../server/lib/eraseRoom';
import { canAccessRoomAsync } from '../../../authorization/server';
import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom';
+import { settings } from '../../../settings/server';
import { API } from '../api';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { eraseTeam } from '../lib/eraseTeam';
@@ -235,6 +236,13 @@ API.v1.addRoute(
}
const canUpdateAny = !!(await hasPermissionAsync(this.userId, 'view-all-team-channels', team.roomId));
+ if (settings.get('ABAC_Enabled') && isDefault) {
+ const room = await Rooms.findOneByIdAndType(roomId, 'p', { projection: { abacAttributes: 1 } });
+ if (room?.abacAttributes?.length) {
+ return API.v1.failure('error-room-is-abac-managed');
+ }
+ }
+
const room = await Team.updateRoom(this.userId, roomId, isDefault, canUpdateAny);
return API.v1.success({ room });
diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts
index f6058085deeec..e567970508438 100644
--- a/apps/meteor/app/api/server/v1/users.ts
+++ b/apps/meteor/app/api/server/v1/users.ts
@@ -1,6 +1,6 @@
import { MeteorError, Team, api, Calendar } from '@rocket.chat/core-services';
import { type IExportOperation, type ILoginToken, type IPersonalAccessToken, type IUser, type UserStatus } from '@rocket.chat/core-typings';
-import { Users, Subscriptions } from '@rocket.chat/models';
+import { Users, Subscriptions, Sessions } from '@rocket.chat/models';
import {
isUserCreateParamsPOST,
isUserSetActiveStatusParamsPOST,
@@ -18,6 +18,7 @@ import {
isUsersSetPreferencesParamsPOST,
isUsersCheckUsernameAvailabilityParamsGET,
isUsersSendConfirmationEmailParamsPOST,
+ ajv,
} from '@rocket.chat/rest-typings';
import { getLoginExpirationInMs, wrapExceptions } from '@rocket.chat/tools';
import { Accounts } from 'meteor/accounts-base';
@@ -69,6 +70,7 @@ import { deleteUserOwnAccount } from '../../../lib/server/methods/deleteUserOwnA
import { settings } from '../../../settings/server';
import { isSMTPConfigured } from '../../../utils/server/functions/isSMTPConfigured';
import { getURL } from '../../../utils/server/getURL';
+import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams } from '../helpers/getUserFromParams';
@@ -326,7 +328,7 @@ API.v1.addRoute(
validateCustomFields(this.bodyParams.customFields);
}
- if (this.bodyParams.freeSwitchExtension && !(await canEditExtension(this.userId, this.bodyParams.freeSwitchExtension))) {
+ if (this.bodyParams.freeSwitchExtension && !(await canEditExtension(this.bodyParams.freeSwitchExtension))) {
return API.v1.failure('Setting user voice call extension is not allowed', 'error-action-not-allowed');
}
@@ -616,7 +618,7 @@ API.v1.addRoute(
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();
- const { status, hasLoggedIn, type, roles, searchTerm } = this.queryParams;
+ const { status, hasLoggedIn, type, roles, searchTerm, inactiveReason } = this.queryParams;
return API.v1.success(
await findPaginatedUsersByStatus({
@@ -629,6 +631,7 @@ API.v1.addRoute(
searchTerm,
hasLoggedIn,
type,
+ inactiveReason,
}),
);
},
@@ -760,17 +763,70 @@ API.v1.addRoute(
},
);
-API.v1.addRoute(
+const usersEndpoints = API.v1.post(
'users.createToken',
- { authRequired: true, deprecationVersion: '8.0.0' },
{
- async post() {
- const user = await getUserFromParams(this.bodyParams);
+ authRequired: true,
+ body: ajv.compile<{ userId: string; secret: string }>({
+ type: 'object',
+ properties: {
+ userId: {
+ type: 'string',
+ minLength: 1,
+ },
+ secret: {
+ type: 'string',
+ minLength: 1,
+ },
+ },
+ required: ['userId', 'secret'],
+ additionalProperties: false,
+ }),
+ response: {
+ 200: ajv.compile<{ data: { userId: string; authToken: string } }>({
+ type: 'object',
+ properties: {
+ data: {
+ type: 'object',
+ properties: {
+ userId: {
+ type: 'string',
+ minLength: 1,
+ },
+ authToken: {
+ type: 'string',
+ minLength: 1,
+ },
+ },
+ required: ['userId'],
+ additionalProperties: false,
+ },
+ success: {
+ type: 'boolean',
+ enum: [true],
+ },
+ },
+ required: ['data', 'success'],
+ additionalProperties: false,
+ }),
+ 400: ajv.compile({
+ type: 'object',
+ properties: {
+ success: { type: 'boolean', enum: [false] },
+ error: { type: 'string' },
+ errorType: { type: 'string' },
+ },
+ required: ['success'],
+ additionalProperties: false,
+ }),
+ },
+ },
+ async function action() {
+ const user = await getUserFromParams(this.bodyParams);
- const data = await generateAccessToken(this.userId, user._id);
+ const data = await generateAccessToken(user._id, this.bodyParams.secret);
- return data ? API.v1.success({ data }) : API.v1.forbidden();
- },
+ return API.v1.success({ data });
},
);
@@ -1282,6 +1338,8 @@ API.v1.addRoute(
throw new Meteor.Error('error-invalid-user-id', 'Invalid user id');
}
+ await Sessions.logoutAllByUserId(userId, this.userId);
+
void notifyOnUserChange({ clientAction: 'updated', id: userId, diff: { 'services.resume.loginTokens': [] } });
return API.v1.success({
@@ -1355,18 +1413,17 @@ API.v1.addRoute(
}
const { _id, username, roles, name } = user;
- let { statusText } = user;
-
- // TODO refactor to not update the user twice (one inside of `setStatusText` and then later just the status + statusDefault)
+ let { statusText, status } = user;
if (this.bodyParams.message || this.bodyParams.message === '') {
- await setStatusText(user._id, this.bodyParams.message);
+ await setStatusText(user._id, this.bodyParams.message, { emit: false });
statusText = this.bodyParams.message;
}
+
if (this.bodyParams.status) {
const validStatus = ['online', 'away', 'offline', 'busy'];
if (validStatus.includes(this.bodyParams.status)) {
- const { status } = this.bodyParams;
+ status = this.bodyParams.status;
if (status === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
@@ -1384,11 +1441,6 @@ API.v1.addRoute(
},
);
- void api.broadcast('presence.status', {
- user: { status, _id, username, statusText, roles, name },
- previousStatus: user.status,
- });
-
void wrapExceptions(() => Calendar.cancelUpcomingStatusChanges(user._id)).suppress();
} else {
throw new Meteor.Error('error-invalid-status', 'Valid status types include online, away, offline, and busy.', {
@@ -1397,6 +1449,11 @@ API.v1.addRoute(
}
}
+ void api.broadcast('presence.status', {
+ user: { status, _id, username, statusText, roles, name },
+ previousStatus: user.status,
+ });
+
return API.v1.success();
},
},
@@ -1439,3 +1496,10 @@ settings.watch('Rate_Limiter_Limit_RegisterUser', (value) => {
API.v1.updateRateLimiterDictionaryForRoute(userRegisterRoute, value);
});
+
+type UsersEndpoints = ExtractRoutesFromAPI;
+
+declare module '@rocket.chat/rest-typings' {
+ // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
+ interface Endpoints extends UsersEndpoints {}
+}
diff --git a/apps/meteor/app/api/server/v1/voip/events.ts b/apps/meteor/app/api/server/v1/voip/events.ts
deleted file mode 100644
index 7a2c9288b4c4b..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/events.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { LivechatVoip } from '@rocket.chat/core-services';
-import { VoipClientEvents } from '@rocket.chat/core-typings';
-import { VoipRoom } from '@rocket.chat/models';
-import { Match, check } from 'meteor/check';
-
-import { canAccessRoomAsync } from '../../../../authorization/server';
-import { API } from '../../api';
-
-API.v1.addRoute(
- 'voip/events',
- { authRequired: true, permissionsRequired: ['view-l-room'] },
- {
- async post() {
- check(this.bodyParams, {
- event: Match.Where((v: string) => {
- return Object.values(VoipClientEvents).includes(v);
- }),
- rid: String,
- comment: Match.Maybe(String),
- });
-
- const { rid, event, comment } = this.bodyParams;
-
- const room = await VoipRoom.findOneVoipRoomById(rid);
- if (!room) {
- return API.v1.notFound();
- }
- if (!(await canAccessRoomAsync(room, this.user))) {
- return API.v1.forbidden();
- }
-
- return API.v1.success(await LivechatVoip.handleEvent(event, room, this.user, comment));
- },
- },
-);
diff --git a/apps/meteor/app/api/server/v1/voip/extensions.ts b/apps/meteor/app/api/server/v1/voip/extensions.ts
deleted file mode 100644
index fd2181d97f0d9..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/extensions.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { VoipAsterisk } from '@rocket.chat/core-services';
-import type { IVoipExtensionBase } from '@rocket.chat/core-typings';
-import { Users } from '@rocket.chat/models';
-import { Match, check } from 'meteor/check';
-
-import { logger } from './logger';
-import { settings } from '../../../../settings/server';
-import { generateJWT } from '../../../../utils/server/lib/JWTHelper';
-import { API } from '../../api';
-
-// Get the connector version and type
-API.v1.addRoute(
- 'connector.getVersion',
- { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] },
- {
- async get() {
- const version = await VoipAsterisk.getConnectorVersion();
- return API.v1.success(version);
- },
- },
-);
-
-// Get the extensions available on the call server
-API.v1.addRoute(
- 'connector.extension.list',
- { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] },
- {
- async get() {
- const list = await VoipAsterisk.getExtensionList();
- const result = list.result as IVoipExtensionBase[];
- return API.v1.success({ extensions: result });
- },
- },
-);
-
-/* Get the details of a single extension.
- * Note : This API will either be called by the endpoint
- * or will be consumed internally.
- */
-API.v1.addRoute(
- 'connector.extension.getDetails',
- { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- extension: String,
- }),
- );
- const endpointDetails = await VoipAsterisk.getExtensionDetails(this.queryParams);
- return API.v1.success({ ...endpointDetails.result });
- },
- },
-);
-
-/* Get the details for registration extension.
- */
-
-API.v1.addRoute(
- 'connector.extension.getRegistrationInfoByExtension',
- { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- extension: String,
- }),
- );
- const endpointDetails = await VoipAsterisk.getRegistrationInfo(this.queryParams);
- const encKey = settings.get('VoIP_JWT_Secret');
- if (!encKey) {
- logger.warn('No JWT keys set. Sending registration info as plain text');
- return API.v1.success({ ...endpointDetails.result });
- }
-
- const result = generateJWT(endpointDetails.result, encKey);
- return API.v1.success({ result });
- },
- },
-);
-
-API.v1.addRoute(
- 'connector.extension.getRegistrationInfoByUserId',
- { authRequired: true, permissionsRequired: ['view-agent-extension-association'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- id: String,
- }),
- );
- const { id } = this.queryParams;
-
- if (id !== this.userId) {
- return API.v1.forbidden();
- }
-
- const { extension } =
- (await Users.getVoipExtensionByUserId(id, {
- projection: {
- _id: 1,
- username: 1,
- extension: 1,
- },
- })) || {};
-
- if (!extension) {
- return API.v1.notFound('Extension not found');
- }
-
- const endpointDetails = await VoipAsterisk.getRegistrationInfo({ extension });
- const encKey = settings.get('VoIP_JWT_Secret');
- if (!encKey) {
- logger.warn('No JWT keys set. Sending registration info as plain text');
- return API.v1.success({ ...endpointDetails.result });
- }
-
- const result = generateJWT(endpointDetails.result, encKey);
- return API.v1.success({ result });
- },
- },
-);
diff --git a/apps/meteor/app/api/server/v1/voip/index.ts b/apps/meteor/app/api/server/v1/voip/index.ts
deleted file mode 100644
index 95b8dd15a9386..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import './extensions';
-import './queues';
-import './events';
-import './rooms';
-import './server-connection';
diff --git a/apps/meteor/app/api/server/v1/voip/logger.ts b/apps/meteor/app/api/server/v1/voip/logger.ts
deleted file mode 100644
index fffa680784803..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/logger.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Logger } from '@rocket.chat/logger';
-
-export const logger = new Logger('VoIP');
diff --git a/apps/meteor/app/api/server/v1/voip/omnichannel.ts b/apps/meteor/app/api/server/v1/voip/omnichannel.ts
deleted file mode 100644
index abd92b9fa5899..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/omnichannel.ts
+++ /dev/null
@@ -1,286 +0,0 @@
-import { LivechatVoip } from '@rocket.chat/core-services';
-import type { IUser, IVoipExtensionWithAgentInfo } from '@rocket.chat/core-typings';
-import { Users } from '@rocket.chat/models';
-import { Match, check } from 'meteor/check';
-
-import { logger } from './logger';
-import { notifyOnUserChange } from '../../../../lib/server/lib/notifyListener';
-import { API } from '../../api';
-import { getPaginationItems } from '../../helpers/getPaginationItems';
-
-function filter(
- array: IVoipExtensionWithAgentInfo[],
- { queues, extension, agentId, status }: { queues?: string[]; extension?: string; agentId?: string; status?: string },
-): IVoipExtensionWithAgentInfo[] {
- const defaultFunc = (): boolean => true;
- return array.filter((item) => {
- const queuesCond = queues && Array.isArray(queues) ? (): boolean => item.queues?.some((q) => queues.includes(q)) || false : defaultFunc;
- const extensionCond = extension?.trim() ? (): boolean => item?.extension === extension : defaultFunc;
- const agentIdCond = agentId?.trim() ? (): boolean => item?.userId === agentId : defaultFunc;
- const statusCond = status?.trim() ? (): boolean => item?.state === status : defaultFunc;
-
- return queuesCond() && extensionCond() && agentIdCond() && statusCond();
- });
-}
-
-function paginate(array: T[], count = 10, offset = 0): T[] {
- return array.slice(offset, offset + count);
-}
-
-const isUserAndExtensionParams = (p: any): p is { userId: string; extension: string } => p.userId && p.extension;
-const isUserIdndTypeParams = (p: any): p is { userId: string; type: 'free' | 'allocated' | 'available' } => p.userId && p.type;
-
-API.v1.addRoute(
- 'omnichannel/agent/extension',
- { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] },
- {
- async post() {
- check(
- this.bodyParams,
- Match.OneOf(
- Match.ObjectIncluding({
- username: String,
- extension: String,
- }),
- Match.ObjectIncluding({
- userId: String,
- extension: String,
- }),
- ),
- );
-
- const { extension } = this.bodyParams;
- let user: IUser | null = null;
-
- if (!isUserAndExtensionParams(this.bodyParams)) {
- if (!this.bodyParams.username) {
- return API.v1.notFound();
- }
- user = await Users.findOneByAgentUsername(this.bodyParams.username, {
- projection: {
- _id: 1,
- username: 1,
- },
- });
- } else {
- if (!this.bodyParams.userId) {
- return API.v1.notFound();
- }
- user = await Users.findOneAgentById(this.bodyParams.userId, {
- projection: {
- _id: 1,
- username: 1,
- },
- });
- }
-
- if (!user) {
- return API.v1.notFound('User not found or does not have livechat-agent role');
- }
-
- try {
- await Users.setExtension(user._id, extension);
-
- void notifyOnUserChange({
- clientAction: 'updated',
- id: user._id,
- diff: {
- extension,
- },
- });
-
- return API.v1.success();
- } catch (e) {
- logger.error({ msg: 'Extension already in use' });
- return API.v1.failure(`extension already in use ${extension}`);
- }
- },
- },
-);
-
-API.v1.addRoute(
- 'omnichannel/agent/extension/:username',
- {
- authRequired: true,
- permissionsRequired: {
- GET: ['view-agent-extension-association'],
- DELETE: ['manage-agent-extension-association'],
- },
- },
- {
- // Get the extensions associated with the agent passed as request params.
- async get() {
- check(
- this.urlParams,
- Match.ObjectIncluding({
- username: String,
- }),
- );
- const { username } = this.urlParams;
- const user = await Users.findOneByAgentUsername(username, {
- projection: { _id: 1 },
- });
- if (!user) {
- return API.v1.notFound('User not found');
- }
- const extension = await Users.getVoipExtensionByUserId(user._id, {
- projection: {
- _id: 1,
- username: 1,
- extension: 1,
- },
- });
- if (!extension) {
- return API.v1.notFound('Extension not found');
- }
- return API.v1.success({ extension });
- },
-
- async delete() {
- check(
- this.urlParams,
- Match.ObjectIncluding({
- username: String,
- }),
- );
- const { username } = this.urlParams;
- const user = await Users.findOneByAgentUsername(username, {
- projection: {
- _id: 1,
- username: 1,
- extension: 1,
- },
- });
- if (!user) {
- return API.v1.notFound();
- }
- if (!user.extension) {
- return API.v1.success();
- }
-
- logger.debug(`Removing extension association for user ${user._id} (extension was ${user.extension})`);
- await Users.unsetExtension(user._id);
-
- void notifyOnUserChange({
- clientAction: 'updated',
- id: user._id,
- diff: {
- extension: null,
- },
- });
-
- return API.v1.success();
- },
- },
-);
-
-// Get free extensions
-API.v1.addRoute(
- 'omnichannel/extension',
- { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.OneOf(
- Match.ObjectIncluding({
- type: Match.OneOf('free', 'allocated', 'available'),
- userId: String,
- }),
- Match.ObjectIncluding({
- type: Match.OneOf('free', 'allocated', 'available'),
- username: String,
- }),
- ),
- );
-
- switch (this.queryParams.type.toLowerCase()) {
- case 'free': {
- const extensions = await LivechatVoip.getFreeExtensions();
- if (!extensions) {
- return API.v1.failure('Error in finding free extensons');
- }
- return API.v1.success({ extensions });
- }
- case 'allocated': {
- const extensions = await LivechatVoip.getExtensionAllocationDetails();
- if (!extensions) {
- return API.v1.failure('Error in allocated extensions');
- }
- return API.v1.success({ extensions: extensions.map((e) => e.extension) });
- }
- case 'available': {
- let user: IUser | null = null;
- if (!isUserIdndTypeParams(this.queryParams)) {
- user = await Users.findOneByAgentUsername(this.queryParams.username, {
- projection: { _id: 1, extension: 1 },
- });
- } else {
- user = await Users.findOneAgentById(this.queryParams.userId, {
- projection: { _id: 1, extension: 1 },
- });
- }
-
- const freeExt = await LivechatVoip.getFreeExtensions();
- const extensions = user?.extension ? [user.extension, ...freeExt] : freeExt;
- return API.v1.success({ extensions });
- }
- default:
- return API.v1.notFound(`${this.queryParams.type} not found `);
- }
- },
- },
-);
-
-API.v1.addRoute(
- 'omnichannel/extensions',
- { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] },
- {
- async get() {
- const { offset, count } = await getPaginationItems(this.queryParams);
- const { status, agentId, queues, extension } = this.queryParams;
-
- check(status, Match.Maybe(String));
- check(agentId, Match.Maybe(String));
- check(queues, Match.Maybe([String]));
- check(extension, Match.Maybe(String));
-
- const extensions = await LivechatVoip.getExtensionListWithAgentData();
- const filteredExts = filter(extensions, {
- status: status ?? undefined,
- agentId: agentId ?? undefined,
- queues: queues ?? undefined,
- extension: extension ?? undefined,
- });
-
- // paginating in memory as Asterisk doesn't provide pagination for commands
- return API.v1.success({
- extensions: paginate(filteredExts, count, offset),
- offset,
- count,
- total: filteredExts.length,
- });
- },
- },
-);
-
-API.v1.addRoute(
- 'omnichannel/agents/available',
- { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] },
- {
- async get() {
- const { offset, count } = await getPaginationItems(this.queryParams);
- const { sort } = await this.parseJsonQuery();
- const { text, includeExtension = '' } = this.queryParams;
-
- const { agents, total } = await LivechatVoip.getAvailableAgents(includeExtension, text, count, offset, sort);
-
- return API.v1.success({
- agents,
- offset,
- count,
- total,
- });
- },
- },
-);
diff --git a/apps/meteor/app/api/server/v1/voip/queues.ts b/apps/meteor/app/api/server/v1/voip/queues.ts
deleted file mode 100644
index 4a8fb86be0e39..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/queues.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { VoipAsterisk } from '@rocket.chat/core-services';
-import type { IVoipConnectorResult, IQueueSummary, IQueueMembershipDetails, IQueueMembershipSubscription } from '@rocket.chat/core-typings';
-import { Match, check } from 'meteor/check';
-
-import { API } from '../../api';
-
-API.v1.addRoute(
- 'voip/queues.getSummary',
- { authRequired: true, permissionsRequired: ['inbound-voip-calls'] },
- {
- async get() {
- const queueSummary = await VoipAsterisk.getQueueSummary();
- return API.v1.success({ summary: queueSummary.result as IQueueSummary[] });
- },
- },
-);
-
-API.v1.addRoute(
- 'voip/queues.getQueuedCallsForThisExtension',
- { authRequired: true, permissionsRequired: ['inbound-voip-calls'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- extension: String,
- }),
- );
- const membershipDetails: IVoipConnectorResult = await VoipAsterisk.getQueuedCallsForThisExtension(this.queryParams);
- return API.v1.success(membershipDetails.result as IQueueMembershipDetails);
- },
- },
-);
-
-API.v1.addRoute(
- 'voip/queues.getMembershipSubscription',
- { authRequired: true, permissionsRequired: ['inbound-voip-calls'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- extension: String,
- }),
- );
- const membershipDetails: IVoipConnectorResult = await VoipAsterisk.getQueueMembership(this.queryParams);
- return API.v1.success(membershipDetails.result as IQueueMembershipSubscription);
- },
- },
-);
diff --git a/apps/meteor/app/api/server/v1/voip/rooms.ts b/apps/meteor/app/api/server/v1/voip/rooms.ts
deleted file mode 100644
index 13d2e2c31a054..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/rooms.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import { LivechatVoip } from '@rocket.chat/core-services';
-import type { IVoipRoom } from '@rocket.chat/core-typings';
-import { VoipRoom, LivechatVisitors, Users } from '@rocket.chat/models';
-import { Random } from '@rocket.chat/random';
-import { isVoipRoomProps, isVoipRoomsProps, isVoipRoomCloseProps } from '@rocket.chat/rest-typings';
-
-import { typedJsonParse } from '../../../../../lib/typedJSONParse';
-import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
-import { API } from '../../api';
-import { getPaginationItems } from '../../helpers/getPaginationItems';
-
-type DateParam = { start?: string; end?: string };
-const parseDateParams = (date?: string): DateParam => {
- return date && typeof date === 'string' ? typedJsonParse(date) : {};
-};
-const validateDateParams = (property: string, date: DateParam = {}): DateParam => {
- if (date?.start && isNaN(Date.parse(date.start))) {
- throw new Error(`The "${property}.start" query parameter must be a valid date.`);
- }
- if (date?.end && isNaN(Date.parse(date.end))) {
- throw new Error(`The "${property}.end" query parameter must be a valid date.`);
- }
- return date;
-};
-const parseAndValidate = (property: string, date?: string): DateParam => {
- return validateDateParams(property, parseDateParams(date));
-};
-
-/**
- * @openapi
- * /voip/server/api/v1/voip/room
- * get:
- * description: Creates a new room if rid is not passed, else gets an existing room
- * based on rid and token . This configures the rate limit. An average call volume in a contact
- * center is 600 calls a day
- * considering 8 hour shift. Which comes to 1.25 calls per minute.
- * we will keep the safe limit which is 5 calls a minute.
- * security:
- * parameters:
- * - name: token
- * in: query
- * description: The visitor token
- * required: true
- * schema:
- * type: string
- * example: ByehQjC44FwMeiLbX
- * - name: rid
- * in: query
- * description: The room id
- * required: false
- * schema:
- * type: string
- * example: ByehQjC44FwMeiLbX
- * - name: agentId
- * in: query
- * description: Agent Id
- * required: false
- * schema:
- * type: string
- * example: ByehQjC44FwMeiLbX
- * responses:
- * 200:
- * description: Room object and flag indicating whether a new room is created.
- * content:
- * application/json:
- * schema:
- * allOf:
- * - $ref: '#/components/schemas/ApiSuccessV1'
- * - type: object
- * properties:
- * room:
- * type: object
- * items:
- * $ref: '#/components/schemas/IRoom'
- * newRoom:
- * type: boolean
- * default:
- * description: Unexpected error
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/ApiFailureV1'
- */
-
-const isRoomSearchProps = (props: any): props is { rid: string; token: string } => {
- return 'rid' in props && 'token' in props;
-};
-
-const isRoomCreationProps = (props: any): props is { agentId: string; direction: IVoipRoom['direction'] } => {
- return 'agentId' in props && 'direction' in props;
-};
-
-API.v1.addRoute(
- 'voip/room',
- {
- authRequired: true,
- rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 60000 },
- permissionsRequired: ['inbound-voip-calls'],
- validateParams: isVoipRoomProps,
- },
- {
- async get() {
- const { token } = this.queryParams;
- let agentId: string | undefined = undefined;
- let direction: IVoipRoom['direction'] = 'inbound';
- let rid: string | undefined = undefined;
-
- if (isRoomCreationProps(this.queryParams)) {
- agentId = this.queryParams.agentId;
- direction = this.queryParams.direction;
- }
-
- if (isRoomSearchProps(this.queryParams)) {
- rid = this.queryParams.rid;
- }
-
- const guest = await LivechatVisitors.getVisitorByToken(token, {});
- if (!guest) {
- return API.v1.failure('invalid-token');
- }
-
- if (!rid) {
- const room = await VoipRoom.findOneOpenByVisitorToken(token, { projection: API.v1.defaultFieldsToExclude });
- if (room) {
- return API.v1.success({ room, newRoom: false });
- }
- if (!agentId) {
- return API.v1.failure('agent-not-found');
- }
-
- const agentObj = await Users.findOneAgentById(agentId, {
- projection: { username: 1 },
- });
- if (!agentObj?.username) {
- return API.v1.failure('agent-not-found');
- }
-
- const { username, _id } = agentObj;
- const agent = { agentId: _id, username };
- const rid = Random.id();
-
- return API.v1.success(
- await LivechatVoip.getNewRoom(guest, agent, rid, direction, {
- projection: API.v1.defaultFieldsToExclude,
- }),
- );
- }
-
- const room = await VoipRoom.findOneByIdAndVisitorToken(rid, token, { projection: API.v1.defaultFieldsToExclude });
- if (!room) {
- return API.v1.failure('invalid-room');
- }
- return API.v1.success({ room, newRoom: false });
- },
- },
-);
-
-API.v1.addRoute(
- 'voip/rooms',
- { authRequired: true, validateParams: isVoipRoomsProps },
- {
- async get() {
- const { offset, count } = await getPaginationItems(this.queryParams);
-
- const { sort, fields } = await this.parseJsonQuery();
- const { agents, open, tags, queue, visitorId, direction, roomName } = this.queryParams;
- const { createdAt: createdAtParam, closedAt: closedAtParam } = this.queryParams;
-
- // Reusing same L room permissions for simplicity
- const hasAdminAccess = await hasPermissionAsync(this.userId, 'view-livechat-rooms');
- const hasAgentAccess =
- (await hasPermissionAsync(this.userId, 'view-l-room')) && agents?.includes(this.userId) && agents?.length === 1;
- if (!hasAdminAccess && !hasAgentAccess) {
- return API.v1.forbidden();
- }
-
- const createdAt = parseAndValidate('createdAt', createdAtParam);
- const closedAt = parseAndValidate('closedAt', closedAtParam);
-
- return API.v1.success(
- await LivechatVoip.findVoipRooms({
- agents,
- open: open === 'true',
- tags,
- queue,
- visitorId,
- createdAt,
- closedAt,
- direction,
- roomName,
- options: { sort, offset, count, fields },
- }),
- );
- },
- },
-);
-
-/**
- * @openapi
- * /voip/server/api/v1/voip/room.close
- * post:
- * description: Closes an open room
- * based on rid and token. Setting rate limit for this too
- * Because room creation happens 5/minute, rate limit for this api
- * is also set to 5/minute.
- * security:
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * rid:
- * type: string
- * token:
- * type: string
- * responses:
- * 200:
- * description: rid of closed room and a comment for closing room
- * content:
- * application/json:
- * schema:
- * allOf:
- * - $ref: '#/components/schemas/ApiSuccessV1'
- * - type: object
- * properties:
- * rid:
- * type: string
- * comment:
- * type: string
- * default:
- * description: Unexpected error
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/ApiFailureV1'
- */
-API.v1.addRoute(
- 'voip/room.close',
- { authRequired: true, validateParams: isVoipRoomCloseProps, permissionsRequired: ['inbound-voip-calls'] },
- {
- async post() {
- const { rid, token, options } = this.bodyParams;
-
- const visitor = await LivechatVisitors.getVisitorByToken(token, {});
- if (!visitor) {
- return API.v1.failure('invalid-token');
- }
- const room = await LivechatVoip.findRoom(token, rid);
- if (!room) {
- return API.v1.failure('invalid-room');
- }
- if (!room.open) {
- return API.v1.failure('room-closed');
- }
- const closeResult = await LivechatVoip.closeRoom(visitor, room, this.user, 'voip-call-wrapup', options);
- if (!closeResult) {
- return API.v1.failure();
- }
- return API.v1.success({ rid });
- },
- },
-);
diff --git a/apps/meteor/app/api/server/v1/voip/server-connection.ts b/apps/meteor/app/api/server/v1/voip/server-connection.ts
deleted file mode 100644
index 1bf2b85e8c7a4..0000000000000
--- a/apps/meteor/app/api/server/v1/voip/server-connection.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { VoipAsterisk } from '@rocket.chat/core-services';
-import { Match, check } from 'meteor/check';
-
-import { API } from '../../api';
-
-API.v1.addRoute(
- 'voip/managementServer/checkConnection',
- { authRequired: true, permissionsRequired: ['manage-voip-contact-center-settings'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- host: String,
- port: String,
- username: String,
- password: String,
- }),
- );
- const { host, port, username, password } = this.queryParams;
- return API.v1.success(await VoipAsterisk.checkManagementConnection(host, port, username, password));
- },
- },
-);
-
-API.v1.addRoute(
- 'voip/callServer/checkConnection',
- { authRequired: true, permissionsRequired: ['manage-voip-contact-center-settings'] },
- {
- async get() {
- check(
- this.queryParams,
- Match.ObjectIncluding({
- websocketUrl: Match.Maybe(String),
- host: Match.Maybe(String),
- port: Match.Maybe(String),
- path: Match.Maybe(String),
- }),
- );
- const { websocketUrl, host, port, path } = this.queryParams;
- if (!websocketUrl && !(host && port && path)) {
- return API.v1.failure('Incorrect / Insufficient Parameters');
- }
- let socketUrl = websocketUrl as string;
- if (!socketUrl) {
- // We will assume that it is always secure.
- // This is because you can not have webRTC working with non-secure server.
- // It works on non-secure server if it is tested on localhost.
- if (parseInt(port as string) !== 443) {
- socketUrl = `wss://${host}:${port}/${(path as string).replace('/', '')}`;
- } else {
- socketUrl = `wss://${host}/${(path as string).replace('/', '')}`;
- }
- }
-
- return API.v1.success(await VoipAsterisk.checkCallserverConnection(socketUrl));
- },
- },
-);
diff --git a/apps/meteor/app/apple/server/AppleCustomOAuth.ts b/apps/meteor/app/apple/server/AppleCustomOAuth.ts
index 5ec0e8f570bc0..d6617b7b5e81d 100644
--- a/apps/meteor/app/apple/server/AppleCustomOAuth.ts
+++ b/apps/meteor/app/apple/server/AppleCustomOAuth.ts
@@ -5,7 +5,7 @@ import { CustomOAuth } from '../../custom-oauth/server/custom_oauth_server';
import { handleIdentityToken } from '../lib/handleIdentityToken';
export class AppleCustomOAuth extends CustomOAuth {
- async getIdentity(_accessToken: string, query: Record): Promise {
+ override async getIdentity(_accessToken: string, query: Record): Promise {
const { id_token: identityToken, user: userStr = '' } = query;
let usrObj = {} as any;
diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts
index 0347d8e77f2fc..6952ac6f5457c 100644
--- a/apps/meteor/app/apps/server/bridges/livechat.ts
+++ b/apps/meteor/app/apps/server/bridges/livechat.ts
@@ -9,8 +9,8 @@ import { OmnichannelSourceType } from '@rocket.chat/core-typings';
import { LivechatVisitors, LivechatRooms, LivechatDepartment, Users } from '@rocket.chat/models';
import { registerGuest } from '@rocket.chat/omni-core';
-import { callbacks } from '../../../../lib/callbacks';
import { deasyncPromise } from '../../../../server/deasync/deasync';
+import { callbacks } from '../../../../server/lib/callbacks';
import { closeRoom } from '../../../livechat/server/lib/closeRoom';
import { setCustomFields } from '../../../livechat/server/lib/custom-fields';
import { getRoomMessages } from '../../../livechat/server/lib/getRoomMessages';
diff --git a/apps/meteor/app/apps/server/bridges/rooms.ts b/apps/meteor/app/apps/server/bridges/rooms.ts
index 861f7ade14dca..ec0f6c8b3bc77 100644
--- a/apps/meteor/app/apps/server/bridges/rooms.ts
+++ b/apps/meteor/app/apps/server/bridges/rooms.ts
@@ -1,9 +1,9 @@
import type { IAppServerOrchestrator } from '@rocket.chat/apps';
import type { IMessage, IMessageRaw } from '@rocket.chat/apps-engine/definition/messages';
-import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms';
+import type { IRoom, IRoomRaw } from '@rocket.chat/apps-engine/definition/rooms';
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import type { IUser } from '@rocket.chat/apps-engine/definition/users';
-import type { GetMessagesOptions } from '@rocket.chat/apps-engine/server/bridges/RoomBridge';
+import type { GetMessagesOptions, GetRoomsFilters, GetRoomsOptions } from '@rocket.chat/apps-engine/server/bridges/RoomBridge';
import { RoomBridge } from '@rocket.chat/apps-engine/server/bridges/RoomBridge';
import type { ISubscription, IUser as ICoreUser, IRoom as ICoreRoom, IMessage as ICoreMessage } from '@rocket.chat/core-typings';
import { Subscriptions, Users, Rooms, Messages } from '@rocket.chat/models';
@@ -17,6 +17,41 @@ import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFrom
import { createChannelMethod } from '../../../lib/server/methods/createChannel';
import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup';
+const rawRoomProjection: FindOptions['projection'] = {
+ _id: 1,
+ fname: 1,
+ name: 1,
+ usernames: 1,
+ members: 1,
+ uids: 1,
+ default: 1,
+ ro: 1,
+ sysMes: 1,
+ msgs: 1,
+ ts: 1,
+ _updatedAt: 1,
+ closedAt: 1,
+ lm: 1,
+ description: 1,
+ customFields: 1,
+ prid: 1,
+ teamId: 1,
+ teamMain: 1,
+ livechatData: 1,
+ waitingResponse: 1,
+ open: 1,
+ source: 1,
+ closer: 1,
+ t: 1,
+ u: 1,
+ v: 1,
+ contactId: 1,
+ departmentId: 1,
+ closedBy: 1,
+ servedBy: 1,
+ responseBy: 1,
+};
+
export class AppRoomBridge extends RoomBridge {
constructor(private readonly orch: IAppServerOrchestrator) {
super();
@@ -151,6 +186,37 @@ export class AppRoomBridge extends RoomBridge {
return promises as Promise;
}
+ protected async getAllRooms(filters: GetRoomsFilters = {}, options: GetRoomsOptions = {}, appId: string): Promise> {
+ this.orch.debugLog(`The App ${appId} is getting all rooms with options`, options);
+
+ const { limit = 100, skip = 0 } = options;
+
+ const findOptions: FindOptions = {
+ sort: { ts: -1 },
+ skip,
+ limit: Math.min(limit, 100),
+ projection: rawRoomProjection,
+ };
+
+ const { types, discussions, teams } = filters;
+
+ const rooms: IRoomRaw[] = [];
+
+ const roomConverter = this.orch.getConverters()?.get('rooms');
+ if (!roomConverter) {
+ throw new Error('Room converter not found');
+ }
+
+ for await (const room of Rooms.findAllByTypesAndDiscussionAndTeam({ types, discussions, teams }, findOptions)) {
+ const converted = await roomConverter.convertRoomRaw(room);
+ if (converted) {
+ rooms.push(converted);
+ }
+ }
+
+ return rooms;
+ }
+
protected async getDirectByUsernames(usernames: Array, appId: string): Promise {
this.orch.debugLog(`The App ${appId} is getting direct room by usernames: "${usernames}"`);
const room = await Rooms.findDirectRoomContainingAllUsernames(usernames, {});
diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js
index 4152b12bbe57c..c81792a0bb390 100644
--- a/apps/meteor/app/apps/server/converters/rooms.js
+++ b/apps/meteor/app/apps/server/converters/rooms.js
@@ -20,6 +20,106 @@ export class AppRoomsConverter {
return this.convertRoom(room);
}
+ convertRoomRaw(room) {
+ if (!room) {
+ return undefined;
+ }
+
+ const mapUserLookup = (user) =>
+ user && {
+ _id: user._id ?? user.id,
+ ...(user.username && { username: user.username }),
+ ...(user.name && { name: user.name }),
+ };
+
+ const map = {
+ id: '_id',
+ displayName: 'fname',
+ slugifiedName: 'name',
+ members: 'members',
+ userIds: 'uids',
+ usernames: 'usernames',
+ messageCount: 'msgs',
+ createdAt: 'ts',
+ updatedAt: '_updatedAt',
+ closedAt: 'closedAt',
+ lastModifiedAt: 'lm',
+ customFields: 'customFields',
+ livechatData: 'livechatData',
+ isWaitingResponse: 'waitingResponse',
+ isOpen: 'open',
+ description: 'description',
+ source: 'source',
+ closer: 'closer',
+ teamId: 'teamId',
+ isTeamMain: 'teamMain',
+ isDefault: 'default',
+ isReadOnly: 'ro',
+ contactId: 'contactId',
+ departmentId: 'departmentId',
+ parentRoomId: 'prid',
+ visitor: (data) => {
+ const { v } = data;
+ if (!v) {
+ return undefined;
+ }
+
+ delete data.v;
+
+ const { _id: id, ...rest } = v;
+
+ return {
+ id,
+ ...rest,
+ };
+ },
+ displaySystemMessages: (data) => {
+ const { sysMes } = data;
+ delete data.sysMes;
+ return typeof sysMes === 'undefined' ? true : sysMes;
+ },
+ type: (data) => {
+ const result = this._convertTypeToApp(data.t);
+ delete data.t;
+ return result;
+ },
+ creator: (data) => {
+ if (!data.u) {
+ return undefined;
+ }
+ const creator = mapUserLookup(data.u);
+ delete data.u;
+ return creator;
+ },
+ closedBy: (data) => {
+ if (!data.closedBy) {
+ return undefined;
+ }
+ const { closedBy } = data;
+ delete data.closedBy;
+ return mapUserLookup(closedBy);
+ },
+ servedBy: (data) => {
+ if (!data.servedBy) {
+ return undefined;
+ }
+ const { servedBy } = data;
+ delete data.servedBy;
+ return mapUserLookup(servedBy);
+ },
+ responseBy: (data) => {
+ if (!data.responseBy) {
+ return undefined;
+ }
+ const { responseBy } = data;
+ delete data.responseBy;
+ return mapUserLookup(responseBy);
+ },
+ };
+
+ return transformMappedData(room, map);
+ }
+
async __getCreator(user) {
if (!user) {
return;
@@ -54,6 +154,7 @@ export class AppRoomsConverter {
username: visitor.username,
token: visitor.token,
status: visitor.status || 'online',
+ activity: visitor.activity,
...(lastMessageTs && { lastMessageTs }),
...(phone && { phone }),
};
@@ -200,6 +301,8 @@ export class AppRoomsConverter {
description: 'description',
source: 'source',
closer: 'closer',
+ teamId: 'teamId',
+ isTeamMain: 'teamMain',
isDefault: (room) => {
const result = !!room.default;
delete room.default;
diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js
index c8fb0b7c4a21c..00b8b3888ae74 100644
--- a/apps/meteor/app/apps/server/converters/visitors.js
+++ b/apps/meteor/app/apps/server/converters/visitors.js
@@ -36,6 +36,7 @@ export class AppVisitorsConverter {
visitorEmails: 'visitorEmails',
livechatData: 'livechatData',
status: 'status',
+ activity: 'activity',
};
return transformMappedData(visitor, map);
diff --git a/apps/meteor/app/authentication/server/hooks/login.ts b/apps/meteor/app/authentication/server/hooks/login.ts
index 10b91271c88fa..191639e208dee 100644
--- a/apps/meteor/app/authentication/server/hooks/login.ts
+++ b/apps/meteor/app/authentication/server/hooks/login.ts
@@ -1,6 +1,6 @@
import { Accounts } from 'meteor/accounts-base';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { settings } from '../../../settings/server';
import type { ILoginAttempt } from '../ILoginAttempt';
import { logFailedLoginAttempts } from '../lib/logLoginAttempts';
diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js
index a56f237c6e527..12825cde0f678 100644
--- a/apps/meteor/app/authentication/server/startup/index.js
+++ b/apps/meteor/app/authentication/server/startup/index.js
@@ -8,10 +8,10 @@ import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
-import { callbacks } from '../../../../lib/callbacks';
-import { beforeCreateUserCallback } from '../../../../lib/callbacks/beforeCreateUserCallback';
import { parseCSV } from '../../../../lib/utils/parseCSV';
import { safeHtmlDots } from '../../../../lib/utils/safeHtmlDots';
+import { callbacks } from '../../../../server/lib/callbacks';
+import { beforeCreateUserCallback } from '../../../../server/lib/callbacks/beforeCreateUserCallback';
import { getClientAddress } from '../../../../server/lib/getClientAddress';
import { getMaxLoginTokens } from '../../../../server/lib/getMaxLoginTokens';
import { i18n } from '../../../../server/lib/i18n';
@@ -204,7 +204,9 @@ const onCreateUserAsync = async function (options, user = {}) {
}
user.status = 'offline';
+
user.active = user.active !== undefined ? user.active : !settings.get('Accounts_ManuallyApproveNewUsers');
+ user.inactiveReason = settings.get('Accounts_ManuallyApproveNewUsers') && !user.active ? 'pending_approval' : undefined;
if (!user.name) {
if (options.profile) {
@@ -292,7 +294,7 @@ Accounts.insertUserDoc = async function (options, user) {
delete user.globalRoles;
- if (user.services && !user.services.password) {
+ if (user.services && !user.services.password && !options.skipAuthServiceDefaultRoles) {
const defaultAuthServiceRoles = parseCSV(settings.get('Accounts_Registration_AuthenticationServices_Default_Roles') || '');
if (defaultAuthServiceRoles.length > 0) {
diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts
index 49855eb7d32f1..600bf15793cc9 100644
--- a/apps/meteor/app/authorization/server/constant/permissions.ts
+++ b/apps/meteor/app/authorization/server/constant/permissions.ts
@@ -156,10 +156,6 @@ export const permissions = [
_id: 'add-livechat-department-agents',
roles: ['livechat-manager', 'livechat-monitor', 'admin'],
},
- {
- _id: 'view-livechat-current-chats',
- roles: ['livechat-manager', 'livechat-monitor', 'admin'],
- },
{
_id: 'view-livechat-real-time-monitoring',
roles: ['livechat-manager', 'livechat-monitor', 'admin'],
@@ -211,23 +207,6 @@ export const permissions = [
{ _id: 'remove-closed-livechat-room', roles: ['livechat-manager', 'admin'] },
{ _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] },
- // VOIP Permissions
- // allows to manage voip calls configuration
- { _id: 'manage-voip-call-settings', roles: ['livechat-manager', 'admin'] },
- { _id: 'manage-voip-contact-center-settings', roles: ['livechat-manager', 'admin'] },
- // allows agent-extension association.
- { _id: 'manage-agent-extension-association', roles: ['admin'] },
- { _id: 'view-agent-extension-association', roles: ['livechat-manager', 'admin', 'livechat-agent'] },
- // allows to receive a voip call
- { _id: 'inbound-voip-calls', roles: ['livechat-agent'] },
-
- // Allow managing team collab voip extensions
- { _id: 'manage-voip-extensions', roles: ['admin'] },
- // Allow viewing the extension number of other users
- { _id: 'view-user-voip-extension', roles: ['admin', 'user'] },
- // Allow viewing details of an extension
- { _id: 'view-voip-extension-details', roles: ['admin', 'user'] },
-
// New Media calls permissions
{ _id: 'allow-internal-voice-calls', roles: ['admin', 'user'] },
{ _id: 'allow-external-voice-calls', roles: ['admin', 'user'] },
diff --git a/apps/meteor/app/authorization/server/index.ts b/apps/meteor/app/authorization/server/index.ts
index a91e23610230a..c74062e774b41 100644
--- a/apps/meteor/app/authorization/server/index.ts
+++ b/apps/meteor/app/authorization/server/index.ts
@@ -4,9 +4,7 @@ import { getUsersInRole } from './functions/getUsersInRole';
import { subscriptionHasRole } from './functions/hasRole';
import './methods/addPermissionToRole';
import './methods/addUserToRole';
-import './methods/deleteRole';
import './methods/removeRoleFromPermission';
-import './methods/removeUserFromRole';
import './streamer/permissions';
export { getRoles, getUsersInRole, subscriptionHasRole, canAccessRoomAsync, roomAccessAttributes };
diff --git a/apps/meteor/app/authorization/server/methods/addUserToRole.ts b/apps/meteor/app/authorization/server/methods/addUserToRole.ts
index 461c0a11418ad..77bdae79da3bc 100644
--- a/apps/meteor/app/authorization/server/methods/addUserToRole.ts
+++ b/apps/meteor/app/authorization/server/methods/addUserToRole.ts
@@ -1,21 +1,12 @@
import { api } from '@rocket.chat/core-services';
import type { IRole, IUser } from '@rocket.chat/core-typings';
-import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Roles, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles';
-import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { settings } from '../../../settings/server';
import { hasPermissionAsync } from '../functions/hasPermission';
-declare module '@rocket.chat/ddp-client' {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface ServerMethods {
- 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope: string | undefined): Promise;
- }
-}
-
export const addUserToRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
@@ -30,21 +21,12 @@ export const addUserToRole = async (userId: string, roleId: string, username: IU
});
}
- let role = await Roles.findOneById>(roleId, { projection: { _id: 1 } });
- if (!role) {
- role = await Roles.findOneByName>(roleId, { projection: { _id: 1 } });
+ const role = await Roles.findOneById>(roleId, { projection: { _id: 1 } });
- if (!role) {
- throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
- method: 'authorization:addUserToRole',
- });
- }
- methodDeprecationLogger.deprecatedParameterUsage(
- 'authorization:addUserToRole',
- 'role',
- '7.0.0',
- ({ parameter, method, version }) => `Calling ${method} with \`${parameter}\` names is deprecated and will be removed ${version}`,
- );
+ if (!role) {
+ throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
+ method: 'authorization:addUserToRole',
+ });
}
if (role._id === 'admin' && !(await hasPermissionAsync(userId, 'assign-admin-role'))) {
@@ -89,20 +71,3 @@ export const addUserToRole = async (userId: string, roleId: string, username: IU
return add;
};
-
-Meteor.methods({
- async 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope) {
- methodDeprecationLogger.method('authorization:addUserToRole', '8.0.0', '/v1/roles.addUserToRole');
-
- const userId = Meteor.userId();
-
- if (!userId) {
- throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
- method: 'authorization:addUserToRole',
- action: 'Accessing_permissions',
- });
- }
-
- return addUserToRole(userId, roleId, username, scope);
- },
-});
diff --git a/apps/meteor/app/authorization/server/methods/deleteRole.ts b/apps/meteor/app/authorization/server/methods/deleteRole.ts
deleted file mode 100644
index c327d1f1a2bc3..0000000000000
--- a/apps/meteor/app/authorization/server/methods/deleteRole.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import type { IRole } from '@rocket.chat/core-typings';
-import type { ServerMethods } from '@rocket.chat/ddp-client';
-import { Roles } from '@rocket.chat/models';
-import { Meteor } from 'meteor/meteor';
-import type { DeleteResult } from 'mongodb';
-
-import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
-import { hasPermissionAsync } from '../functions/hasPermission';
-
-declare module '@rocket.chat/ddp-client' {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface ServerMethods {
- 'authorization:deleteRole'(roleId: IRole['_id'] | IRole['name']): Promise;
- }
-}
-
-Meteor.methods({
- async 'authorization:deleteRole'(roleId) {
- methodDeprecationLogger.method('authorization:deleteRole', '8.0.0', '/v1/roles.delete');
-
- const userId = Meteor.userId();
-
- if (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {
- throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
- method: 'authorization:deleteRole',
- action: 'Accessing_permissions',
- });
- }
-
- const options = {
- projection: {
- _id: 1,
- protected: 1,
- },
- };
-
- let role = await Roles.findOneById>(roleId, options);
- if (!role) {
- role = await Roles.findOneByName>(roleId, options);
-
- if (!role) {
- throw new Meteor.Error('error-invalid-role', 'Invalid role', {
- method: 'authorization:deleteRole',
- });
- }
-
- methodDeprecationLogger.deprecatedParameterUsage(
- 'authorization:deleteRole',
- 'role',
- '7.0.0',
- ({ parameter, method, version }) => `Calling ${method} with ${parameter} names is deprecated and will be removed ${version}`,
- );
- }
-
- if (role.protected) {
- throw new Meteor.Error('error-delete-protected-role', 'Cannot delete a protected role', {
- method: 'authorization:deleteRole',
- });
- }
-
- const users = await Roles.countUsersInRole(role._id);
-
- if (users > 0) {
- throw new Meteor.Error('error-role-in-use', "Cannot delete role because it's in use", {
- method: 'authorization:deleteRole',
- });
- }
-
- return Roles.removeById(role._id);
- },
-});
diff --git a/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts b/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts
index 5efcc71b3577b..6caef9c656597 100644
--- a/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts
+++ b/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts
@@ -1,21 +1,12 @@
import { api } from '@rocket.chat/core-services';
import type { IRole, IUser } from '@rocket.chat/core-typings';
-import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Roles, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { removeUserFromRolesAsync } from '../../../../server/lib/roles/removeUserFromRoles';
-import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { settings } from '../../../settings/server';
import { hasPermissionAsync } from '../functions/hasPermission';
-declare module '@rocket.chat/ddp-client' {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface ServerMethods {
- 'authorization:removeUserFromRole'(roleId: IRole['_id'], username: IUser['username'], scope?: string): Promise;
- }
-}
-
export const removeUserFromRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
@@ -30,21 +21,11 @@ export const removeUserFromRole = async (userId: string, roleId: string, usernam
});
}
- let role = await Roles.findOneById>(roleId, { projection: { _id: 1 } });
+ const role = await Roles.findOneById>(roleId, { projection: { _id: 1 } });
if (!role) {
- role = await Roles.findOneByName>(roleId, { projection: { _id: 1 } });
- if (!role) {
- throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
- method: 'authorization:removeUserFromRole',
- });
- }
-
- methodDeprecationLogger.deprecatedParameterUsage(
- 'authorization:removeUserFromRole',
- 'role',
- '7.0.0',
- ({ parameter, method, version }) => `Calling ${method} with ${parameter} names is deprecated and will be removed ${version}`,
- );
+ throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
+ method: 'authorization:removeUserFromRole',
+ });
}
const user = await Users.findOneByUsernameIgnoringCase(username, {
@@ -94,20 +75,3 @@ export const removeUserFromRole = async (userId: string, roleId: string, usernam
return remove;
};
-
-Meteor.methods({
- async 'authorization:removeUserFromRole'(roleId, username, scope) {
- methodDeprecationLogger.method('authorization:removeUserFromRole', '8.0.0', '/v1/roles.removeUserFromRole');
-
- const userId = Meteor.userId();
-
- if (!userId) {
- throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
- method: 'authorization:removeUserFromRole',
- action: 'Accessing_permissions',
- });
- }
-
- return removeUserFromRole(userId, roleId, username, scope);
- },
-});
diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts
index 23e1b189a7920..afb4b5d5cf5fd 100644
--- a/apps/meteor/app/autotranslate/server/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/server/autotranslate.ts
@@ -13,8 +13,8 @@ import { escapeHTML } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
-import { callbacks } from '../../../lib/callbacks';
import { isTruthy } from '../../../lib/isTruthy';
+import { callbacks } from '../../../server/lib/callbacks';
import { notifyOnMessageChange } from '../../lib/server/lib/notifyListener';
import { Markdown } from '../../markdown/server';
import { settings } from '../../settings/server';
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
index c2fd6a95958f3..61ad721dc0593 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
@@ -5,7 +5,7 @@ import { Integrations, Rooms, Subscriptions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import type { Document, UpdateResult } from 'mongodb';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability';
import { notifyOnIntegrationChangedByChannels, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts
index 925b4833efd90..f916259c03364 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts
@@ -4,7 +4,7 @@ import { Rooms } from '@rocket.chat/models';
import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
export const saveRoomTopic = async (
rid: string,
diff --git a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts
index 7cbdca852cd6d..6ca88c1fdabc5 100644
--- a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts
+++ b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts
@@ -1,5 +1,5 @@
import { Team } from '@rocket.chat/core-services';
-import type { IRoom, IRoomWithRetentionPolicy, IUser, MessageTypesValues } from '@rocket.chat/core-typings';
+import type { IRoom, IRoomWithRetentionPolicy, IUser, MessageTypesValues, ITeam } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Rooms, Users } from '@rocket.chat/models';
@@ -11,6 +11,7 @@ import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { setRoomAvatar } from '../../../lib/server/functions/setRoomAvatar';
import { notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener';
+import { settings } from '../../../settings/server';
import { saveReactWhenReadOnly } from '../functions/saveReactWhenReadOnly';
import { saveRoomAnnouncement } from '../functions/saveRoomAnnouncement';
import { saveRoomCustomFields } from '../functions/saveRoomCustomFields';
@@ -61,14 +62,33 @@ type RoomSettingsValidators = {
const hasRetentionPolicy = (room: IRoom & { retention?: any }): room is IRoomWithRetentionPolicy =>
'retention' in room && room.retention !== undefined;
+const isAbacManagedRoom = (room: IRoom): boolean => {
+ return room.t === 'p' && settings.get('ABAC_Enabled') && Array.isArray(room?.abacAttributes) && room.abacAttributes.length > 0;
+};
+
+const isAbacManagedTeam = (team: Partial | null, teamRoom: IRoom): boolean => {
+ return (
+ team?.type === TEAM_TYPE.PRIVATE &&
+ settings.get('ABAC_Enabled') &&
+ Array.isArray(teamRoom?.abacAttributes) &&
+ teamRoom.abacAttributes.length > 0
+ );
+};
+
const validators: RoomSettingsValidators = {
- async default({ userId }) {
+ async default({ userId, room, value }) {
if (!(await hasPermissionAsync(userId, 'view-room-administration'))) {
throw new Meteor.Error('error-action-not-allowed', 'Viewing room administration is not allowed', {
method: 'saveRoomSettings',
action: 'Viewing_room_administration',
});
}
+ if (isAbacManagedRoom(room) && value) {
+ throw new Meteor.Error('error-action-not-allowed', 'Setting an ABAC managed room as default is not allowed', {
+ method: 'saveRoomSettings',
+ action: 'Viewing_room_administration',
+ });
+ }
},
async featured({ userId }) {
if (!(await hasPermissionAsync(userId, 'view-room-administration'))) {
@@ -98,6 +118,13 @@ const validators: RoomSettingsValidators = {
});
}
+ if (isAbacManagedRoom(room) && value !== 'p') {
+ throw new Meteor.Error('error-action-not-allowed', 'Changing an ABAC managed private room to public is not allowed', {
+ method: 'saveRoomSettings',
+ action: 'Change_Room_Type',
+ });
+ }
+
if (!room.teamId) {
return;
}
@@ -116,6 +143,13 @@ const validators: RoomSettingsValidators = {
action: 'Change_Room_Type',
});
}
+
+ if (isAbacManagedTeam(team, room) && value !== 'p') {
+ throw new Meteor.Error('error-action-not-allowed', 'Changing an ABAC managed private team room to public is not allowed', {
+ method: 'saveRoomSettings',
+ action: 'Change_Room_Type',
+ });
+ }
},
async encrypted({ userId, value, room, rid }) {
if (value !== room.encrypted) {
diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts
index 0ee4ba09ff57a..08b551782f6b8 100644
--- a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts
+++ b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts
@@ -3,13 +3,13 @@ import { Settings } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { z } from 'zod';
-import { callbacks } from '../../../../lib/callbacks';
+import { getWorkspaceAccessToken } from './getWorkspaceAccessToken';
import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError';
import { CloudWorkspaceLicenseError } from '../../../../lib/errors/CloudWorkspaceLicenseError';
+import { callbacks } from '../../../../server/lib/callbacks';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { settings } from '../../../settings/server';
import { LICENSE_VERSION } from '../license';
-import { getWorkspaceAccessToken } from './getWorkspaceAccessToken';
const workspaceLicensePayloadSchema = z.object({
version: z.number(),
diff --git a/apps/meteor/app/cloud/server/functions/removeLicense.ts b/apps/meteor/app/cloud/server/functions/removeLicense.ts
index 88f1d68251777..31cd23df14558 100644
--- a/apps/meteor/app/cloud/server/functions/removeLicense.ts
+++ b/apps/meteor/app/cloud/server/functions/removeLicense.ts
@@ -3,9 +3,9 @@ import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from './getWorkspaceAccessToken';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';
import { syncWorkspace } from './syncWorkspace';
-import { callbacks } from '../../../../lib/callbacks';
import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError';
import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError';
+import { callbacks } from '../../../../server/lib/callbacks';
import { settings } from '../../../settings/server';
export async function removeLicense() {
diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts
index f28821b083875..572a9a4a1bbef 100644
--- a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts
+++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts
@@ -1,14 +1,14 @@
import { DuplicatedLicenseError } from '@rocket.chat/license';
import { Settings } from '@rocket.chat/models';
-import { callbacks } from '../../../../../lib/callbacks';
+import { fetchWorkspaceSyncPayload } from './fetchWorkspaceSyncPayload';
import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError';
import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError';
+import { callbacks } from '../../../../../server/lib/callbacks';
import { SystemLogger } from '../../../../../server/lib/logger/system';
import { buildWorkspaceRegistrationData } from '../buildRegistrationData';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
-import { fetchWorkspaceSyncPayload } from './fetchWorkspaceSyncPayload';
export async function syncCloudData() {
try {
diff --git a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
index bfed645e146de..4a5f6779974e5 100644
--- a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
+++ b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
@@ -10,8 +10,8 @@ import { ServiceConfiguration } from 'meteor/service-configuration';
import _ from 'underscore';
import { normalizers, fromTemplate, renameInvalidProperties } from './transform_helpers';
-import { callbacks } from '../../../lib/callbacks';
import { isURL } from '../../../lib/utils/isURL';
+import { callbacks } from '../../../server/lib/callbacks';
import { notifyOnUserChange } from '../../lib/server/lib/notifyListener';
import { registerAccessTokenService } from '../../lib/server/oauth/oauth';
import { settings } from '../../settings/server';
diff --git a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts
index 86b2b30b699df..494c84590b50c 100644
--- a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts
+++ b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts
@@ -1,7 +1,7 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Messages, Rooms, VideoConference } from '@rocket.chat/models';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { deleteRoom } from '../../../lib/server/functions/deleteRoom';
import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener';
diff --git a/apps/meteor/app/dolphin/server/lib.ts b/apps/meteor/app/dolphin/server/lib.ts
index 65a8ca6d2dacf..826a277b81e4e 100644
--- a/apps/meteor/app/dolphin/server/lib.ts
+++ b/apps/meteor/app/dolphin/server/lib.ts
@@ -2,8 +2,8 @@ import type { IUser } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';
-import { callbacks } from '../../../lib/callbacks';
-import { beforeCreateUserCallback } from '../../../lib/callbacks/beforeCreateUserCallback';
+import { callbacks } from '../../../server/lib/callbacks';
+import { beforeCreateUserCallback } from '../../../server/lib/callbacks/beforeCreateUserCallback';
import { CustomOAuth } from '../../custom-oauth/server/custom_oauth_server';
import { settings } from '../../settings/server';
diff --git a/apps/meteor/app/e2e/server/beforeCreateRoom.ts b/apps/meteor/app/e2e/server/beforeCreateRoom.ts
index 19a8bcb2364ea..cb04d0def049d 100644
--- a/apps/meteor/app/e2e/server/beforeCreateRoom.ts
+++ b/apps/meteor/app/e2e/server/beforeCreateRoom.ts
@@ -1,4 +1,4 @@
-import { prepareCreateRoomCallback } from '../../../lib/callbacks/beforeCreateRoomCallback';
+import { prepareCreateRoomCallback } from '../../../server/lib/callbacks/beforeCreateRoomCallback';
import { settings } from '../../settings/server';
prepareCreateRoomCallback.add(({ type, extraData }) => {
diff --git a/apps/meteor/app/e2e/server/index.ts b/apps/meteor/app/e2e/server/index.ts
index b0d9dadcd2e2f..8c8c59488fd79 100644
--- a/apps/meteor/app/e2e/server/index.ts
+++ b/apps/meteor/app/e2e/server/index.ts
@@ -1,6 +1,6 @@
import { api } from '@rocket.chat/core-services';
-import { callbacks } from '../../../lib/callbacks';
+import { callbacks } from '../../../server/lib/callbacks';
import './beforeCreateRoom';
import './methods/setUserPublicAndPrivateKeys';
diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
index dd74041a90b71..1c018cb71cd37 100644
--- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
+++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
@@ -1,21 +1,11 @@
-import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Subscriptions, Rooms } from '@rocket.chat/models';
-import { Meteor } from 'meteor/meteor';
-import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import {
notifyOnSubscriptionChangedById,
notifyOnSubscriptionChanged,
notifyOnRoomChangedById,
} from '../../../lib/server/lib/notifyListener';
-declare module '@rocket.chat/ddp-client' {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface ServerMethods {
- 'e2e.updateGroupKey'(rid: string, uid: string, key: string): Promise;
- }
-}
-
export async function updateGroupKey(rid: string, uid: string, key: string, callerUserId: string) {
// I have a subscription to this room
const mySub = await Subscriptions.findOneByRoomIdAndUserId(rid, callerUserId);
@@ -46,16 +36,3 @@ export async function updateGroupKey(rid: string, uid: string, key: string, call
}
}
}
-
-Meteor.methods({
- async 'e2e.updateGroupKey'(rid, uid, key) {
- methodDeprecationLogger.method('e2e.updateGroupKey', '8.0.0', '/v1/e2e.acceptSuggestedGroupKey');
-
- const userId = Meteor.userId();
- if (!userId) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'e2e.acceptSuggestedGroupKey' });
- }
-
- return updateGroupKey(rid, uid, key, userId);
- },
-});
diff --git a/apps/meteor/app/ecdh/Session.ts b/apps/meteor/app/ecdh/Session.ts
deleted file mode 100644
index 47728667b9071..0000000000000
--- a/apps/meteor/app/ecdh/Session.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { X25519SecretKey, CryptographyKey } from 'sodium-plus';
-import { SodiumPlus, X25519PublicKey } from 'sodium-plus';
-
-export class Session {
- // Encoding for the key exchange, no requirements to be small
- protected readonly stringFormatKey: BufferEncoding = 'base64';
-
- // Encoding for the transfer of encrypted data, should be smaller as possible
- protected readonly stringFormatEncryptedData: BufferEncoding = 'base64';
-
- // Encoding before the encryption to keep unicode chars
- protected readonly stringFormatRawData: BufferEncoding = 'base64';
-
- protected decryptKey: CryptographyKey;
-
- protected encryptKey: CryptographyKey;
-
- protected secretKey: X25519SecretKey;
-
- public publicKey: X25519PublicKey;
-
- private static sodium: SodiumPlus | undefined;
-
- async sodium(): Promise {
- if (!Session.sodium) {
- Session.sodium = await SodiumPlus.auto();
- }
-
- return Session.sodium;
- }
-
- get publicKeyString(): string {
- return this.publicKey.toString(this.stringFormatKey);
- }
-
- publicKeyFromString(text: string): X25519PublicKey {
- return new X25519PublicKey(Buffer.from(text, this.stringFormatKey));
- }
-
- async encryptToBuffer(plaintext: string | Buffer): Promise {
- const sodium = await this.sodium();
- const nonce = await sodium.randombytes_buf(24);
-
- const buffer = Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(plaintext);
-
- const ciphertext = await sodium.crypto_secretbox(Buffer.from(buffer).toString(this.stringFormatRawData), nonce, this.encryptKey);
-
- return Buffer.concat([nonce, ciphertext]);
- }
-
- async encrypt(plaintext: string | Buffer): Promise {
- const buffer = await this.encryptToBuffer(plaintext);
- return buffer.toString(this.stringFormatEncryptedData);
- }
-
- async decryptToBuffer(data: string | Buffer): Promise {
- const sodium = await this.sodium();
- const buffer = Buffer.from(Buffer.isBuffer(data) ? data.toString() : data, this.stringFormatEncryptedData);
-
- const decrypted = await sodium.crypto_secretbox_open(buffer.slice(24), buffer.slice(0, 24), this.decryptKey);
-
- return Buffer.from(decrypted.toString(), this.stringFormatRawData);
- }
-
- async decrypt(data: string | Buffer): Promise {
- const buffer = await this.decryptToBuffer(data);
- return buffer.toString();
- }
-}
diff --git a/apps/meteor/app/ecdh/client/ClientSession.ts b/apps/meteor/app/ecdh/client/ClientSession.ts
deleted file mode 100644
index 6e87cbae6b139..0000000000000
--- a/apps/meteor/app/ecdh/client/ClientSession.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Session } from '../Session';
-
-export class ClientSession extends Session {
- async init(): Promise {
- const sodium = await this.sodium();
-
- const clientKeypair = await sodium.crypto_box_keypair();
- this.secretKey = await sodium.crypto_box_secretkey(clientKeypair);
- this.publicKey = await sodium.crypto_box_publickey(clientKeypair);
-
- return this.publicKey.toString(this.stringFormatKey);
- }
-
- async setServerKey(serverPublic: string): Promise {
- const sodium = await this.sodium();
-
- const [decryptKey, encryptKey] = await sodium.crypto_kx_client_session_keys(
- this.publicKey,
- this.secretKey,
- this.publicKeyFromString(serverPublic),
- );
-
- this.decryptKey = decryptKey;
- this.encryptKey = encryptKey;
- }
-}
diff --git a/apps/meteor/app/emoji-emojione/server/callbacks.ts b/apps/meteor/app/emoji-emojione/server/callbacks.ts
index 5f73b73d9d4b9..081e115a152a5 100644
--- a/apps/meteor/app/emoji-emojione/server/callbacks.ts
+++ b/apps/meteor/app/emoji-emojione/server/callbacks.ts
@@ -1,7 +1,7 @@
import emojione from 'emojione';
import { Meteor } from 'meteor/meteor';
-import { callbacks } from '../../../lib/callbacks';
+import { callbacks } from '../../../server/lib/callbacks';
Meteor.startup(() => {
callbacks.add(
diff --git a/apps/meteor/app/file-upload/server/config/GridFS.ts b/apps/meteor/app/file-upload/server/config/GridFS.ts
index 551569d4c58c4..bb9dfa5891f26 100644
--- a/apps/meteor/app/file-upload/server/config/GridFS.ts
+++ b/apps/meteor/app/file-upload/server/config/GridFS.ts
@@ -28,7 +28,7 @@ class ExtractRange extends stream.Transform {
this.bytes_read = 0;
}
- _transform(chunk: any, _enc: BufferEncoding, cb: TransformCallback) {
+ override _transform(chunk: any, _enc: BufferEncoding, cb: TransformCallback) {
if (this.bytes_read > this.stop) {
// done reading
this.end();
diff --git a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts
index 8ed534c7015b9..dd67e3489f5da 100644
--- a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts
+++ b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts
@@ -12,9 +12,9 @@ import { Rooms, Uploads, Users } from '@rocket.chat/models';
import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
-import { callbacks } from '../../../../lib/callbacks';
import { getFileExtension } from '../../../../lib/utils/getFileExtension';
import { omit } from '../../../../lib/utils/omit';
+import { callbacks } from '../../../../server/lib/callbacks';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { canAccessRoomAsync } from '../../../authorization/server/functions/canAccessRoom';
import { executeSendMessage } from '../../../lib/server/methods/sendMessage';
diff --git a/apps/meteor/app/importer-csv/server/CsvImporter.ts b/apps/meteor/app/importer-csv/server/CsvImporter.ts
index 4f5eeba7d88b9..cab23a46da62a 100644
--- a/apps/meteor/app/importer-csv/server/CsvImporter.ts
+++ b/apps/meteor/app/importer-csv/server/CsvImporter.ts
@@ -19,7 +19,7 @@ export class CsvImporter extends Importer {
this.csvParser = parse;
}
- async prepareUsingLocalFile(fullFilePath: string): Promise {
+ override async prepareUsingLocalFile(fullFilePath: string): Promise {
this.logger.debug('start preparing import operation');
await this.converter.clearImportData();
diff --git a/apps/meteor/app/importer-omnichannel-contacts/server/ContactImporter.ts b/apps/meteor/app/importer-omnichannel-contacts/server/ContactImporter.ts
index ca5f66bc05ea5..5415f10e63887 100644
--- a/apps/meteor/app/importer-omnichannel-contacts/server/ContactImporter.ts
+++ b/apps/meteor/app/importer-omnichannel-contacts/server/ContactImporter.ts
@@ -18,7 +18,7 @@ export class ContactImporter extends Importer {
this.csvParser = parse;
}
- async prepareUsingLocalFile(fullFilePath: string): Promise {
+ override async prepareUsingLocalFile(fullFilePath: string): Promise {
this.logger.debug('start preparing import operation');
await this.converter.clearImportData();
diff --git a/apps/meteor/app/importer-pending-avatars/server/PendingAvatarImporter.ts b/apps/meteor/app/importer-pending-avatars/server/PendingAvatarImporter.ts
index f057da4a625d4..746bb41681336 100644
--- a/apps/meteor/app/importer-pending-avatars/server/PendingAvatarImporter.ts
+++ b/apps/meteor/app/importer-pending-avatars/server/PendingAvatarImporter.ts
@@ -33,7 +33,7 @@ export class PendingAvatarImporter extends Importer {
return fileCount;
}
- async startImport(importSelection: IImporterShortSelection): Promise {
+ override async startImport(importSelection: IImporterShortSelection): Promise {
const pendingFileUserList = Users.findAllUsersWithPendingAvatar();
try {
for await (const user of pendingFileUserList) {
diff --git a/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts b/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts
index a6d147cf8df35..818031d07916e 100644
--- a/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts
+++ b/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts
@@ -43,7 +43,7 @@ export class PendingFileImporter extends Importer {
return fileCount;
}
- async startImport(importSelection: IImporterShortSelection): Promise {
+ override async startImport(importSelection: IImporterShortSelection): Promise {
const downloadedFileIds: string[] = [];
const maxFileCount = 10;
const maxFileSize = 1024 * 1024 * 500;
diff --git a/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts b/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts
index c05c6da8d690e..aa51f56daa47f 100644
--- a/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts
+++ b/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts
@@ -20,7 +20,7 @@ export class SlackUsersImporter extends Importer {
this.csvParser = parse;
}
- async prepareUsingLocalFile(fullFilePath: string): Promise {
+ override async prepareUsingLocalFile(fullFilePath: string): Promise {
this.logger.debug('start preparing import operation');
await this.converter.clearImportData();
diff --git a/apps/meteor/app/importer-slack/server/SlackImporter.ts b/apps/meteor/app/importer-slack/server/SlackImporter.ts
index c7e1b7c624ce7..f0fefed3f749e 100644
--- a/apps/meteor/app/importer-slack/server/SlackImporter.ts
+++ b/apps/meteor/app/importer-slack/server/SlackImporter.ts
@@ -267,7 +267,7 @@ export class SlackImporter extends Importer {
return data.length;
}
- async prepareUsingLocalFile(fullFilePath: string): Promise {
+ override async prepareUsingLocalFile(fullFilePath: string): Promise {
this.logger.debug('start preparing import operation');
await this.converter.clearImportData();
diff --git a/apps/meteor/app/importer/server/classes/converters/ContactConverter.ts b/apps/meteor/app/importer/server/classes/converters/ContactConverter.ts
index eb6b25acd3851..01067cbe118f8 100644
--- a/apps/meteor/app/importer/server/classes/converters/ContactConverter.ts
+++ b/apps/meteor/app/importer/server/classes/converters/ContactConverter.ts
@@ -16,7 +16,7 @@ export class ContactConverter extends RecordConverter {
return validateCustomFields(allowedCustomFields, customFields, { ignoreAdditionalFields: true });
}
- protected async convertRecord(record: IImportContactRecord): Promise {
+ protected override async convertRecord(record: IImportContactRecord): Promise {
const { data } = record;
await createContact({
@@ -36,7 +36,7 @@ export class ContactConverter extends RecordConverter {
return LivechatVisitors.getNextVisitorUsername();
}
- protected getDataType(): 'contact' {
+ protected override getDataType(): 'contact' {
return 'contact';
}
}
diff --git a/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts b/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts
index 732e46b4398bc..aa47be4434186 100644
--- a/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts
+++ b/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts
@@ -27,7 +27,7 @@ type IMessageReactions = Record;
export class MessageConverter extends RecordConverter {
private rids: string[] = [];
- async convertData({ afterImportAllMessagesFn, ...callbacks }: MessageConversionCallbacks = {}): Promise {
+ override async convertData({ afterImportAllMessagesFn, ...callbacks }: MessageConversionCallbacks = {}): Promise {
this.rids = [];
await super.convertData(callbacks);
@@ -76,7 +76,7 @@ export class MessageConverter extends RecordConverter {
}
}
- protected async convertRecord(record: IImportMessageRecord): Promise {
+ protected override async convertRecord(record: IImportMessageRecord): Promise {
await this.insertMessage(record.data);
return true;
}
@@ -257,7 +257,7 @@ export class MessageConverter extends RecordConverter {
}
}
- protected getDataType(): 'message' {
+ protected override getDataType(): 'message' {
return 'message';
}
}
diff --git a/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts b/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts
index ba7d01b0009ee..a19d5f0ae0fd3 100644
--- a/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts
+++ b/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts
@@ -20,7 +20,7 @@ export class RoomConverter extends RecordConverter {
return this.convertData(callbacks);
}
- protected async convertRecord(record: IImportChannelRecord): Promise {
+ protected override async convertRecord(record: IImportChannelRecord): Promise {
const { data } = record;
if (!data.name && data.t !== 'd') {
@@ -193,7 +193,7 @@ export class RoomConverter extends RecordConverter {
throw new Error('importer-channel-invalid-creator');
}
- protected getDataType(): 'channel' {
+ protected override getDataType(): 'channel' {
return 'channel';
}
}
diff --git a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts
index dbfbd9f0c94d9..efa721a518ddd 100644
--- a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts
+++ b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts
@@ -7,7 +7,7 @@ import { Accounts } from 'meteor/accounts-base';
import { RecordConverter, type RecordConverterOptions } from './RecordConverter';
import { generateTempPassword } from './generateTempPassword';
-import { callbacks as systemCallbacks } from '../../../../../lib/callbacks';
+import { callbacks as systemCallbacks } from '../../../../../server/lib/callbacks';
import { addUserToDefaultChannels } from '../../../../lib/server/functions/addUserToDefaultChannels';
import { generateUsernameSuggestion } from '../../../../lib/server/functions/getUsernameSuggestion';
import { saveUserIdentity } from '../../../../lib/server/functions/saveUserIdentity';
@@ -32,7 +32,7 @@ export class UserConverter extends RecordConverter();
- protected async convertRecord(record: IImportUserRecord): Promise {
+ protected override async convertRecord(record: IImportUserRecord): Promise {
const { data, _id } = record;
data.importIds = data.importIds.filter((item) => item);
@@ -55,7 +55,7 @@ export class UserConverter extends RecordConverter {
+ override async convertData(userCallbacks: IConversionCallbacks = {}): Promise {
this.insertedIds.clear();
this.updatedIds.clear();
@@ -424,7 +424,7 @@ export class UserConverter extends RecordConverter u.toUpperCase());
}
- protected getDataType(): 'user' {
+ protected override getDataType(): 'user' {
return 'user';
}
}
diff --git a/apps/meteor/app/integrations/server/api/api.ts b/apps/meteor/app/integrations/server/api/api.ts
index 59770587170a4..a9b77aadf65bb 100644
--- a/apps/meteor/app/integrations/server/api/api.ts
+++ b/apps/meteor/app/integrations/server/api/api.ts
@@ -9,10 +9,11 @@ import type { RateLimiterOptionsToCheck } from 'meteor/rate-limit';
import { WebApp } from 'meteor/webapp';
import _ from 'underscore';
+import { isPlainObject } from '../../../../lib/utils/isPlainObject';
import { APIClass } from '../../../api/server/ApiClass';
import type { RateLimiterOptions } from '../../../api/server/api';
import { API, defaultRateLimiterOptions } from '../../../api/server/api';
-import type { FailureResult, PartialThis, SuccessResult, UnavailableResult } from '../../../api/server/definition';
+import type { FailureResult, GenericRouteExecutionContext, SuccessResult, UnavailableResult } from '../../../api/server/definition';
import type { WebhookResponseItem } from '../../../lib/server/functions/processWebhookMessage';
import { processWebhookMessage } from '../../../lib/server/functions/processWebhookMessage';
import { settings } from '../../../settings/server';
@@ -39,11 +40,10 @@ type IntegrationOptions = {
};
};
-type IntegrationThis = Omit & {
+type IntegrationThis = GenericRouteExecutionContext & {
request: Request & {
integration: IIncomingIntegration;
};
- urlParams: Record;
user: IUser & { username: RequiredField };
};
@@ -138,8 +138,8 @@ async function executeIntegrationRest(
const scriptEngine = getEngine(this.request.integration);
- let { bodyParams } = this;
- const separateResponse = this.bodyParams?.separateResponse === true;
+ let bodyParams = isPlainObject(this.bodyParams) ? this.bodyParams : {};
+ const separateResponse = bodyParams.separateResponse === true;
let scriptResponse: Record | undefined;
if (scriptEngine.integrationHasValidScript(this.request.integration) && this.request.body) {
@@ -152,21 +152,22 @@ async function executeIntegrationRest(
const contentRaw = Buffer.concat(buffers).toString('utf8');
const protocol = `${this.request.headers.get('x-forwarded-proto')}:` || 'http:';
const url = new URL(this.request.url, `${protocol}//${this.request.headers.get('host')}`);
+ const query = isPlainObject(this.queryParams) ? this.queryParams : {};
const request = {
url: {
+ query,
hash: url.hash,
search: url.search,
- query: this.queryParams,
pathname: url.pathname,
path: this.request.url,
},
url_raw: this.request.url,
url_params: this.urlParams,
- content: this.bodyParams,
+ content: bodyParams,
content_raw: contentRaw,
headers: Object.fromEntries(this.request.headers.entries()),
- body: this.bodyParams,
+ body: bodyParams,
user: {
_id: this.user._id,
name: this.user.name || '',
@@ -187,11 +188,11 @@ async function executeIntegrationRest(
});
return API.v1.success();
}
- if (result && result.error) {
+ if (result?.error) {
return API.v1.failure(result.error);
}
- bodyParams = result && result.content;
+ bodyParams = result?.content;
if (!('separateResponse' in bodyParams)) {
bodyParams.separateResponse = separateResponse;
@@ -312,8 +313,8 @@ function integrationInfoRest(): { statusCode: number; body: { success: boolean }
}
class WebHookAPI extends APIClass<'/hooks'> {
- async authenticatedRoute(this: IntegrationThis): Promise {
- const { integrationId, token } = this.urlParams;
+ override async authenticatedRoute(routeContext: IntegrationThis): Promise {
+ const { integrationId, token } = routeContext.urlParams;
const integration = await Integrations.findOneByIdAndToken(integrationId, decodeURIComponent(token));
if (!integration) {
@@ -322,12 +323,12 @@ class WebHookAPI extends APIClass<'/hooks'> {
throw new Error('Invalid integration id or token provided.');
}
- this.request.integration = integration;
+ routeContext.request.integration = integration;
- return Users.findOneById(this.request.integration.userId);
+ return Users.findOneById(routeContext.request.integration.userId);
}
- shouldAddRateLimitToRoute(options: { rateLimiterOptions?: RateLimiterOptions | boolean }): boolean {
+ override shouldAddRateLimitToRoute(options: { rateLimiterOptions?: RateLimiterOptions | boolean }): boolean {
const { rateLimiterOptions } = options;
return (
(typeof rateLimiterOptions === 'object' || rateLimiterOptions === undefined) &&
@@ -336,14 +337,14 @@ class WebHookAPI extends APIClass<'/hooks'> {
);
}
- async shouldVerifyRateLimit(): Promise {
+ override async shouldVerifyRateLimit(): Promise {
return (
settings.get('API_Enable_Rate_Limiter') === true &&
(process.env.NODE_ENV !== 'development' || settings.get('API_Enable_Rate_Limiter_Dev') === true)
);
}
- async enforceRateLimit(
+ override async enforceRateLimit(
objectForRateLimitMatch: RateLimiterOptionsToCheck,
request: Request,
response: Response,
diff --git a/apps/meteor/app/integrations/server/lib/triggerHandler.ts b/apps/meteor/app/integrations/server/lib/triggerHandler.ts
index 9f4519e5b01e5..e3808da9ef8e9 100644
--- a/apps/meteor/app/integrations/server/lib/triggerHandler.ts
+++ b/apps/meteor/app/integrations/server/lib/triggerHandler.ts
@@ -216,12 +216,17 @@ class RocketChatIntegrationHandler {
}
break;
case 'roomJoined':
- case 'roomLeft':
if (args.length >= 3) {
argObject.user = args[1] as IUser;
argObject.room = args[2] as IRoom;
}
break;
+ case 'roomLeft':
+ if (args.length >= 3) {
+ argObject.user = (args[1] as { user: IUser })?.user;
+ argObject.room = args[2] as IRoom;
+ }
+ break;
case 'userCreated':
if (args.length >= 2) {
argObject.user = args[1] as IUser;
@@ -235,7 +240,9 @@ class RocketChatIntegrationHandler {
outgoingLogger.debug({
msg: `Got the event arguments for the event: ${argObject.event}`,
- argObject,
+ messageId: argObject.message?._id,
+ roomId: argObject.room?._id,
+ userId: argObject.user?._id || argObject.owner?._id,
});
return argObject;
diff --git a/apps/meteor/app/integrations/server/triggers.ts b/apps/meteor/app/integrations/server/triggers.ts
index 06fc9b0a9e1af..adf3d5b8bdf4d 100644
--- a/apps/meteor/app/integrations/server/triggers.ts
+++ b/apps/meteor/app/integrations/server/triggers.ts
@@ -1,6 +1,6 @@
import { triggerHandler } from './lib/triggerHandler';
-import { callbacks } from '../../../lib/callbacks';
-import { afterLeaveRoomCallback } from '../../../lib/callbacks/afterLeaveRoomCallback';
+import { callbacks } from '../../../server/lib/callbacks';
+import { afterLeaveRoomCallback } from '../../../server/lib/callbacks/afterLeaveRoomCallback';
const callbackHandler = function _callbackHandler(eventType: string) {
return function _wrapperFunction(...args: any[]) {
diff --git a/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts b/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts
index 052445a1ebc99..fa905cc345c6d 100644
--- a/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts
+++ b/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts
@@ -63,6 +63,13 @@ export const findOrCreateInvite = async (userId: string, invite: Pick {
if (!token || typeof token !== 'string') {
throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', {
@@ -25,6 +27,13 @@ export const validateInviteToken = async (token: string) => {
});
}
+ if (settings.get('ABAC_Enabled') && room?.abacAttributes?.length) {
+ throw new Meteor.Error('error-invalid-room', 'Room is ABAC managed', {
+ method: 'validateInviteToken',
+ field: 'rid',
+ });
+ }
+
if (inviteData.expires && new Date(inviteData.expires).getTime() <= Date.now()) {
throw new Meteor.Error('error-invite-expired', 'The invite token has expired.', {
method: 'validateInviteToken',
diff --git a/apps/meteor/app/irc/server/irc-bridge/index.js b/apps/meteor/app/irc/server/irc-bridge/index.js
index 26d4948bd0f19..b86dff943e45f 100644
--- a/apps/meteor/app/irc/server/irc-bridge/index.js
+++ b/apps/meteor/app/irc/server/irc-bridge/index.js
@@ -3,10 +3,10 @@ import { Settings } from '@rocket.chat/models';
import moment from 'moment';
import Queue from 'queue-fifo';
-import { callbacks } from '../../../../lib/callbacks';
-import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback';
-import { afterLogoutCleanUpCallback } from '../../../../lib/callbacks/afterLogoutCleanUpCallback';
import { withThrottling } from '../../../../lib/utils/highOrderFunctions';
+import { callbacks } from '../../../../server/lib/callbacks';
+import { afterLeaveRoomCallback } from '../../../../server/lib/callbacks/afterLeaveRoomCallback';
+import { afterLogoutCleanUpCallback } from '../../../../server/lib/callbacks/afterLogoutCleanUpCallback';
import { updateAuditedBySystem } from '../../../../server/settings/lib/auditedSettingUpdates';
import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import * as servers from '../servers';
diff --git a/apps/meteor/app/lib/client/methods/sendMessage.ts b/apps/meteor/app/lib/client/methods/sendMessage.ts
index 85117da3db345..0969ea8d9cf36 100644
--- a/apps/meteor/app/lib/client/methods/sendMessage.ts
+++ b/apps/meteor/app/lib/client/methods/sendMessage.ts
@@ -1,5 +1,6 @@
import type { IMessage } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
+import { clientCallbacks } from '@rocket.chat/ui-client';
import { Meteor } from 'meteor/meteor';
import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived';
@@ -7,7 +8,6 @@ import { settings } from '../../../../client/lib/settings';
import { dispatchToastMessage } from '../../../../client/lib/toast';
import { getUser, getUserId } from '../../../../client/lib/user';
import { Messages, Rooms } from '../../../../client/stores';
-import { callbacks } from '../../../../lib/callbacks';
import { trim } from '../../../../lib/utils/stringUtils';
import { t } from '../../../utils/lib/i18n';
@@ -44,7 +44,7 @@ Meteor.methods({
await onClientMessageReceived(message as IMessage).then((message) => {
Messages.state.store(message);
- return callbacks.run('afterSaveMessage', message, { room, user });
+ return clientCallbacks.run('afterSaveMessage', message, { room, user });
});
},
});
diff --git a/apps/meteor/app/lib/lib/MessageTypes.ts b/apps/meteor/app/lib/lib/MessageTypes.ts
index 2d83584da5c08..8205200615ff7 100644
--- a/apps/meteor/app/lib/lib/MessageTypes.ts
+++ b/apps/meteor/app/lib/lib/MessageTypes.ts
@@ -29,6 +29,14 @@ export const MessageTypesValues: Array<{ key: MessageTypesValuesType; i18nLabel:
key: 'au', // added user
i18nLabel: 'Message_HideType_au',
},
+ {
+ key: 'ui', // user invited to room
+ i18nLabel: 'Message_HideType_ui',
+ },
+ {
+ key: 'uir', // user rejected invitation to room
+ i18nLabel: 'Message_HideType_uir',
+ },
{
key: 'added-user-to-team',
i18nLabel: 'Message_HideType_added_user_to_team',
diff --git a/apps/meteor/app/lib/server/functions/acceptRoomInvite.ts b/apps/meteor/app/lib/server/functions/acceptRoomInvite.ts
new file mode 100644
index 0000000000000..6a406055dfd5d
--- /dev/null
+++ b/apps/meteor/app/lib/server/functions/acceptRoomInvite.ts
@@ -0,0 +1,61 @@
+import { Apps, AppEvents } from '@rocket.chat/apps';
+import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
+import { Message } from '@rocket.chat/core-services';
+import type { IUser, IRoom, ISubscription } from '@rocket.chat/core-typings';
+import { Subscriptions, Users } from '@rocket.chat/models';
+import { Meteor } from 'meteor/meteor';
+
+import { callbacks } from '../../../../server/lib/callbacks';
+import { notifyOnSubscriptionChangedById } from '../lib/notifyListener';
+
+/**
+ * Accepts a room invite when triggered by internal events such as federation
+ * or third-party callbacks. Performs the necessary database updates and triggers
+ * safe callbacks, ensuring no propagation loops are created during external event
+ * processing.
+ */
+
+// TODO this funcion is pretty much the same as the one in addUserToRoom.ts, we should probably
+// unify them at some point
+export const performAcceptRoomInvite = async (
+ room: IRoom,
+ subscription: ISubscription,
+ user: IUser & { username: string },
+): Promise => {
+ if (subscription.status !== 'INVITED' || !subscription.inviter) {
+ throw new Meteor.Error('error-not-invited', `User was not invited to this room ${subscription.status}`);
+ }
+ const inviter = await Users.findOneById(subscription.inviter._id);
+
+ await callbacks.run('beforeJoinRoom', user, room);
+
+ await callbacks.run('beforeAddedToRoom', { user, inviter }, room);
+
+ try {
+ await Apps.self?.triggerEvent(AppEvents.IPreRoomUserJoined, room, user, inviter);
+ } catch (error: any) {
+ if (error.name === AppsEngineException.name) {
+ throw new Meteor.Error('error-app-prevented', error.message);
+ }
+
+ throw error;
+ }
+
+ await Subscriptions.acceptInvitationById(subscription._id);
+
+ void notifyOnSubscriptionChangedById(subscription._id, 'updated');
+
+ await Message.saveSystemMessage('uj', room._id, user.username, user);
+
+ if (room.t === 'c' || room.t === 'p') {
+ process.nextTick(async () => {
+ // Add a new event, with an optional inviter
+ await callbacks.run('afterAddedToRoom', { user, inviter }, room);
+
+ // Keep the current event
+ await callbacks.run('afterJoinRoom', user, room);
+
+ void Apps.self?.triggerEvent(AppEvents.IPostRoomUserJoined, room, user, inviter);
+ });
+ }
+};
diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
index f5317a7b0e3b3..89f4e5e352755 100644
--- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
@@ -3,8 +3,9 @@ import type { IUser } from '@rocket.chat/core-typings';
import { Subscriptions } from '@rocket.chat/models';
import { getDefaultChannels } from './getDefaultChannels';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
+import { settings } from '../../../settings/server';
import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
import { notifyOnSubscriptionChangedById } from '../lib/notifyListener';
@@ -13,6 +14,10 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?:
const defaultRooms = await getDefaultChannels();
for await (const room of defaultRooms) {
+ if (settings.get('ABAC_Enabled') && room?.abacAttributes?.length) {
+ continue;
+ }
+
if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) {
const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user);
diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
index 881afb11812c7..6ebfd84844878 100644
--- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
@@ -1,18 +1,17 @@
import { Apps, AppEvents } from '@rocket.chat/apps';
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
-import { Message, Team } from '@rocket.chat/core-services';
-import { type IUser } from '@rocket.chat/core-typings';
+import { Team, Room } from '@rocket.chat/core-services';
+import { isRoomNativeFederated, type IUser } from '@rocket.chat/core-typings';
import { Subscriptions, Users, Rooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
-import { callbacks } from '../../../../lib/callbacks';
-import { beforeAddUserToRoom } from '../../../../lib/callbacks/beforeAddUserToRoom';
-import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
+import { callbacks } from '../../../../server/lib/callbacks';
+import { beforeAddUserToRoom } from '../../../../server/lib/callbacks/beforeAddUserToRoom';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { settings } from '../../../settings/server';
-import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
-import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
+import { beforeAddUserToRoom as beforeAddUserToRoomPatch } from '../lib/beforeAddUserToRoom';
+import { notifyOnRoomChangedById } from '../lib/notifyListener';
/**
* This function adds user to the given room.
@@ -49,6 +48,12 @@ export const addUserToRoom = async (
throw new Meteor.Error('user-not-found');
}
+ // Check if user is already in room
+ const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userToBeAdded._id);
+ if (subscription) {
+ return;
+ }
+
if (
!(await roomDirectives.allowMemberAction(room, RoomMemberActions.JOIN, userToBeAdded._id)) &&
!(await roomDirectives.allowMemberAction(room, RoomMemberActions.INVITE, userToBeAdded._id))
@@ -57,7 +62,10 @@ export const addUserToRoom = async (
}
try {
- await beforeAddUserToRoom.run({ user: userToBeAdded, inviter: (inviter && (await Users.findOneById(inviter._id))) || undefined }, room);
+ const inviterUser = inviter && ((await Users.findOneById(inviter._id)) || undefined);
+ // Not "duplicated": we're moving away from callbacks so this is a patch function. We should migrate the next one to be a patch or use this same patch, instead of calling both
+ await beforeAddUserToRoomPatch([userToBeAdded.username!], room, inviterUser);
+ await beforeAddUserToRoom.run({ user: userToBeAdded, inviter: inviterUser }, room);
} catch (error) {
throw new Meteor.Error((error as any)?.message);
}
@@ -66,12 +74,6 @@ export const addUserToRoom = async (
await callbacks.run('beforeAddedToRoom', { user: userToBeAdded, inviter });
- // Check if user is already in room
- const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userToBeAdded._id);
- if (subscription || !userToBeAdded) {
- return;
- }
-
try {
await Apps.self?.triggerEvent(AppEvents.IPreRoomUserJoined, room, userToBeAdded, inviter);
} catch (error: any) {
@@ -81,6 +83,12 @@ export const addUserToRoom = async (
throw error;
}
+
+ // for federation rooms we stop here since everything else will be handled by the federation invite flow
+ if (isRoomNativeFederated(room)) {
+ return;
+ }
+
// TODO: are we calling this twice?
if (room.t === 'c' || room.t === 'p' || room.t === 'l') {
// Add a new event, with an optional inviter
@@ -90,50 +98,16 @@ export const addUserToRoom = async (
await callbacks.run('beforeJoinRoom', userToBeAdded, room);
}
- const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded);
-
- const { insertedId } = await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, {
+ await Room.createUserSubscription({
+ room,
ts: now,
- open: !createAsHidden,
- alert: createAsHidden ? false : !skipAlertSound,
- unread: 1,
- userMentions: 1,
- groupMentions: 0,
- ...autoTranslateConfig,
- ...getDefaultSubscriptionPref(userToBeAdded as IUser),
+ inviter,
+ userToBeAdded,
+ createAsHidden,
+ skipAlertSound,
+ skipSystemMessage,
});
- if (insertedId) {
- void notifyOnSubscriptionChangedById(insertedId, 'inserted');
- }
-
- if (!userToBeAdded.username) {
- throw new Meteor.Error('error-invalid-user', 'Cannot add an user to a room without a username');
- }
-
- if (!skipSystemMessage) {
- if (inviter) {
- const extraData = {
- ts: now,
- u: {
- _id: inviter._id,
- username: inviter.username,
- },
- };
- if (room.teamMain) {
- await Message.saveSystemMessage('added-user-to-team', rid, userToBeAdded.username, userToBeAdded, extraData);
- } else {
- await Message.saveSystemMessage('au', rid, userToBeAdded.username, userToBeAdded, extraData);
- }
- } else if (room.prid) {
- await Message.saveSystemMessage('ut', rid, userToBeAdded.username, userToBeAdded, { ts: now });
- } else if (room.teamMain) {
- await Message.saveSystemMessage('ujt', rid, userToBeAdded.username, userToBeAdded, { ts: now });
- } else {
- await Message.saveSystemMessage('uj', rid, userToBeAdded.username, userToBeAdded, { ts: now });
- }
- }
-
if (room.t === 'c' || room.t === 'p') {
process.nextTick(async () => {
// Add a new event, with an optional inviter
diff --git a/apps/meteor/app/lib/server/functions/archiveRoom.ts b/apps/meteor/app/lib/server/functions/archiveRoom.ts
index 46fd7a1ac35ba..9bc3582d6671d 100644
--- a/apps/meteor/app/lib/server/functions/archiveRoom.ts
+++ b/apps/meteor/app/lib/server/functions/archiveRoom.ts
@@ -2,7 +2,7 @@ import { Message } from '@rocket.chat/core-services';
import type { IMessage } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions } from '@rocket.chat/models';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener';
export const archiveRoom = async function (rid: string, user: IMessage['u']): Promise {
diff --git a/apps/meteor/app/lib/server/functions/closeOmnichannelConversations.ts b/apps/meteor/app/lib/server/functions/closeOmnichannelConversations.ts
index e66c0ab3edd2a..49aa6e44d55b5 100644
--- a/apps/meteor/app/lib/server/functions/closeOmnichannelConversations.ts
+++ b/apps/meteor/app/lib/server/functions/closeOmnichannelConversations.ts
@@ -1,7 +1,7 @@
import type { IUser } from '@rocket.chat/core-typings';
import { LivechatRooms } from '@rocket.chat/models';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { i18n } from '../../../../server/lib/i18n';
import { closeRoom } from '../../../livechat/server/lib/closeRoom';
import { settings } from '../../../settings/server';
diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts
index 7f26a78d85089..029cebc352d50 100644
--- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts
+++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts
@@ -7,8 +7,9 @@ import { Random } from '@rocket.chat/random';
import { Meteor } from 'meteor/meteor';
import type { MatchKeysAndValues } from 'mongodb';
-import { callbacks } from '../../../../lib/callbacks';
import { isTruthy } from '../../../../lib/isTruthy';
+import { callbacks } from '../../../../server/lib/callbacks';
+import { getNameForDMs } from '../../../../server/services/room/getNameForDMs';
import { settings } from '../../../settings/server';
import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener';
@@ -37,16 +38,13 @@ const generateSubscription = (
},
});
-const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', ');
-const getName = (members: IUser[]): string => members.map(({ username }) => username).join(', ');
-
export async function createDirectRoom(
members: IUser[] | string[],
roomExtraData: Partial = {},
options: {
- creator?: string;
+ forceNew?: boolean;
+ creator?: IUser['_id'];
subscriptionExtra?: ISubscriptionExtraData;
- federatedRoomId?: string;
},
): Promise {
const maxUsers = settings.get('DirectMesssage_maxUsers') || 1;
@@ -72,6 +70,7 @@ export async function createDirectRoom(
await callbacks.run('beforeCreateDirectRoom', membersUsernames, roomExtraData);
const roomMembers = await Users.findUsersByUsernames(membersUsernames).toArray();
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sortedMembers = roomMembers.sort((u1, u2) => (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!));
@@ -79,15 +78,11 @@ export async function createDirectRoom(
const uids = roomMembers.map(({ _id }) => _id).sort();
// Deprecated: using users' _id to compose the room _id is deprecated
- const room: IRoom | null =
- uids.length === 2
- ? await Rooms.findOneById(uids.join(''), { projection: { _id: 1 } })
- : await Rooms.findOneDirectRoomContainingAllUserIDs(uids, { projection: { _id: 1 } });
+ const room: IRoom | null = options?.forceNew ? null : await Rooms.findOneDirectRoomContainingAllUserIDs(uids, { projection: { _id: 1 } });
const isNewRoom = !room;
const roomInfo = {
- ...(uids.length === 2 && { _id: uids.join('') }), // Deprecated: using users' _id to compose the room _id is deprecated
t: 'd',
usernames,
usersCount: members.length,
@@ -155,15 +150,40 @@ export async function createDirectRoom(
{ projection: { 'username': 1, 'settings.preferences': 1 } },
).toArray();
+ const creatorUser = roomMembers.find((member) => member._id === options?.creator);
+ if (roomExtraData.federated && !creatorUser) {
+ throw new Meteor.Error('error-creator-not-in-room', 'The creator user must be part of the direct room');
+ }
+
+ const roomNames = getNameForDMs(roomMembers);
+
for await (const member of membersWithPreferences) {
- const otherMembers = sortedMembers.filter(({ _id }) => _id !== member._id);
+ const subscriptionStatus: Partial =
+ roomExtraData.federated && options.creator !== member._id && creatorUser
+ ? {
+ status: 'INVITED',
+ inviter: {
+ _id: creatorUser._id,
+ username: creatorUser.username!,
+ name: creatorUser.name,
+ },
+ open: true,
+ unread: 1,
+ userMentions: 1,
+ }
+ : {};
+
+ const { fname, name } = roomNames[member._id];
+
const { modifiedCount, upsertedCount } = await Subscriptions.updateOne(
{ rid, 'u._id': member._id },
{
...(options?.creator === member._id && { $set: { open: true } }),
- $setOnInsert: generateSubscription(getFname(otherMembers), getName(otherMembers), member, {
+ $setOnInsert: generateSubscription(fname, name, member, {
...options?.subscriptionExtra,
...(options?.creator !== member._id && { open: members.length > 2 }),
+ ...subscriptionStatus,
+ ...(roomExtraData.federated && member._id === options?.creator && { roles: ['owner'] }),
}),
},
{ upsert: true },
@@ -181,7 +201,6 @@ export async function createDirectRoom(
await callbacks.run('afterCreateDirectRoom', insertedRoom, {
members: roomMembers,
creatorId: options?.creator,
- mrid: options?.federatedRoomId,
});
void Apps.self?.triggerEvent(AppEvents.IPostRoomCreate, insertedRoom);
diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts
index 3d9e3b1810570..7b64ec9e68b09 100644
--- a/apps/meteor/app/lib/server/functions/createRoom.ts
+++ b/apps/meteor/app/lib/server/functions/createRoom.ts
@@ -1,17 +1,16 @@
import { AppEvents, Apps } from '@rocket.chat/apps';
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
-import { Message, Team } from '@rocket.chat/core-services';
+import { FederationMatrix, Message, Room, Team } from '@rocket.chat/core-services';
import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services';
-import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings';
-import { isRoomNativeFederated } from '@rocket.chat/core-typings';
+import { type ICreatedRoom, type IUser, type IRoom, type RoomType, isUserNativeFederated } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { createDirectRoom } from './createDirectRoom';
-import { callbacks } from '../../../../lib/callbacks';
-import { beforeAddUserToRoom } from '../../../../lib/callbacks/beforeAddUserToRoom';
-import { beforeCreateRoomCallback, prepareCreateRoomCallback } from '../../../../lib/callbacks/beforeCreateRoomCallback';
import { calculateRoomRolePriorityFromRoles } from '../../../../lib/roles/calculateRoomRolePriorityFromRoles';
+import { callbacks } from '../../../../server/lib/callbacks';
+import { beforeAddUserToRoom } from '../../../../server/lib/callbacks/beforeAddUserToRoom';
+import { beforeCreateRoomCallback, prepareCreateRoomCallback } from '../../../../server/lib/callbacks/beforeCreateRoomCallback';
import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
import { syncRoomRolePriorityForUserAndRoom } from '../../../../server/lib/roles/syncRoomRolePriority';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
@@ -59,6 +58,27 @@ async function createUsersSubscriptions({
await notifyOnRoomChanged(room, 'inserted');
}
+ // Invite federated members to the room SYNCRONOUSLY,
+ // since we do not use to invite lots of users at once, this is acceptable.
+ const membersToInvite = members.filter((m) => m !== owner.username);
+
+ await FederationMatrix.ensureFederatedUsersExistLocally(membersToInvite);
+
+ for await (const memberUsername of membersToInvite) {
+ const member = await Users.findOneByUsername(memberUsername);
+ if (!member) {
+ throw new Error('Federated user not found locally');
+ }
+
+ await Room.createUserSubscription({
+ ts: new Date(),
+ room,
+ userToBeAdded: member,
+ inviter: owner,
+ status: 'INVITED',
+ });
+ }
+
return;
}
@@ -135,7 +155,7 @@ export const createRoom = async (
rid: string;
}
> => {
- const { teamId, ...optionalExtraData } = roomExtraData || ({} as IRoom);
+ const { teamId, ...extraData } = roomExtraData || ({} as IRoom);
// TODO: use a shared helper to check whether a user is federated
const hasFederatedMembers = members.some((member) => {
@@ -146,23 +166,12 @@ export const createRoom = async (
});
// Prevent adding federated users to rooms that are not marked as federated explicitly
- if (hasFederatedMembers && optionalExtraData.federated !== true) {
+ if (hasFederatedMembers && extraData.federated !== true) {
throw new Meteor.Error('error-federated-users-in-non-federated-rooms', 'Cannot add federated users to non-federated rooms', {
method: 'createRoom',
});
}
- const extraData = {
- ...optionalExtraData,
- ...((hasFederatedMembers || optionalExtraData.federated) && {
- federated: true,
- federation: {
- version: 1,
- // TODO we should be able to provide all values from here, currently we update on callback afterCreateRoom
- },
- }),
- };
-
await prepareCreateRoomCallback.run({
type,
// name,
@@ -173,15 +182,21 @@ export const createRoom = async (
// options,
});
- const shouldBeHandledByFederation = isRoomNativeFederated(extraData);
- if (shouldBeHandledByFederation && owner && !(await hasPermissionAsync(owner._id, 'access-federation'))) {
+ const shouldBeHandledByFederation = extraData.federated === true;
+
+ if (
+ shouldBeHandledByFederation &&
+ owner &&
+ !isUserNativeFederated(owner) &&
+ !(await hasPermissionAsync(owner._id, 'access-federation'))
+ ) {
throw new Meteor.Error('error-not-authorized-federation', 'Not authorized to access federation', {
method: 'createRoom',
});
}
if (type === 'd') {
- return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || owner?.username });
+ return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || owner?._id });
}
if (!onlyUsernames(members)) {
@@ -286,6 +301,13 @@ export const createRoom = async (
void notifyOnRoomChanged(room, 'inserted');
+ // If federated, we must create Matrix room BEFORE subscriptions so invites can be sent.
+ if (shouldBeHandledByFederation) {
+ // Reusing unused callback to create Matrix room.
+ // We should discuss the opportunity to rename it to something with "before" prefix.
+ await callbacks.run('federation.afterCreateFederatedRoom', room, { owner, originalMemberList: members, options });
+ }
+
await createUsersSubscriptions({ room, members, now, owner, options, shouldBeHandledByFederation });
if (type === 'c') {
@@ -301,10 +323,6 @@ export const createRoom = async (
}
callbacks.runAsync('afterCreateRoom', owner, room);
- if (shouldBeHandledByFederation) {
- callbacks.runAsync('federation.afterCreateFederatedRoom', room, { owner, originalMemberList: members, options });
- }
-
void Apps.self?.triggerEvent(AppEvents.IPostRoomCreate, room);
return {
rid: room._id, // backwards compatible
diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts
index 0be9a8b78e9ba..1c3fb32ef28bd 100644
--- a/apps/meteor/app/lib/server/functions/deleteMessage.ts
+++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts
@@ -4,7 +4,7 @@ import { isThreadMessage, type AtLeast, type IMessage, type IRoom, type IThreadM
import { Messages, Rooms, Uploads, Users, ReadReceipts, Subscriptions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { canDeleteMessageAsync } from '../../../authorization/server/functions/canDeleteMessage';
import { FileUpload } from '../../../file-upload/server';
import { settings } from '../../../settings/server';
diff --git a/apps/meteor/app/lib/server/functions/deleteRoom.ts b/apps/meteor/app/lib/server/functions/deleteRoom.ts
index 386ba8da8b94d..d9af32b4faa82 100644
--- a/apps/meteor/app/lib/server/functions/deleteRoom.ts
+++ b/apps/meteor/app/lib/server/functions/deleteRoom.ts
@@ -1,6 +1,6 @@
import { Messages, Rooms, Subscriptions } from '@rocket.chat/models';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { FileUpload } from '../../../file-upload/server';
import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener';
diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts
index 925dffb84ef3d..02847707a8765 100644
--- a/apps/meteor/app/lib/server/functions/deleteUser.ts
+++ b/apps/meteor/app/lib/server/functions/deleteUser.ts
@@ -20,7 +20,7 @@ import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from
import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms';
import { relinquishRoomOwnerships } from './relinquishRoomOwnerships';
import { updateGroupDMsName } from './updateGroupDMsName';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import { i18n } from '../../../../server/lib/i18n';
import { FileUpload } from '../../../file-upload/server';
import { settings } from '../../../settings/server';
diff --git a/apps/meteor/app/lib/server/functions/getFullUserData.ts b/apps/meteor/app/lib/server/functions/getFullUserData.ts
index c55a5464a2a8b..f1e1be0af0499 100644
--- a/apps/meteor/app/lib/server/functions/getFullUserData.ts
+++ b/apps/meteor/app/lib/server/functions/getFullUserData.ts
@@ -19,9 +19,10 @@ export const defaultFields = {
reason: 1,
statusText: 1,
avatarETag: 1,
- extension: 1,
federated: 1,
statusLivechat: 1,
+ abacAttributes: 1,
+ freeSwitchExtension: 1,
} as const;
export const fullFields = {
@@ -35,7 +36,6 @@ export const fullFields = {
requirePasswordChangeReason: 1,
roles: 1,
importIds: 1,
- freeSwitchExtension: 1,
} as const;
let publicCustomFields: Record = {};
@@ -86,7 +86,6 @@ export async function getFullUserDataByIdOrUsernameOrImportId(
(searchType === 'username' && searchValue === caller.username) ||
(searchType === 'importId' && caller.importIds?.includes(searchValue));
const canViewAllInfo = !!myself || (await hasPermissionAsync(userId, 'view-full-other-user-info'));
- const canViewExtension = !!myself || (await hasPermissionAsync(userId, 'view-user-voip-extension'));
// Only search for importId if the user has permission to view the import id
if (searchType === 'importId' && !canViewAllInfo) {
@@ -98,7 +97,6 @@ export async function getFullUserDataByIdOrUsernameOrImportId(
const options = {
projection: {
...fields,
- ...(canViewExtension && { freeSwitchExtension: 1 }),
...(myself && { services: 1 }),
},
};
diff --git a/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts b/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts
index 63e6dc52b9142..2a14900e10a65 100644
--- a/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts
+++ b/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts
@@ -1,12 +1,14 @@
-export const getModifiedHttpHeaders = (httpHeaders: Headers | Record) => {
- let modifiedHttpHeaders: { [k: string]: string };
-
+export const normalizeHeaders = (httpHeaders?: Headers | Record) => {
if (httpHeaders instanceof Headers) {
- modifiedHttpHeaders = { ...Object.fromEntries(httpHeaders.entries()) };
- } else {
- modifiedHttpHeaders = { ...httpHeaders };
+ return { ...Object.fromEntries(httpHeaders.entries()) };
}
+ return { ...httpHeaders };
+};
+
+export const getModifiedHttpHeaders = (httpHeaders: Headers | Record) => {
+ const modifiedHttpHeaders = normalizeHeaders(httpHeaders);
+
if ('x-auth-token' in modifiedHttpHeaders) {
modifiedHttpHeaders['x-auth-token'] = '[redacted]';
}
diff --git a/apps/meteor/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts b/apps/meteor/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts
index e1aeabe1b46a5..6217342d0c534 100644
--- a/apps/meteor/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts
+++ b/apps/meteor/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts
@@ -14,7 +14,7 @@ export const getRoomByNameOrIdWithOptionToJoin = async ({
joinChannel = true,
errorOnEmpty = true,
}: {
- user: Pick;
+ user: Pick;
nameOrId: string;
type?: RoomType;
tryDirectByUserIdOnly?: boolean;
diff --git a/apps/meteor/app/lib/server/functions/notifications/email.js b/apps/meteor/app/lib/server/functions/notifications/email.js
index 8fa1eb02a29fc..fa99f7eeb2048 100644
--- a/apps/meteor/app/lib/server/functions/notifications/email.js
+++ b/apps/meteor/app/lib/server/functions/notifications/email.js
@@ -1,8 +1,8 @@
import { escapeHTML } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';
-import { callbacks } from '../../../../../lib/callbacks';
import { ltrim } from '../../../../../lib/utils/stringUtils';
+import { callbacks } from '../../../../../server/lib/callbacks';
import { i18n } from '../../../../../server/lib/i18n';
import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator';
import * as Mailer from '../../../../mailer/server/api';
diff --git a/apps/meteor/app/lib/server/functions/notifications/index.ts b/apps/meteor/app/lib/server/functions/notifications/index.ts
index 538ff2ad5ed91..47a38ac0f068c 100644
--- a/apps/meteor/app/lib/server/functions/notifications/index.ts
+++ b/apps/meteor/app/lib/server/functions/notifications/index.ts
@@ -2,7 +2,7 @@ import type { IMessage, IUser } from '@rocket.chat/core-typings';
import { isFileAttachment, isFileImageAttachment } from '@rocket.chat/core-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';
-import { callbacks } from '../../../../../lib/callbacks';
+import { callbacks } from '../../../../../server/lib/callbacks';
import { i18n } from '../../../../../server/lib/i18n';
import { settings } from '../../../../settings/server';
diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
index 23e82389cb271..b58710aaeb5e1 100644
--- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
+++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
@@ -1,65 +1,67 @@
import { Apps, AppEvents } from '@rocket.chat/apps';
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
import { Message, Team, Room } from '@rocket.chat/core-services';
-import type { IUser } from '@rocket.chat/core-typings';
+import type { IRoom, IUser, MessageTypesValues } from '@rocket.chat/core-typings';
import { Subscriptions, Rooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
-import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback';
-import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback';
+import { afterLeaveRoomCallback } from '../../../../server/lib/callbacks/afterLeaveRoomCallback';
+import { beforeLeaveRoomCallback } from '../../../../server/lib/callbacks/beforeLeaveRoomCallback';
import { settings } from '../../../settings/server';
import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener';
-export const removeUserFromRoom = async function (rid: string, user: IUser, options?: { byUser: IUser }): Promise {
- const room = await Rooms.findOneById(rid);
-
- if (!room) {
+/**
+ * Removes a user from a room when triggered by federation or other external events.
+ * Executes only the necessary database operations, with no callbacks, to prevent
+ * propagation loops during external event processing.
+ */
+export const performUserRemoval = async function (
+ room: IRoom,
+ user: IUser,
+ options?: { byUser?: IUser; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
+): Promise {
+ const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, {
+ projection: { _id: 1, status: 1 },
+ });
+ if (!subscription) {
return;
}
- try {
- await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user, options?.byUser);
- } catch (error: any) {
- if (error.name === AppsEngineException.name) {
- throw new Meteor.Error('error-app-prevented', error.message);
- }
-
- throw error;
+ // make TS happy, this should never happen
+ if (!user.username) {
+ throw new Error('User must have a username to be removed from the room');
}
- await Room.beforeLeave(room);
-
// TODO: move before callbacks to service
await beforeLeaveRoomCallback.run(user, room);
- const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id, {
- projection: { _id: 1 },
- });
-
if (subscription) {
- const removedUser = user;
- if (options?.byUser) {
+ if (options?.customSystemMessage) {
+ await Message.saveSystemMessage(options?.customSystemMessage, room._id, user.username || '', user);
+ } else if (options?.byUser) {
const extraData = {
u: options.byUser,
};
if (room.teamMain) {
- await Message.saveSystemMessage('removed-user-from-team', rid, user.username || '', user, extraData);
+ await Message.saveSystemMessage('removed-user-from-team', room._id, user.username, user, extraData);
} else {
- await Message.saveSystemMessage('ru', rid, user.username || '', user, extraData);
+ await Message.saveSystemMessage('ru', room._id, user.username, user, extraData);
}
+ } else if (subscription.status === 'INVITED') {
+ await Message.saveSystemMessage('uir', room._id, user.username, user);
} else if (room.teamMain) {
- await Message.saveSystemMessage('ult', rid, removedUser.username || '', removedUser);
+ await Message.saveSystemMessage('ult', room._id, user.username, user);
} else {
- await Message.saveSystemMessage('ul', rid, removedUser.username || '', removedUser);
+ await Message.saveSystemMessage('ul', room._id, user.username, user);
}
}
if (room.t === 'l') {
- await Message.saveSystemMessage('command', rid, 'survey', user);
+ await Message.saveSystemMessage('command', room._id, 'survey', user);
}
- const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id);
+ const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(room._id, user._id);
if (deletedSubscription) {
void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
}
@@ -72,10 +74,47 @@ export const removeUserFromRoom = async function (rid: string, user: IUser, opti
await Rooms.removeUsersFromE2EEQueueByRoomId(room._id, [user._id]);
}
- // TODO: CACHE: maybe a queue?
- await afterLeaveRoomCallback.run({ user, kicker: options?.byUser }, room);
+ // remove references to the user in direct message rooms
+ if (room.t === 'd') {
+ await Rooms.removeUserReferenceFromDMsById(room._id, user.username, user._id);
+ }
+
+ void notifyOnRoomChangedById(room._id);
+};
- void notifyOnRoomChangedById(rid);
+/**
+ * Removes a user from the given room by performing the required database updates
+ * and triggering all standard callbacks. Used for local actions (UI or API)
+ * that should propagate normally to federation and other subscribers.
+ */
+export const removeUserFromRoom = async function (
+ rid: string,
+ user: IUser,
+ options?: { byUser?: IUser; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
+): Promise {
+ const room = await Rooms.findOneById(rid);
+ if (!room) {
+ return;
+ }
+
+ // Rationale: for an abac room, we don't want apps to be able to prevent a user from leaving
+ if (!options?.skipAppPreEvents) {
+ try {
+ await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user, options?.byUser);
+ } catch (error: any) {
+ if (error.name === AppsEngineException.name) {
+ throw new Meteor.Error('error-app-prevented', error.message);
+ }
+
+ throw error;
+ }
+ }
+
+ await Room.beforeLeave(room);
+
+ await performUserRemoval(room, user, options);
+
+ await afterLeaveRoomCallback.run({ user, kicker: options?.byUser }, room);
await Apps.self?.triggerEvent(AppEvents.IPostRoomUserLeave, room, user, options?.byUser);
};
diff --git a/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts b/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts
index e77e56cc84b9d..6665177794e73 100644
--- a/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts
+++ b/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts
@@ -6,9 +6,17 @@ import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import type { ClientSession } from 'mongodb';
-import { callbacks } from '../../../../../lib/callbacks';
+import { handleBio } from './handleBio';
+import { handleNickname } from './handleNickname';
+import { saveNewUser } from './saveNewUser';
+import { sendPasswordEmail } from './sendUserEmail';
+import { setPasswordUpdater } from './setPasswordUpdater';
+import { validateUserData } from './validateUserData';
+import { validateUserEditing } from './validateUserEditing';
import { wrapInSessionTransaction, onceTransactionCommitedSuccessfully } from '../../../../../server/database/utils';
import type { UserChangedAuditStore } from '../../../../../server/lib/auditServerEvents/userChanged';
+import { callbacks } from '../../../../../server/lib/callbacks';
+import { shouldBreakInVersion } from '../../../../../server/lib/shouldBreakInVersion';
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { safeGetMeteorUser } from '../../../../utils/server/functions/safeGetMeteorUser';
import { generatePassword } from '../../lib/generatePassword';
@@ -18,14 +26,6 @@ import { saveCustomFields } from '../saveCustomFields';
import { saveUserIdentity } from '../saveUserIdentity';
import { setEmail } from '../setEmail';
import { setStatusText } from '../setStatusText';
-import { handleBio } from './handleBio';
-import { handleNickname } from './handleNickname';
-import { saveNewUser } from './saveNewUser';
-import { sendPasswordEmail } from './sendUserEmail';
-import { setPasswordUpdater } from './setPasswordUpdater';
-import { validateUserData } from './validateUserData';
-import { validateUserEditing } from './validateUserEditing';
-import { shouldBreakInVersion } from '../../../../../server/lib/shouldBreakInVersion';
export type SaveUserData = {
_id?: IUser['_id'];
@@ -125,7 +125,7 @@ const _saveUser = (session?: ClientSession) =>
}
if (typeof userData.statusText === 'string') {
- await setStatusText(userData._id, userData.statusText, updater, session);
+ await setStatusText(userData._id, userData.statusText, { updater, session });
}
if (userData.email) {
diff --git a/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts b/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts
index 2fd4a3f0e6f42..529568854c68c 100644
--- a/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts
+++ b/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts
@@ -12,12 +12,8 @@ const isEditingUserRoles = (previousRoles: IUser['roles'], newRoles?: IUser['rol
(newRoles.some((item) => !previousRoles.includes(item)) || previousRoles.some((item) => !newRoles.includes(item)));
const isEditingField = (previousValue?: string, newValue?: string) => typeof newValue !== 'undefined' && newValue !== previousValue;
-export const canEditExtension = async (userId: string, newExtension?: string) => {
- if (!settings.get('VoIP_TeamCollab_Enabled')) {
- return false;
- }
-
- if (!(await hasPermissionAsync(userId, 'manage-voip-extensions'))) {
+export const canEditExtension = async (newExtension?: string) => {
+ if (!settings.get('VoIP_TeamCollab_SIP_Integration_Enabled')) {
return false;
}
@@ -117,7 +113,7 @@ export async function validateUserEditing(userId: IUser['_id'], userData: Update
if (
isEditingField(user.freeSwitchExtension ?? '', userData.freeSwitchExtension) &&
- !(await canEditExtension(userId, userData.freeSwitchExtension))
+ !(await canEditExtension(userData.freeSwitchExtension))
) {
throw new MeteorError('error-action-not-allowed', 'Edit user voice call extension is not allowed', {
method: 'insertOrUpdateUser',
diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
index 62c127b46325d..e943e1b9128ef 100644
--- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
+++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
-import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptions, Users } from '@rocket.chat/models';
+import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptions, Users, CallHistory } from '@rocket.chat/models';
import type { ClientSession } from 'mongodb';
import { _setRealName } from './setRealName';
@@ -181,5 +181,8 @@ async function updateUsernameReferences({
// update name and username of users on video conferences
await VideoConference.updateUserReferences(user._id, username || previousUsername, name || previousName);
+
+ // update name and username of users on call history
+ await CallHistory.updateUserReferences(user._id, username || previousUsername, name || previousName);
}
}
diff --git a/apps/meteor/app/lib/server/functions/sendMessage.ts b/apps/meteor/app/lib/server/functions/sendMessage.ts
index adcd9592b3ae3..6af459f2332f1 100644
--- a/apps/meteor/app/lib/server/functions/sendMessage.ts
+++ b/apps/meteor/app/lib/server/functions/sendMessage.ts
@@ -1,5 +1,5 @@
import { Apps } from '@rocket.chat/apps';
-import { api, Message } from '@rocket.chat/core-services';
+import { Message } from '@rocket.chat/core-services';
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';
import { Match, check } from 'meteor/check';
@@ -79,8 +79,8 @@ const validateAttachmentsFields = (attachmentField: any) => {
}),
);
- if (typeof attachmentField.value !== 'undefined') {
- attachmentField.value = String(attachmentField.value);
+ if (!attachmentField.value || !attachmentField.title) {
+ throw new Error('Invalid attachment field, title and value is required');
}
};
@@ -225,11 +225,6 @@ export const sendMessage = async function (user: any, message: any, room: any, u
await validateMessage(message, room, user);
prepareMessageObject(message, room._id, user);
- if (message.t === 'otr') {
- void api.broadcast('otrMessage', { roomId: message.rid, message, user, room });
- return message;
- }
-
if (settings.get('Message_Read_Receipt_Enabled')) {
message.unread = true;
}
diff --git a/apps/meteor/app/lib/server/functions/setStatusText.ts b/apps/meteor/app/lib/server/functions/setStatusText.ts
index 7c81bae0112ed..8a5276d584ead 100644
--- a/apps/meteor/app/lib/server/functions/setStatusText.ts
+++ b/apps/meteor/app/lib/server/functions/setStatusText.ts
@@ -9,7 +9,19 @@ import { onceTransactionCommitedSuccessfully } from '../../../../server/database
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { RateLimiter } from '../lib';
-async function _setStatusText(userId: string, statusText: string, updater?: Updater, session?: ClientSession): Promise {
+async function _setStatusText(
+ userId: string,
+ statusText: string,
+ {
+ updater,
+ session,
+ emit = true,
+ }: {
+ updater?: Updater;
+ session?: ClientSession;
+ emit?: boolean;
+ } = {},
+): Promise {
if (!userId) {
return false;
}
@@ -35,13 +47,15 @@ async function _setStatusText(userId: string, statusText: string, updater?: Upda
await Users.updateStatusText(user._id, statusText, { session });
}
- const { _id, username, status, name, roles } = user;
- await onceTransactionCommitedSuccessfully(() => {
- void api.broadcast('presence.status', {
- user: { _id, username, status, statusText, name, roles },
- previousStatus: status,
- });
- }, session);
+ if (emit) {
+ const { _id, username, status, name, roles } = user;
+ await onceTransactionCommitedSuccessfully(() => {
+ void api.broadcast('presence.status', {
+ user: { _id, username, status, statusText, name, roles },
+ previousStatus: status,
+ });
+ }, session);
+ }
return true;
}
diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
index fa59df01ba589..8ebfe0c7449bc 100644
--- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
+++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
@@ -9,7 +9,7 @@ import { closeOmnichannelConversations } from './closeOmnichannelConversations';
import { shouldRemoveOrChangeOwner, getSubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner';
import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms';
import { relinquishRoomOwnerships } from './relinquishRoomOwnerships';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
import * as Mailer from '../../../mailer/server/api';
import { settings } from '../../../settings/server';
import {
diff --git a/apps/meteor/app/lib/server/functions/setUsername.ts b/apps/meteor/app/lib/server/functions/setUsername.ts
index 761549ed10287..ad0364d5617a0 100644
--- a/apps/meteor/app/lib/server/functions/setUsername.ts
+++ b/apps/meteor/app/lib/server/functions/setUsername.ts
@@ -15,8 +15,8 @@ import { joinDefaultChannels } from './joinDefaultChannels';
import { saveUserIdentity } from './saveUserIdentity';
import { setUserAvatar } from './setUserAvatar';
import { validateUsername } from './validateUsername';
-import { callbacks } from '../../../../lib/callbacks';
import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
+import { callbacks } from '../../../../server/lib/callbacks';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { settings } from '../../../settings/server';
import { notifyOnUserChange } from '../lib/notifyListener';
diff --git a/apps/meteor/app/lib/server/index.ts b/apps/meteor/app/lib/server/index.ts
index f56827baeb8ac..9fb7c341b83e4 100644
--- a/apps/meteor/app/lib/server/index.ts
+++ b/apps/meteor/app/lib/server/index.ts
@@ -16,7 +16,6 @@ import './methods/checkRegistrationSecretURL';
import './methods/cleanRoomHistory';
import './methods/createChannel';
import './methods/createPrivateGroup';
-import './methods/createToken';
import './methods/deleteUserOwnAccount';
import './methods/executeSlashCommandPreview';
import './startup/mentionUserNotInChannel';
@@ -27,8 +26,6 @@ import './methods/getSingleMessage';
import './methods/getMessages';
import './methods/getSlashCommandPreviews';
import './methods/getUsernameSuggestion';
-import './methods/getUserRoles';
-import './methods/insertOrUpdateUser';
import './methods/joinDefaultChannels';
import './methods/joinRoom';
import './methods/leaveRoom';
@@ -39,14 +36,11 @@ import './methods/saveSetting';
import './methods/saveSettings';
import './methods/sendMessage';
import './methods/sendSMTPTestEmail';
-import './methods/setAdminStatus';
import './methods/setEmail';
import './methods/setRealName';
-import './methods/setUsername';
import './methods/unarchiveRoom';
import './methods/unblockUser';
import './methods/updateMessage';
-import './methods/saveCustomFields';
import './methods/checkFederationConfiguration';
export * from './lib';
diff --git a/apps/meteor/app/lib/server/lib/afterSaveMessage.ts b/apps/meteor/app/lib/server/lib/afterSaveMessage.ts
index 910b59d76e8a4..b320c2d87e8d6 100644
--- a/apps/meteor/app/lib/server/lib/afterSaveMessage.ts
+++ b/apps/meteor/app/lib/server/lib/afterSaveMessage.ts
@@ -2,7 +2,7 @@ import type { IMessage, IUser, IRoom } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { Rooms } from '@rocket.chat/models';
-import { callbacks } from '../../../../lib/callbacks';
+import { callbacks } from '../../../../server/lib/callbacks';
export async function afterSaveMessage(message: IMessage, room: IRoom, user: IUser, roomUpdater?: Updater): Promise {
const updater = roomUpdater ?? Rooms.getUpdater();
diff --git a/apps/meteor/app/lib/server/lib/beforeAddUserToRoom.ts b/apps/meteor/app/lib/server/lib/beforeAddUserToRoom.ts
new file mode 100644
index 0000000000000..984dc34a22a12
--- /dev/null
+++ b/apps/meteor/app/lib/server/lib/beforeAddUserToRoom.ts
@@ -0,0 +1,6 @@
+import type { IRoom, IUser } from '@rocket.chat/core-typings';
+import { makeFunction } from '@rocket.chat/patch-injection';
+
+export const beforeAddUserToRoom = makeFunction(async (_users: IUser['username'][], _room: IRoom, _actor?: IUser) => {
+ // no op on CE
+});
diff --git a/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts b/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts
index ee95c9d2fa328..6a7fc267caaa4 100644
--- a/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts
+++ b/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts
@@ -24,13 +24,17 @@ const compareVersions = (version: string, message: string) => {
}
};
-export type DeprecationLoggerNextPlannedVersion = '7.0.0' | '8.0.0';
+export type DeprecationLoggerNextPlannedVersion = '9.0.0';
export const apiDeprecationLogger = ((logger) => {
return {
endpoint: (endpoint: string, version: DeprecationLoggerNextPlannedVersion, res: Response, info = '') => {
const message = `The endpoint "${endpoint}" is deprecated and will be removed on version ${version}${info ? ` (${info})` : ''}`;
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
+
compareVersions(version, message);
writeDeprecationHeader(res, 'endpoint-deprecation', message, version);
@@ -54,6 +58,10 @@ export const apiDeprecationLogger = ((logger) => {
}) ?? `The parameter "${parameter}" in the endpoint "${endpoint}" is deprecated and will be removed on version ${version}`;
compareVersions(version, message);
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
+
metrics.deprecations.inc({ type: 'parameter-deprecation', kind: 'endpoint', name: endpoint, params: parameter });
writeDeprecationHeader(res, 'parameter-deprecation', message, version);
@@ -76,6 +84,11 @@ export const apiDeprecationLogger = ((logger) => {
endpoint,
version,
}) ?? `The usage of the endpoint "${endpoint}" is deprecated and will be removed on version ${version}`;
+
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
+
compareVersions(version, message);
metrics.deprecations.inc({ type: 'invalid-usage', kind: 'endpoint', name: endpoint, params: parameter });
@@ -96,12 +109,18 @@ export const methodDeprecationLogger = ((logger) => {
) => {
const replacement = typeof info === 'string' ? info : `Use the ${info} endpoint instead`;
const message = `The method "${method}" is deprecated and will be removed on version ${version}${replacement ? ` (${replacement})` : ''}`;
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
compareVersions(version, message);
metrics.deprecations.inc({ type: 'deprecation', name: method, kind: 'method' });
logger.warn(message);
},
parameter: (method: string, parameter: string, version: DeprecationLoggerNextPlannedVersion) => {
const message = `The parameter "${parameter}" in the method "${method}" is deprecated and will be removed on version ${version}`;
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
metrics.deprecations.inc({ type: 'parameter-deprecation', name: method, params: parameter });
@@ -122,6 +141,11 @@ export const methodDeprecationLogger = ((logger) => {
method,
version,
}) ?? `The usage of the method "${method}" is deprecated and will be removed on version ${version}`;
+
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
+
compareVersions(version, message);
metrics.deprecations.inc({ type: 'invalid-usage', name: method, params: parameter, kind: 'method' });
@@ -130,6 +154,10 @@ export const methodDeprecationLogger = ((logger) => {
},
/** @deprecated */
warn: (message: string) => {
+ if (process.env.TEST_MODE === 'true') {
+ throw new Error(message);
+ }
+
compareVersions('0.0.0', message);
logger.warn(message);
},
diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts
index 2b5c10caa662e..210510b001dc6 100644
--- a/apps/meteor/app/lib/server/lib/notifyListener.ts
+++ b/apps/meteor/app/lib/server/lib/notifyListener.ts
@@ -7,7 +7,6 @@ import type {
IRole,
IPermission,
IIntegration,
- IPbxEvent,
LoginServiceConfiguration as LoginServiceConfigurationData,
ILivechatInquiryRecord,
ILivechatPriority,
@@ -24,12 +23,10 @@ import type {
ILivechatContact,
} from '@rocket.chat/core-typings';
import {
- dbWatchersDisabled,
Rooms,
LivechatRooms,
Permissions,
Settings,
- PbxEvents,
Roles,
Integrations,
LoginServiceConfiguration,
@@ -47,360 +44,327 @@ import { shouldHideSystemMessage } from '../../../../server/lib/systemMessage/hi
type ClientAction = 'inserted' | 'updated' | 'removed';
-function withDbWatcherCheck Promise>(fn: T): T {
- return dbWatchersDisabled ? fn : ((() => Promise.resolve()) as T);
-}
-
-export const notifyOnLivechatPriorityChanged = withDbWatcherCheck(
- async (data: Pick, clientAction: ClientAction = 'updated'): Promise => {
- const { _id, ...rest } = data;
- void api.broadcast('watch.priorities', { clientAction, id: _id, diff: { ...rest } });
- },
-);
-
-export const notifyOnRoomChanged = withDbWatcherCheck(
- async (data: T | T[], clientAction: ClientAction = 'updated'): Promise => {
- const items = Array.isArray(data) ? data : [data];
- for (const item of items) {
- void api.broadcast('watch.rooms', { clientAction, room: item });
- }
- },
-);
+export const notifyOnLivechatPriorityChanged = async (
+ data: Pick,
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const { _id, ...rest } = data;
+ void api.broadcast('watch.priorities', { clientAction, id: _id, diff: { ...rest } });
+};
-export const notifyOnRoomChangedById = withDbWatcherCheck(
- async (ids: T['_id'] | T['_id'][], clientAction: ClientAction = 'updated'): Promise => {
- const eligibleIds = Array.isArray(ids) ? ids : [ids];
- const items = Rooms.findByIds(eligibleIds);
- for await (const item of items) {
- void api.broadcast('watch.rooms', { clientAction, room: item });
- }
- },
-);
+export const notifyOnRoomChanged = async (
+ data: T | T[],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const items = Array.isArray(data) ? data : [data];
+ for (const item of items) {
+ void api.broadcast('watch.rooms', { clientAction, room: item });
+ }
+};
-export const notifyOnRoomChangedByUsernamesOrUids = withDbWatcherCheck(
- async (
- uids: T['u']['_id'][],
- usernames: T['u']['username'][],
- clientAction: ClientAction = 'updated',
- ): Promise => {
- const items = Rooms.findByUsernamesOrUids(uids, usernames);
- for await (const item of items) {
- void api.broadcast('watch.rooms', { clientAction, room: item });
- }
- },
-);
+export const notifyOnRoomChangedById = async (
+ ids: T['_id'] | T['_id'][],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const eligibleIds = Array.isArray(ids) ? ids : [ids];
+ const items = Rooms.findByIds(eligibleIds);
+ for await (const item of items) {
+ void api.broadcast('watch.rooms', { clientAction, room: item });
+ }
+};
-export const notifyOnRoomChangedByContactId = withDbWatcherCheck(
- async (contactId: T['_id'], clientAction: ClientAction = 'updated'): Promise => {
- const cursor = LivechatRooms.findOpenByContactId(contactId);
+export const notifyOnRoomChangedByUsernamesOrUids = async (
+ uids: T['u']['_id'][],
+ usernames: T['u']['username'][],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const items = Rooms.findByUsernamesOrUids(uids, usernames);
+ for await (const item of items) {
+ void api.broadcast('watch.rooms', { clientAction, room: item });
+ }
+};
- void cursor.forEach((room) => {
- void api.broadcast('watch.rooms', { clientAction, room });
- });
- },
-);
+export const notifyOnRoomChangedByContactId = async (
+ contactId: T['_id'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const cursor = LivechatRooms.findOpenByContactId(contactId);
-export const notifyOnRoomChangedByUserDM = withDbWatcherCheck(
- async (userId: T['u']['_id'], clientAction: ClientAction = 'updated'): Promise => {
- const items = Rooms.findDMsByUids([userId]);
- for await (const item of items) {
- void api.broadcast('watch.rooms', { clientAction, room: item });
- }
- },
-);
+ void cursor.forEach((room) => {
+ void api.broadcast('watch.rooms', { clientAction, room });
+ });
+};
-export const notifyOnPermissionChanged = withDbWatcherCheck(
- async (permission: IPermission, clientAction: ClientAction = 'updated'): Promise => {
- void api.broadcast('permission.changed', { clientAction, data: permission });
+export const notifyOnRoomChangedByUserDM = async (
+ userId: T['u']['_id'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const items = Rooms.findDMsByUids([userId]);
+ for await (const item of items) {
+ void api.broadcast('watch.rooms', { clientAction, room: item });
+ }
+};
- if (permission.level === 'settings' && permission.settingId) {
- const setting = await Settings.findOneNotHiddenById(permission.settingId);
- if (!setting) {
- return;
- }
- void notifyOnSettingChanged(setting, 'updated');
- }
- },
-);
+export const notifyOnPermissionChanged = async (permission: IPermission, clientAction: ClientAction = 'updated'): Promise => {
+ void api.broadcast('permission.changed', { clientAction, data: permission });
-export const notifyOnPermissionChangedById = withDbWatcherCheck(
- async (pid: IPermission['_id'], clientAction: ClientAction = 'updated'): Promise => {
- const permission = await Permissions.findOneById(pid);
- if (!permission) {
+ if (permission.level === 'settings' && permission.settingId) {
+ const setting = await Settings.findOneNotHiddenById(permission.settingId);
+ if (!setting) {
return;
}
+ void notifyOnSettingChanged(setting, 'updated');
+ }
+};
- return notifyOnPermissionChanged(permission, clientAction);
- },
-);
+export const notifyOnPermissionChangedById = async (pid: IPermission['_id'], clientAction: ClientAction = 'updated'): Promise => {
+ const permission = await Permissions.findOneById(pid);
+ if (!permission) {
+ return;
+ }
-export const notifyOnPbxEventChangedById = withDbWatcherCheck(
- async (id: T['_id'], clientAction: ClientAction = 'updated'): Promise => {
- const item = await PbxEvents.findOneById(id);
- if (!item) {
- return;
- }
+ return notifyOnPermissionChanged(permission, clientAction);
+};
- void api.broadcast('watch.pbxevents', { clientAction, id, data: item });
- },
-);
+export const notifyOnRoleChanged = async (role: T, clientAction: 'removed' | 'changed' = 'changed'): Promise => {
+ void api.broadcast('watch.roles', { clientAction, role });
+};
-export const notifyOnRoleChanged = withDbWatcherCheck(
- async (role: T, clientAction: 'removed' | 'changed' = 'changed'): Promise => {
- void api.broadcast('watch.roles', { clientAction, role });
- },
-);
+export const notifyOnRoleChangedById = async (
+ id: T['_id'],
+ clientAction: 'removed' | 'changed' = 'changed',
+): Promise => {
+ const role = await Roles.findOneById(id);
+ if (!role) {
+ return;
+ }
-export const notifyOnRoleChangedById = withDbWatcherCheck(
- async (id: T['_id'], clientAction: 'removed' | 'changed' = 'changed'): Promise => {
- const role = await Roles.findOneById(id);
- if (!role) {
- return;
- }
+ void notifyOnRoleChanged(role, clientAction);
+};
- void notifyOnRoleChanged(role, clientAction);
- },
-);
+export const notifyOnLoginServiceConfigurationChanged = async (
+ service: Partial & Pick,
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ void api.broadcast('watch.loginServiceConfiguration', {
+ clientAction,
+ id: service._id,
+ data: service,
+ });
+};
-export const notifyOnLoginServiceConfigurationChanged = withDbWatcherCheck(
- async (
- service: Partial & Pick,
- clientAction: ClientAction = 'updated',
- ): Promise => {
- void api.broadcast('watch.loginServiceConfiguration', {
- clientAction,
- id: service._id,
- data: service,
- });
- },
-);
+export const notifyOnLoginServiceConfigurationChangedByService = async (
+ service: T['service'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const item = await LoginServiceConfiguration.findOneByService>(service, {
+ projection: { secret: 0 },
+ });
+ if (!item) {
+ return;
+ }
-export const notifyOnLoginServiceConfigurationChangedByService = withDbWatcherCheck(
- async (service: T['service'], clientAction: ClientAction = 'updated'): Promise => {
- const item = await LoginServiceConfiguration.findOneByService>(service, {
- projection: { secret: 0 },
- });
- if (!item) {
- return;
- }
+ void notifyOnLoginServiceConfigurationChanged(item, clientAction);
+};
- void notifyOnLoginServiceConfigurationChanged(item, clientAction);
- },
-);
+export const notifyOnIntegrationChanged = async (
+ data: T,
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ void api.broadcast('watch.integrations', { clientAction, id: data._id, data });
+};
-export const notifyOnIntegrationChanged = withDbWatcherCheck(
- async (data: T, clientAction: ClientAction = 'updated'): Promise => {
- void api.broadcast('watch.integrations', { clientAction, id: data._id, data });
- },
-);
+export const notifyOnIntegrationChangedById = async (
+ id: T['_id'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const item = await Integrations.findOneById(id);
+ if (!item) {
+ return;
+ }
-export const notifyOnIntegrationChangedById = withDbWatcherCheck(
- async (id: T['_id'], clientAction: ClientAction = 'updated'): Promise => {
- const item = await Integrations.findOneById(id);
- if (!item) {
- return;
- }
+ void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
+};
- void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
- },
-);
+export const notifyOnIntegrationChangedByUserId = async (
+ id: T['userId'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const items = Integrations.findByUserId(id);
-export const notifyOnIntegrationChangedByUserId = withDbWatcherCheck(
- async (id: T['userId'], clientAction: ClientAction = 'updated'): Promise => {
- const items = Integrations.findByUserId(id);
+ for await (const item of items) {
+ void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
+ }
+};
- for await (const item of items) {
- void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
- }
- },
-);
+export const notifyOnIntegrationChangedByChannels = async (
+ channels: T['channel'],
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ const items = Integrations.findByChannels(channels);
-export const notifyOnIntegrationChangedByChannels = withDbWatcherCheck(
- async (channels: T['channel'], clientAction: ClientAction = 'updated'): Promise => {
- const items = Integrations.findByChannels(channels);
+ for await (const item of items) {
+ void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
+ }
+};
- for await (const item of items) {
- void api.broadcast('watch.integrations', { clientAction, id: item._id, data: item });
- }
- },
-);
+export const notifyOnEmailInboxChanged = async (
+ data: Pick | T, // TODO: improve typing
+ clientAction: ClientAction = 'updated',
+): Promise => {
+ void api.broadcast('watch.emailInbox', { clientAction, id: data._id, data });
+};
-export const notifyOnEmailInboxChanged = withDbWatcherCheck(
- async