diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel
index 8015df1aa0231..1eb57a109a10f 100644
--- a/.docker/Dockerfile.rhel
+++ b/.docker/Dockerfile.rhel
@@ -1,6 +1,6 @@
FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7
-ENV RC_VERSION 3.7.2
+ENV RC_VERSION 3.8.0
MAINTAINER buildmaster@rocket.chat
diff --git a/.eslintignore b/.eslintignore
index ebdb084375a0e..2a39e35ae2b25 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -18,3 +18,4 @@ public/pdf.worker.min.js
public/workers/**/*
imports/client/
!/.storybook/
+ee/server/services/dist/**
diff --git a/.eslintrc b/.eslintrc
index ff4471548fc60..d1d22fdb7fa5e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -27,7 +27,9 @@
"syntax"
],
"react-hooks/rules-of-hooks": "error",
- "react-hooks/exhaustive-deps": "warn"
+ "react-hooks/exhaustive-deps": ["warn", {
+ "additionalHooks": "(useComponentDidUpdate)"
+ }]
},
"settings": {
"import/resolver": {
diff --git a/.github/history.json b/.github/history.json
index cd0138c9cc0e2..db71fc40adc53 100644
--- a/.github/history.json
+++ b/.github/history.json
@@ -50290,6 +50290,1219 @@
}
]
},
+ "3.8.0-rc.0": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-alpha.4006",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19000",
+ "title": "[NEW][Enterprise] Micro services",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "sampaiodiego",
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19352",
+ "title": "[FIX][ENTERPRISE] Race condition on Omnichannel queues",
+ "userLogin": "renatobecker",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo",
+ "web-flow",
+ "renatobecker"
+ ]
+ },
+ {
+ "pr": "19368",
+ "title": "[NEW] Replace client-side event emitters",
+ "userLogin": "tiagoevanp",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tiagoevanp",
+ "gabriellsh",
+ "web-flow",
+ "tassoevan"
+ ]
+ },
+ {
+ "pr": "19380",
+ "title": "Regression: Pass `unset` parameter of updated `userData` notification",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19379",
+ "title": "[FIX] Omnichannel - typo error label at current chats page",
+ "userLogin": "rafaelblink",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rafaelblink"
+ ]
+ },
+ {
+ "pr": "17120",
+ "title": "[NEW] Whitelisting bad words",
+ "userLogin": "aryankoul",
+ "milestone": "3.8.0",
+ "contributors": [
+ "aryankoul",
+ "sampaiodiego",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19290",
+ "title": "[NEW][APPS] New Scheduler API",
+ "userLogin": "thassiov",
+ "milestone": "3.8.0",
+ "contributors": [
+ "thassiov",
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19088",
+ "title": "[IMPROVE][APPS] Apps list page on servers without internet connection",
+ "userLogin": "thassiov",
+ "milestone": "3.8.0",
+ "contributors": [
+ "thassiov",
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19364",
+ "title": "[FIX] 2FA required rendering blank page",
+ "userLogin": "ggazzo",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo",
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19089",
+ "title": "[IMPROVE] New sidebar layout",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "MartinSchoeler",
+ "ggazzo",
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19228",
+ "title": "[NEW] [Apps] Add new typing bridge method (Typing-Indicator)",
+ "userLogin": "lolimay",
+ "milestone": "3.8.0",
+ "contributors": [
+ "lolimay",
+ "d-gubert",
+ "web-flow",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19363",
+ "title": "[NEW] Add enterprise data to statistics",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "18687",
+ "title": "[NEW][Apps] Remove TS compiler",
+ "userLogin": "d-gubert",
+ "milestone": "3.8.0",
+ "contributors": [
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19240",
+ "title": "Update feature-request opening process on README",
+ "userLogin": "brij1999",
+ "contributors": [
+ "brij1999",
+ "web-flow",
+ "geekgonecrazy",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19358",
+ "title": "Regression: GenericTable.HeaderCell does not accept on click anymore",
+ "userLogin": "ggazzo",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo"
+ ]
+ },
+ {
+ "pr": "19359",
+ "title": "Update Fuselage Version",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "ggazzo",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "18237",
+ "title": "Rewrite: Reset Login Form",
+ "userLogin": "ggazzo",
+ "milestone": "3.5.0",
+ "contributors": [
+ "ggazzo",
+ "gabriellsh",
+ "MartinSchoeler",
+ "dougfabris"
+ ]
+ },
+ {
+ "pr": "19351",
+ "title": "[FIX] Thread List showing wrong items",
+ "userLogin": "ggazzo",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo",
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19361",
+ "title": "Bump Livechat widget",
+ "userLogin": "renatobecker",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo",
+ "web-flow",
+ "renatobecker"
+ ]
+ },
+ {
+ "pr": "18960",
+ "title": "[FIX] Non admin cannot add custom avatar to group",
+ "userLogin": "FelipeParreira",
+ "description": "Allow non-admins to change room avatar.",
+ "milestone": "3.8.0",
+ "contributors": [
+ "FelipeParreira",
+ "web-flow",
+ "pierre-lehnen-rc",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19009",
+ "title": "[FIX] IRC Bridge not working",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc",
+ "sampaiodiego",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19203",
+ "title": "[FIX] Invalid attachments on User Data downloads",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "18146",
+ "title": "[NEW] OAuth groups to channels mapping",
+ "userLogin": "arminfelder",
+ "milestone": "3.8.0",
+ "contributors": [
+ "arminfelder",
+ "web-flow",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19263",
+ "title": "[FIX] Anonymous users are counted on the server statistics and engagement dashboard",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19348",
+ "title": "[FIX] Admin not working on IE11",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "ggazzo",
+ "web-flow",
+ "dougfabris"
+ ]
+ },
+ {
+ "pr": "19266",
+ "title": "[FIX] Server Errors on new Client Connections",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc",
+ "sampaiodiego",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19349",
+ "title": "[FIX] Use etag on user info",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19298",
+ "title": "Bump object-path from 0.11.4 to 0.11.5",
+ "userLogin": "dependabot[bot]",
+ "contributors": [
+ "dependabot[bot]",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19341",
+ "title": "[NEW] Admin option to reset users’ 2FA",
+ "userLogin": "rodrigok",
+ "description": "Admins can reset the 2FA of other users if they have the permission `edit-other-user-totp` and the `Accounts > Two Factor Authentication > Enforce password fallback` setting is enabled.",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19339",
+ "title": "[FIX] Broken user info when a user don't have an email address",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19320",
+ "title": "[NEW] Apps prometheus metrics",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19337",
+ "title": "[FIX] LDAP Sync Error Dup Key",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc"
+ ]
+ },
+ {
+ "pr": "19342",
+ "title": "Remove WeDeploy from README",
+ "userLogin": "lucas-andre",
+ "contributors": [
+ "lucas-andre",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19264",
+ "title": "[FIX] \"Export Messages\" only works for global roles ",
+ "userLogin": "pierre-lehnen-rc",
+ "milestone": "3.8.0",
+ "contributors": [
+ "pierre-lehnen-rc"
+ ]
+ },
+ {
+ "pr": "19297",
+ "title": "Use GitHub Container Registry",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "sampaiodiego",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19275",
+ "title": "[NEW] Audits search by User",
+ "userLogin": "dougfabris",
+ "contributors": [
+ "dougfabris",
+ "MartinSchoeler",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "18649",
+ "title": "[FIX] SAML login undefined error message",
+ "userLogin": "galshiff",
+ "description": "Fixed the SAML login undefined error message",
+ "contributors": [
+ "galshiff"
+ ]
+ },
+ {
+ "pr": "19321",
+ "title": "[IMPROVE] React Avatar Provider",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "ggazzo"
+ ]
+ },
+ {
+ "pr": "19316",
+ "title": "[FIX] Message actions on top of text",
+ "userLogin": "gabriellsh",
+ "contributors": [
+ "gabriellsh",
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19322",
+ "title": "Regression: User card closing",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan"
+ ]
+ },
+ {
+ "pr": "19303",
+ "title": "Non-idiomatic React code",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19310",
+ "title": "Regression: Reassessment of client helpers 'XYZ key should not contain .'",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "ggazzo"
+ ]
+ },
+ {
+ "pr": "18239",
+ "title": "[FIX] Setting values being showed up in logs when using log level for debug",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok",
+ "web-flow",
+ "pierre-lehnen-rc"
+ ]
+ },
+ {
+ "pr": "19061",
+ "title": "[FIX] Push notifications with lower priority for Android devices",
+ "userLogin": "ceefour",
+ "description": "fix(push): Set push notification priority to 'high' for FCM",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ceefour"
+ ]
+ },
+ {
+ "pr": "19276",
+ "title": "Remove legacy modal template",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan"
+ ]
+ },
+ {
+ "pr": "19249",
+ "title": "Reassessment of client helpers",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19255",
+ "title": "Remove legacy slider",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan"
+ ]
+ },
+ {
+ "pr": "19247",
+ "title": "[FIX] Don't send room name on notification",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19204",
+ "title": "[FIX] Error preventing from removing users without a role",
+ "userLogin": "RohitKumar-200",
+ "milestone": "3.8.0",
+ "contributors": [
+ "RohitKumar-200",
+ "ggazzo",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19192",
+ "title": "Fix Indie Hosters install image",
+ "userLogin": "aradhya-gupta",
+ "contributors": [
+ "aradhya-gupta"
+ ]
+ },
+ {
+ "pr": "19200",
+ "title": "[FIX] UserCard Roles Description",
+ "userLogin": "ggazzo",
+ "contributors": [
+ "ggazzo",
+ "web-flow",
+ null
+ ]
+ },
+ {
+ "pr": "18272",
+ "title": "[NEW] Reaction view",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler",
+ "tassoevan",
+ "web-flow",
+ "ggazzo",
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "18929",
+ "title": "[FIX] Wrong avatar urls when using providers",
+ "userLogin": "MartinSchoeler",
+ "milestone": "3.7.0",
+ "contributors": [
+ "MartinSchoeler",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19132",
+ "title": "[IMPROVE] Display channel avatar on the Header",
+ "userLogin": "bhavayAnand9",
+ "contributors": [
+ "ba-9",
+ "bhavayAnand9",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19078",
+ "title": "Update comment of \"issue-close-app\"",
+ "userLogin": "frdmn",
+ "contributors": [
+ "frdmn",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19169",
+ "title": "[FIX] Remove requirements to tag description and department",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19201",
+ "title": "[FIX] Omnichannel auditing required field",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19199",
+ "title": "[FIX] Agent status offline and wrong i18n key",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19202",
+ "title": "Refactor some React Pages and Components ",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan"
+ ]
+ },
+ {
+ "pr": "17154",
+ "title": "[NEW] feat(CAS): Adding option to enable/disable user creation from CAS auth",
+ "userLogin": "jgribonvald",
+ "milestone": "3.8.0",
+ "contributors": [
+ "jgribonvald",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19179",
+ "title": "[FIX] Adding missing custom fields translation in my account's profile",
+ "userLogin": "dougfabris",
+ "milestone": "3.7.1",
+ "contributors": [
+ "dougfabris"
+ ]
+ },
+ {
+ "pr": "19181",
+ "title": "[FIX] Performance issues when using new Oplog implementation",
+ "userLogin": "rodrigok",
+ "description": "A missing configuration was not limiting the new oplog tailing to pool the database frequently even when no data was available, leading to both node and mongodb process been consuming high CPU even with low usage. This case was happening for installations using `mmapv1` database engine or when no admin access was granted to the database user, both preventing the usage of the new [Change Streams](https://docs.mongodb.com/manual/changeStreams/) implementation and fallbacking to our custom oplog implementation in replacement to the Meteor's one what was able to be disabled and use the native implementation via the environmental variable `USE_NATIVE_OPLOG=true`.",
+ "milestone": "3.7.1",
+ "contributors": [
+ "rodrigok",
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "18920",
+ "title": "Refactor: Omnichannel departments",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler",
+ "ggazzo",
+ "gabriellsh",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19171",
+ "title": "[FIX] Livechat Appearance label and reset button",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19172",
+ "title": "[FIX] Thread view in a channel user haven't joined (#19008) ",
+ "userLogin": "dougfabris",
+ "contributors": [
+ "dougfabris"
+ ]
+ },
+ {
+ "pr": "19170",
+ "title": "[FIX] Error when editing priority and required description",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19168",
+ "title": "[FIX] Selecting the same department for multiple units",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19114",
+ "title": "[FIX] Integrations history page not reacting to changes.",
+ "userLogin": "gabriellsh",
+ "contributors": [
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19101",
+ "title": "[FIX] Admin Sidebar overflowing",
+ "userLogin": "gabriellsh",
+ "milestone": "3.7.1",
+ "contributors": [
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19133",
+ "title": "[FIX] VisitorAutoComplete component",
+ "userLogin": "renatobecker",
+ "milestone": "3.7.1",
+ "contributors": [
+ "renatobecker"
+ ]
+ },
+ {
+ "pr": "19134",
+ "title": "[FIX] Omnichannel: triggers page not rendering.",
+ "userLogin": "gabriellsh",
+ "milestone": "3.7.1",
+ "contributors": [
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19166",
+ "title": "[FIX] Missing \"Bio\" in user's profile view (#18821)",
+ "userLogin": "dougfabris",
+ "milestone": "3.7.1",
+ "contributors": [
+ "dougfabris"
+ ]
+ },
+ {
+ "pr": "19060",
+ "title": "Merge master into develop & Set version to 3.8.0-develop",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "sampaiodiego",
+ "web-flow"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.1": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-alpha.4006",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19402",
+ "title": "Regression: Thread not showing for unloaded message",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19401",
+ "title": "Regression: Room item menu display delay",
+ "userLogin": "gabriellsh",
+ "milestone": "3.8.0",
+ "contributors": [
+ "gabriellsh",
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19390",
+ "title": "Regression: Thread component not updating its message list",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.2": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-alpha.4006",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19409",
+ "title": "Regression: Agent Status leading to broken page",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19411",
+ "title": "Regression: Rocket.Chat Apps updates always fail",
+ "userLogin": "d-gubert",
+ "milestone": "3.8.0",
+ "contributors": [
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19412",
+ "title": "Regression: Fix visitor field missing on subscription payload",
+ "userLogin": "renatobecker",
+ "milestone": "3.8.0",
+ "contributors": [
+ "renatobecker"
+ ]
+ },
+ {
+ "pr": "19407",
+ "title": "Regression: Fix stream-room-data payload",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.3": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-alpha.4006",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19427",
+ "title": "Micro Services: Create internal services and allowed services list",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego",
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19435",
+ "title": "Micro Services: Prevent duplicated events",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19382",
+ "title": "Regression: Sidebar message preview escaping html",
+ "userLogin": "gabriellsh",
+ "contributors": [
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19419",
+ "title": "Regression: unable to mark room as read",
+ "userLogin": "gabriellsh",
+ "contributors": [
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19418",
+ "title": "Build micro services Docker images with correct tags",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.4": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-beta.4014",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19385",
+ "title": "Update Apps-Engine version",
+ "userLogin": "d-gubert",
+ "milestone": "3.8.0",
+ "contributors": [
+ "d-gubert",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19423",
+ "title": "Micro Services: Fix logout issue",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19449",
+ "title": "Regression: Sidebar reactivity when read last messages",
+ "userLogin": "tiagoevanp",
+ "contributors": [
+ "tiagoevanp"
+ ]
+ },
+ {
+ "pr": "19443",
+ "title": "Regression: Attachment without title or description show \"sent attachment\" in view mode extended ",
+ "userLogin": "tiagoevanp",
+ "contributors": [
+ "tiagoevanp",
+ "web-flow",
+ "gabriellsh"
+ ]
+ },
+ {
+ "pr": "19448",
+ "title": "Micro Services: Add metrics capability to Services",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.5": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-beta.4014",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19413",
+ "title": "Regression: Thread list misbehaving",
+ "userLogin": "tassoevan",
+ "milestone": "3.8.0",
+ "contributors": [
+ "tassoevan",
+ "tiagoevanp",
+ "web-flow"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.6": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-beta.4014",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19460",
+ "title": "Regression: `Leave Room` modal not closing",
+ "userLogin": "gabriellsh",
+ "contributors": [
+ "gabriellsh",
+ "tassoevan",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19464",
+ "title": "Unify ephemeral message events",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19468",
+ "title": "Regression: Fix livechat permission validations",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.7": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-beta.4014",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19477",
+ "title": "Regression: Fix setting value not being sent over websocket",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19478",
+ "title": "Bump Livechat widget ",
+ "userLogin": "renatobecker",
+ "milestone": "3.8.0",
+ "contributors": [
+ "renatobecker"
+ ]
+ },
+ {
+ "pr": "19440",
+ "title": "[NEW] Branding updated with new logos",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.8": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0-beta.4086",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19416",
+ "title": "Regression: Allow apps to schedule jobs along with processor register",
+ "userLogin": "thassiov",
+ "milestone": "3.8.0",
+ "contributors": [
+ "thassiov",
+ "d-gubert",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19484",
+ "title": "Micro Services: Do not wait forever for a service. Fail after 10s or 10 minutes if whitelisted",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19472",
+ "title": "[FIX] OAuth create via environment variable",
+ "userLogin": "geekgonecrazy",
+ "milestone": "3.8.0",
+ "contributors": [
+ "geekgonecrazy",
+ "web-flow"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.9": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19499",
+ "title": "Update Apps-Engine to latest release",
+ "userLogin": "d-gubert",
+ "contributors": [
+ "d-gubert"
+ ]
+ },
+ {
+ "pr": "19494",
+ "title": "Remove unecessary return at the send code api",
+ "userLogin": "rodrigok",
+ "milestone": "3.8.0",
+ "contributors": [
+ "rodrigok"
+ ]
+ },
+ {
+ "pr": "19486",
+ "title": "Regression: Fix Thread List order",
+ "userLogin": "MartinSchoeler",
+ "contributors": [
+ "MartinSchoeler",
+ "ggazzo",
+ "web-flow"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.10": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19508",
+ "title": "Regression: Fix React warnings",
+ "userLogin": "ggazzo",
+ "milestone": "3.8.0",
+ "contributors": [
+ "ggazzo",
+ "dougfabris",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19474",
+ "title": "Regression: Fix presence status",
+ "userLogin": "tassoevan",
+ "contributors": [
+ "tassoevan",
+ "web-flow",
+ "ggazzo"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.11": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19498",
+ "title": "Regression: Fix broadcast events when running as monolith",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19501",
+ "title": "[FIX] Cloud Register Allowing Empty Tokens",
+ "userLogin": "graywolf336",
+ "milestone": "3.8.0",
+ "contributors": [
+ "graywolf336",
+ "web-flow"
+ ]
+ },
+ {
+ "pr": "19513",
+ "title": "Regression: Fix ephemeral message stream",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0-rc.12": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19527",
+ "title": "Regression: Fix presence request logic",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ },
+ {
+ "pr": "19519",
+ "title": "[FIX] Custom Emojis PNGs on IE11",
+ "userLogin": "MartinSchoeler",
+ "milestone": "3.7.2",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ },
+ {
+ "pr": "19524",
+ "title": "[FIX] Channel creation not working on IE",
+ "userLogin": "MartinSchoeler",
+ "milestone": "3.7.2",
+ "contributors": [
+ "MartinSchoeler"
+ ]
+ }
+ ]
+ },
"3.7.2": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
@@ -50300,6 +51513,16 @@
"4.0"
],
"pull_requests": [
+ {
+ "pr": "19529",
+ "title": "Release 3.7.2",
+ "userLogin": "sampaiodiego",
+ "contributors": [
+ "MartinSchoeler",
+ "sampaiodiego",
+ "ggazzo"
+ ]
+ },
{
"pr": "19525",
"title": "[FIX] Update Polyfills and fix directory in IE",
@@ -50339,6 +51562,49 @@
]
}
]
+ },
+ "3.8.0-rc.13": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": []
+ },
+ "3.8.0-rc.14": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": [
+ {
+ "pr": "19532",
+ "title": "Regression: Prevent network broker from starting when not needed",
+ "userLogin": "sampaiodiego",
+ "milestone": "3.8.0",
+ "contributors": [
+ "sampaiodiego"
+ ]
+ }
+ ]
+ },
+ "3.8.0": {
+ "node_version": "12.18.4",
+ "npm_version": "6.14.8",
+ "apps_engine_version": "1.19.0",
+ "mongo_versions": [
+ "3.4",
+ "3.6",
+ "4.0"
+ ],
+ "pull_requests": []
}
}
}
\ No newline at end of file
diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml
index 9c71ecbc5a89f..2a0b464239f00 100644
--- a/.github/issue-close-app.yml
+++ b/.github/issue-close-app.yml
@@ -12,6 +12,7 @@ comment: |
* Expected behavior
* Actual behavior
* Server Setup Information
+ * Version of Rocket.Chat Server
issueConfigs:
# There can be several configs for different kind of issues.
- content:
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 382c86009fbe1..272c40ac57d51 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -265,6 +265,13 @@ jobs:
steps:
- uses: actions/checkout@v2
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ secrets.CR_USER }}
+ password: ${{ secrets.CR_PAT }}
+
- name: Free disk space
run: |
sudo swapoff -a
@@ -346,23 +353,15 @@ jobs:
meteor build --server-only --directory /tmp/build-pr
- name: Build Docker image for PRs
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- VERSION: pr-${{ github.event.number }}
run: |
cd /tmp/build-pr
- docker login docker.pkg.github.com -u "${GITHUB_ACTOR}" -p "${GITHUB_TOKEN}"
-
- cp $GITHUB_WORKSPACE/.docker/Dockerfile .
-
- export LOWERCASE_REPOSITORY=$(echo "$GITHUB_REPOSITORY" | tr "[:upper:]" "[:lower:]")
-
- export IMAGE_NAME="docker.pkg.github.com/${LOWERCASE_REPOSITORY}/rocket.chat:${VERSION}"
+ LOWERCASE_REPOSITORY=$(echo "${{ github.repository_owner }}" | tr "[:upper:]" "[:lower:]")
+ IMAGE_NAME="ghcr.io/${LOWERCASE_REPOSITORY}/rocket.chat:pr-${{ github.event.number }}"
echo "Build official Docker image ${IMAGE_NAME}"
- docker build -t $IMAGE_NAME .
+ docker build -f $GITHUB_WORKSPACE/.docker/Dockerfile -t $IMAGE_NAME .
docker push $IMAGE_NAME
deploy:
@@ -426,6 +425,12 @@ jobs:
steps:
- uses: actions/checkout@v2
+ - name: Login to DockerHub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USER }}
+ password: ${{ secrets.DOCKER_PASS }}
+
- name: Restore build
uses: actions/download-artifact@v1
with:
@@ -433,17 +438,14 @@ jobs:
path: /tmp/build
- name: Unpack build
- env:
- DOCKER_USER: ${{ secrets.DOCKER_USER }}
- DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
run: |
cd /tmp/build
tar xzf Rocket.Chat.tar.gz
rm Rocket.Chat.tar.gz
- export DOCKER_PATH="${GITHUB_WORKSPACE}/.docker"
+ DOCKER_PATH="${GITHUB_WORKSPACE}/.docker"
if [[ '${{ matrix.release }}' = 'preview' ]]; then
- export DOCKER_PATH="${DOCKER_PATH}-mongo"
+ DOCKER_PATH="${DOCKER_PATH}-mongo"
fi;
echo "Build ${{ matrix.release }} Docker image"
@@ -452,25 +454,23 @@ jobs:
cp ${DOCKER_PATH}/entrypoint.sh .
fi;
- docker login -u $DOCKER_USER -p $DOCKER_PASS
-
- name: Build Docker image for tag
if: github.event_name == 'release'
run: |
cd /tmp/build
- export CIRCLE_TAG="${GITHUB_REF#*tags/}"
+ CIRCLE_TAG="${GITHUB_REF#*tags/}"
if [[ '${{ matrix.release }}' = 'preview' ]]; then
- export IMAGE="${IMAGE}.preview"
+ IMAGE="${IMAGE}.preview"
fi;
docker build -t ${IMAGE}:$CIRCLE_TAG .
docker push ${IMAGE}:$CIRCLE_TAG
if echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then
- export RELEASE="latest"
+ RELEASE="latest"
elif echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then
- export RELEASE="release-candidate"
+ RELEASE="release-candidate"
fi
docker tag ${IMAGE}:$CIRCLE_TAG ${IMAGE}:${RELEASE}
@@ -482,8 +482,49 @@ jobs:
cd /tmp/build
if [[ '${{ matrix.release }}' = 'preview' ]]; then
- export IMAGE="${IMAGE}.preview"
+ IMAGE="${IMAGE}.preview"
fi;
docker build -t ${IMAGE}:develop .
docker push ${IMAGE}:develop
+
+ services-image-build:
+ runs-on: ubuntu-latest
+ needs: deploy
+
+ strategy:
+ matrix:
+ service: ["account", "authorization", "ddp-streamer", "presence", "stream-hub"]
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Use Node.js 12.18.4
+ uses: actions/setup-node@v1
+ with:
+ node-version: "12.18.4"
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USER }}
+ password: ${{ secrets.DOCKER_PASS }}
+
+ - name: Build Docker images
+ run: |
+ # defines image tag
+ if [[ $GITHUB_REF == refs/tags/* ]]; then IMAGE_TAG="${GITHUB_REF#refs/tags/}"; else IMAGE_TAG="${GITHUB_REF#refs/heads/}"; fi
+
+ # first install repo dependencies
+ npm i
+
+ # then micro services dependencies
+ cd ./ee/server/services
+ npm i
+ npm run build
+
+ echo "Building Docker image for service: ${{ matrix.service }}:${IMAGE_TAG}"
+
+ docker build --build-arg SERVICE=${{ matrix.service }} -t rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} .
+
+ docker push rocketchat/${{ matrix.service }}-service
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 607b9c8b9953c..5bb8ef5081fcb 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -5,11 +5,10 @@ on:
- cron: "0 */6 * * *"
jobs:
- stale:
+ no-response:
runs-on: ubuntu-latest
-
steps:
- - uses: actions/stale@v3
+ - uses: actions/stale@v3.0.8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 10
@@ -24,15 +23,18 @@ jobs:
stale-issue-label: 'stat: no response'
operations-per-run: 40
- - uses: actions/stale@v3
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- days-before-stale: 60
- days-before-close: 7
- exempt-issue-labels: 'Epic,Feature: Planned,sla,sponsored,stat: waiting PR merge,Triaged,subj: security'
- stale-issue-message: >-
- This issue has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs. Thank you
- for your contributions.
- stale-issue-label: 'stat: stale'
- operations-per-run: 40
+# stale:
+# runs-on: ubuntu-latest
+# steps:
+# - uses: actions/stale@v3.0.8
+# with:
+# repo-token: ${{ secrets.GITHUB_TOKEN }}
+# days-before-stale: 60
+# days-before-close: 7
+# exempt-issue-labels: 'Epic,Feature: Planned,sla,sponsored,stat: waiting PR merge,Triaged,subj: security'
+# stale-issue-message: >-
+# This issue has been automatically marked as stale because it has not had
+# recent activity. It will be closed if no further activity occurs. Thank you
+# for your contributions.
+# stale-issue-label: 'stat: stale'
+# operations-per-run: 40
diff --git a/.meteorignore b/.meteorignore
new file mode 100644
index 0000000000000..35dd55fee165e
--- /dev/null
+++ b/.meteorignore
@@ -0,0 +1 @@
+ee/server/services
diff --git a/.scripts/start.js b/.scripts/start.js
index 1159290bbbb9a..8caf131893a6b 100644
--- a/.scripts/start.js
+++ b/.scripts/start.js
@@ -19,13 +19,17 @@ const isPortTaken = (port) => new Promise((resolve, reject) => {
.listen(port);
});
-const waitPortRelease = (port) => new Promise((resolve, reject) => {
+const waitPortRelease = (port, count = 0) => new Promise((resolve, reject) => {
isPortTaken(port).then((taken) => {
if (!taken) {
return resolve();
}
+ if (count > 60) {
+ return reject();
+ }
+ console.log('Port', port, 'not release, waiting 1s...');
setTimeout(() => {
- waitPortRelease(port).then(resolve).catch(reject);
+ waitPortRelease(port, ++count).then(resolve).catch(reject);
}, 1000);
});
});
@@ -77,7 +81,10 @@ function startProcess(opts, callback) {
processes.splice(processes.indexOf(proc), 1);
- processes.forEach((p) => p.kill());
+ processes.forEach((p) => {
+ console.log('Killing process', p.pid);
+ p.kill();
+ });
if (processes.length === 0) {
waitPortRelease(appOptions.env.PORT).then(() => {
diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat
index d8f959ba0277c..baeb445d346bb 100755
--- a/.snapcraft/resources/prepareRocketChat
+++ b/.snapcraft/resources/prepareRocketChat
@@ -1,6 +1,6 @@
#!/bin/bash
-curl -SLf "https://releases.rocket.chat/3.7.2/download/" -o rocket.chat.tgz
+curl -SLf "https://releases.rocket.chat/3.8.0/download/" -o rocket.chat.tgz
tar xf rocket.chat.tgz --strip 1
diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml
index eb4add4efb069..d3e9e48820f86 100644
--- a/.snapcraft/snap/snapcraft.yaml
+++ b/.snapcraft/snap/snapcraft.yaml
@@ -7,7 +7,7 @@
# 5. `snapcraft snap`
name: rocketchat-server
-version: 3.7.2
+version: 3.8.0
summary: Rocket.Chat server
description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/
confinement: strict
diff --git a/.storybook/mocks/meteor.js b/.storybook/mocks/meteor.js
index a690a4db6842d..d224131c5e112 100644
--- a/.storybook/mocks/meteor.js
+++ b/.storybook/mocks/meteor.js
@@ -1,4 +1,7 @@
export const Meteor = {
+ Device: {
+ isDesktop: () => false,
+ },
isClient: true,
isServer: false,
_localStorage: window.localStorage,
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
index e2e7d66c7ad80..65e19305d637d 100644
--- a/.storybook/webpack.config.js
+++ b/.storybook/webpack.config.js
@@ -53,7 +53,7 @@ module.exports = async ({ config }) => {
require.resolve('./mocks/meteor.js'),
),
new webpack.NormalModuleReplacementPlugin(
- /\/server(\/index.js)$/,
+ /(app)\/*.*\/(server)\/*/,
require.resolve('./mocks/empty.js'),
),
);
diff --git a/HISTORY.md b/HISTORY.md
index c4d9d90894f31..77d7f0f0a6623 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,6 +1,300 @@
+# 3.8.0
+`2020-11-13 · 14 🎉 · 4 🚀 · 40 🐛 · 54 🔍 · 30 👩💻👨💻`
+
+### Engine versions
+- Node: `12.18.4`
+- NPM: `6.14.8`
+- MongoDB: `3.4, 3.6, 4.0`
+- Apps-Engine: `1.19.0`
+
+### 🎉 New features
+
+
+- **Apps:** Add new typing bridge method (Typing-Indicator) ([#19228](https://github.com/RocketChat/Rocket.Chat/pull/19228))
+
+- **APPS:** New Scheduler API ([#19290](https://github.com/RocketChat/Rocket.Chat/pull/19290))
+
+- **Apps:** Remove TS compiler ([#18687](https://github.com/RocketChat/Rocket.Chat/pull/18687))
+
+- **Enterprise:** Micro services ([#19000](https://github.com/RocketChat/Rocket.Chat/pull/19000))
+
+- Add enterprise data to statistics ([#19363](https://github.com/RocketChat/Rocket.Chat/pull/19363))
+
+- Admin option to reset users’ 2FA ([#19341](https://github.com/RocketChat/Rocket.Chat/pull/19341))
+
+ Admins can reset the 2FA of other users if they have the permission `edit-other-user-totp` and the `Accounts > Two Factor Authentication > Enforce password fallback` setting is enabled.
+
+- Apps prometheus metrics ([#19320](https://github.com/RocketChat/Rocket.Chat/pull/19320))
+
+- Audits search by User ([#19275](https://github.com/RocketChat/Rocket.Chat/pull/19275))
+
+- Branding updated with new logos ([#19440](https://github.com/RocketChat/Rocket.Chat/pull/19440))
+
+- feat(CAS): Adding option to enable/disable user creation from CAS auth ([#17154](https://github.com/RocketChat/Rocket.Chat/pull/17154) by [@jgribonvald](https://github.com/jgribonvald))
+
+- OAuth groups to channels mapping ([#18146](https://github.com/RocketChat/Rocket.Chat/pull/18146) by [@arminfelder](https://github.com/arminfelder))
+
+- Reaction view ([#18272](https://github.com/RocketChat/Rocket.Chat/pull/18272))
+
+- Replace client-side event emitters ([#19368](https://github.com/RocketChat/Rocket.Chat/pull/19368))
+
+- Whitelisting bad words ([#17120](https://github.com/RocketChat/Rocket.Chat/pull/17120) by [@aryankoul](https://github.com/aryankoul))
+
+### 🚀 Improvements
+
+
+- **APPS:** Apps list page on servers without internet connection ([#19088](https://github.com/RocketChat/Rocket.Chat/pull/19088))
+
+- Display channel avatar on the Header ([#19132](https://github.com/RocketChat/Rocket.Chat/pull/19132) by [@ba-9](https://github.com/ba-9) & [@bhavayAnand9](https://github.com/bhavayAnand9))
+
+- New sidebar layout ([#19089](https://github.com/RocketChat/Rocket.Chat/pull/19089))
+
+- React Avatar Provider ([#19321](https://github.com/RocketChat/Rocket.Chat/pull/19321))
+
+### 🐛 Bug fixes
+
+
+- "Export Messages" only works for global roles ([#19264](https://github.com/RocketChat/Rocket.Chat/pull/19264))
+
+- **ENTERPRISE:** Race condition on Omnichannel queues ([#19352](https://github.com/RocketChat/Rocket.Chat/pull/19352))
+
+- 2FA required rendering blank page ([#19364](https://github.com/RocketChat/Rocket.Chat/pull/19364))
+
+- Adding missing custom fields translation in my account's profile ([#19179](https://github.com/RocketChat/Rocket.Chat/pull/19179))
+
+- Admin not working on IE11 ([#19348](https://github.com/RocketChat/Rocket.Chat/pull/19348))
+
+- Admin Sidebar overflowing ([#19101](https://github.com/RocketChat/Rocket.Chat/pull/19101))
+
+- Agent status offline and wrong i18n key ([#19199](https://github.com/RocketChat/Rocket.Chat/pull/19199))
+
+- Anonymous users are counted on the server statistics and engagement dashboard ([#19263](https://github.com/RocketChat/Rocket.Chat/pull/19263))
+
+- Broken user info when a user don't have an email address ([#19339](https://github.com/RocketChat/Rocket.Chat/pull/19339))
+
+- Channel creation not working on IE ([#19524](https://github.com/RocketChat/Rocket.Chat/pull/19524))
+
+- Cloud Register Allowing Empty Tokens ([#19501](https://github.com/RocketChat/Rocket.Chat/pull/19501))
+
+- Custom Emojis PNGs on IE11 ([#19519](https://github.com/RocketChat/Rocket.Chat/pull/19519))
+
+- Don't send room name on notification ([#19247](https://github.com/RocketChat/Rocket.Chat/pull/19247))
+
+- Error preventing from removing users without a role ([#19204](https://github.com/RocketChat/Rocket.Chat/pull/19204) by [@RohitKumar-200](https://github.com/RohitKumar-200))
+
+- Error when editing priority and required description ([#19170](https://github.com/RocketChat/Rocket.Chat/pull/19170))
+
+- Integrations history page not reacting to changes. ([#19114](https://github.com/RocketChat/Rocket.Chat/pull/19114))
+
+- Invalid attachments on User Data downloads ([#19203](https://github.com/RocketChat/Rocket.Chat/pull/19203))
+
+- IRC Bridge not working ([#19009](https://github.com/RocketChat/Rocket.Chat/pull/19009))
+
+- LDAP Sync Error Dup Key ([#19337](https://github.com/RocketChat/Rocket.Chat/pull/19337))
+
+- Livechat Appearance label and reset button ([#19171](https://github.com/RocketChat/Rocket.Chat/pull/19171))
+
+- Message actions on top of text ([#19316](https://github.com/RocketChat/Rocket.Chat/pull/19316))
+
+- Missing "Bio" in user's profile view (#18821) ([#19166](https://github.com/RocketChat/Rocket.Chat/pull/19166))
+
+- Non admin cannot add custom avatar to group ([#18960](https://github.com/RocketChat/Rocket.Chat/pull/18960) by [@FelipeParreira](https://github.com/FelipeParreira))
+
+ Allow non-admins to change room avatar.
+
+- OAuth create via environment variable ([#19472](https://github.com/RocketChat/Rocket.Chat/pull/19472))
+
+- Omnichannel - typo error label at current chats page ([#19379](https://github.com/RocketChat/Rocket.Chat/pull/19379))
+
+- Omnichannel auditing required field ([#19201](https://github.com/RocketChat/Rocket.Chat/pull/19201))
+
+- Omnichannel: triggers page not rendering. ([#19134](https://github.com/RocketChat/Rocket.Chat/pull/19134))
+
+- Performance issues when using new Oplog implementation ([#19181](https://github.com/RocketChat/Rocket.Chat/pull/19181))
+
+ A missing configuration was not limiting the new oplog tailing to pool the database frequently even when no data was available, leading to both node and mongodb process been consuming high CPU even with low usage. This case was happening for installations using `mmapv1` database engine or when no admin access was granted to the database user, both preventing the usage of the new [Change Streams](https://docs.mongodb.com/manual/changeStreams/) implementation and fallbacking to our custom oplog implementation in replacement to the Meteor's one what was able to be disabled and use the native implementation via the environmental variable `USE_NATIVE_OPLOG=true`.
+
+- Push notifications with lower priority for Android devices ([#19061](https://github.com/RocketChat/Rocket.Chat/pull/19061) by [@ceefour](https://github.com/ceefour))
+
+ fix(push): Set push notification priority to 'high' for FCM
+
+- Remove requirements to tag description and department ([#19169](https://github.com/RocketChat/Rocket.Chat/pull/19169))
+
+- SAML login undefined error message ([#18649](https://github.com/RocketChat/Rocket.Chat/pull/18649) by [@galshiff](https://github.com/galshiff))
+
+ Fixed the SAML login undefined error message
+
+- Selecting the same department for multiple units ([#19168](https://github.com/RocketChat/Rocket.Chat/pull/19168))
+
+- Server Errors on new Client Connections ([#19266](https://github.com/RocketChat/Rocket.Chat/pull/19266))
+
+- Setting values being showed up in logs when using log level for debug ([#18239](https://github.com/RocketChat/Rocket.Chat/pull/18239))
+
+- Thread List showing wrong items ([#19351](https://github.com/RocketChat/Rocket.Chat/pull/19351))
+
+- Thread view in a channel user haven't joined (#19008) ([#19172](https://github.com/RocketChat/Rocket.Chat/pull/19172))
+
+- Use etag on user info ([#19349](https://github.com/RocketChat/Rocket.Chat/pull/19349))
+
+- UserCard Roles Description ([#19200](https://github.com/RocketChat/Rocket.Chat/pull/19200))
+
+- VisitorAutoComplete component ([#19133](https://github.com/RocketChat/Rocket.Chat/pull/19133))
+
+- Wrong avatar urls when using providers ([#18929](https://github.com/RocketChat/Rocket.Chat/pull/18929))
+
+
+🔍 Minor changes
+
+
+- Build micro services Docker images with correct tags ([#19418](https://github.com/RocketChat/Rocket.Chat/pull/19418))
+
+- Bump Livechat widget ([#19361](https://github.com/RocketChat/Rocket.Chat/pull/19361))
+
+- Bump Livechat widget ([#19478](https://github.com/RocketChat/Rocket.Chat/pull/19478))
+
+- Bump object-path from 0.11.4 to 0.11.5 ([#19298](https://github.com/RocketChat/Rocket.Chat/pull/19298) by [@dependabot[bot]](https://github.com/dependabot[bot]))
+
+- Fix Indie Hosters install image ([#19192](https://github.com/RocketChat/Rocket.Chat/pull/19192) by [@aradhya-gupta](https://github.com/aradhya-gupta))
+
+- Merge master into develop & Set version to 3.8.0-develop ([#19060](https://github.com/RocketChat/Rocket.Chat/pull/19060))
+
+- Micro Services: Add metrics capability to Services ([#19448](https://github.com/RocketChat/Rocket.Chat/pull/19448))
+
+- Micro Services: Create internal services and allowed services list ([#19427](https://github.com/RocketChat/Rocket.Chat/pull/19427))
+
+- Micro Services: Do not wait forever for a service. Fail after 10s or 10 minutes if whitelisted ([#19484](https://github.com/RocketChat/Rocket.Chat/pull/19484))
+
+- Micro Services: Fix logout issue ([#19423](https://github.com/RocketChat/Rocket.Chat/pull/19423))
+
+- Micro Services: Prevent duplicated events ([#19435](https://github.com/RocketChat/Rocket.Chat/pull/19435))
+
+- Non-idiomatic React code ([#19303](https://github.com/RocketChat/Rocket.Chat/pull/19303))
+
+- Reassessment of client helpers ([#19249](https://github.com/RocketChat/Rocket.Chat/pull/19249))
+
+- Refactor some React Pages and Components ([#19202](https://github.com/RocketChat/Rocket.Chat/pull/19202))
+
+- Refactor: Omnichannel departments ([#18920](https://github.com/RocketChat/Rocket.Chat/pull/18920))
+
+- Regression: `Leave Room` modal not closing ([#19460](https://github.com/RocketChat/Rocket.Chat/pull/19460))
+
+- Regression: Agent Status leading to broken page ([#19409](https://github.com/RocketChat/Rocket.Chat/pull/19409))
+
+- Regression: Allow apps to schedule jobs along with processor register ([#19416](https://github.com/RocketChat/Rocket.Chat/pull/19416))
+
+- Regression: Attachment without title or description show "sent attachment" in view mode extended ([#19443](https://github.com/RocketChat/Rocket.Chat/pull/19443))
+
+- Regression: Fix broadcast events when running as monolith ([#19498](https://github.com/RocketChat/Rocket.Chat/pull/19498))
+
+- Regression: Fix ephemeral message stream ([#19513](https://github.com/RocketChat/Rocket.Chat/pull/19513))
+
+- Regression: Fix livechat permission validations ([#19468](https://github.com/RocketChat/Rocket.Chat/pull/19468))
+
+- Regression: Fix presence request logic ([#19527](https://github.com/RocketChat/Rocket.Chat/pull/19527))
+
+- Regression: Fix presence status ([#19474](https://github.com/RocketChat/Rocket.Chat/pull/19474))
+
+- Regression: Fix React warnings ([#19508](https://github.com/RocketChat/Rocket.Chat/pull/19508))
+
+- Regression: Fix setting value not being sent over websocket ([#19477](https://github.com/RocketChat/Rocket.Chat/pull/19477))
+
+- Regression: Fix stream-room-data payload ([#19407](https://github.com/RocketChat/Rocket.Chat/pull/19407))
+
+- Regression: Fix Thread List order ([#19486](https://github.com/RocketChat/Rocket.Chat/pull/19486))
+
+- Regression: Fix visitor field missing on subscription payload ([#19412](https://github.com/RocketChat/Rocket.Chat/pull/19412))
+
+- Regression: GenericTable.HeaderCell does not accept on click anymore ([#19358](https://github.com/RocketChat/Rocket.Chat/pull/19358))
+
+- Regression: Pass `unset` parameter of updated `userData` notification ([#19380](https://github.com/RocketChat/Rocket.Chat/pull/19380))
+
+- Regression: Prevent network broker from starting when not needed ([#19532](https://github.com/RocketChat/Rocket.Chat/pull/19532))
+
+- Regression: Reassessment of client helpers 'XYZ key should not contain .' ([#19310](https://github.com/RocketChat/Rocket.Chat/pull/19310))
+
+- Regression: Rocket.Chat Apps updates always fail ([#19411](https://github.com/RocketChat/Rocket.Chat/pull/19411))
+
+- Regression: Room item menu display delay ([#19401](https://github.com/RocketChat/Rocket.Chat/pull/19401))
+
+- Regression: Sidebar message preview escaping html ([#19382](https://github.com/RocketChat/Rocket.Chat/pull/19382))
+
+- Regression: Sidebar reactivity when read last messages ([#19449](https://github.com/RocketChat/Rocket.Chat/pull/19449))
+
+- Regression: Thread component not updating its message list ([#19390](https://github.com/RocketChat/Rocket.Chat/pull/19390))
+
+- Regression: Thread list misbehaving ([#19413](https://github.com/RocketChat/Rocket.Chat/pull/19413))
+
+- Regression: Thread not showing for unloaded message ([#19402](https://github.com/RocketChat/Rocket.Chat/pull/19402))
+
+- Regression: unable to mark room as read ([#19419](https://github.com/RocketChat/Rocket.Chat/pull/19419))
+
+- Regression: User card closing ([#19322](https://github.com/RocketChat/Rocket.Chat/pull/19322))
+
+- Remove legacy modal template ([#19276](https://github.com/RocketChat/Rocket.Chat/pull/19276))
+
+- Remove legacy slider ([#19255](https://github.com/RocketChat/Rocket.Chat/pull/19255))
+
+- Remove unecessary return at the send code api ([#19494](https://github.com/RocketChat/Rocket.Chat/pull/19494))
+
+- Remove WeDeploy from README ([#19342](https://github.com/RocketChat/Rocket.Chat/pull/19342) by [@lucas-andre](https://github.com/lucas-andre))
+
+- Rewrite: Reset Login Form ([#18237](https://github.com/RocketChat/Rocket.Chat/pull/18237))
+
+- Unify ephemeral message events ([#19464](https://github.com/RocketChat/Rocket.Chat/pull/19464))
+
+- Update Apps-Engine to latest release ([#19499](https://github.com/RocketChat/Rocket.Chat/pull/19499))
+
+- Update Apps-Engine version ([#19385](https://github.com/RocketChat/Rocket.Chat/pull/19385))
+
+- Update comment of "issue-close-app" ([#19078](https://github.com/RocketChat/Rocket.Chat/pull/19078))
+
+- Update feature-request opening process on README ([#19240](https://github.com/RocketChat/Rocket.Chat/pull/19240) by [@brij1999](https://github.com/brij1999))
+
+- Update Fuselage Version ([#19359](https://github.com/RocketChat/Rocket.Chat/pull/19359))
+
+- Use GitHub Container Registry ([#19297](https://github.com/RocketChat/Rocket.Chat/pull/19297))
+
+
+
+### 👩💻👨💻 Contributors 😍
+
+- [@FelipeParreira](https://github.com/FelipeParreira)
+- [@RohitKumar-200](https://github.com/RohitKumar-200)
+- [@aradhya-gupta](https://github.com/aradhya-gupta)
+- [@arminfelder](https://github.com/arminfelder)
+- [@aryankoul](https://github.com/aryankoul)
+- [@ba-9](https://github.com/ba-9)
+- [@bhavayAnand9](https://github.com/bhavayAnand9)
+- [@brij1999](https://github.com/brij1999)
+- [@ceefour](https://github.com/ceefour)
+- [@dependabot[bot]](https://github.com/dependabot[bot])
+- [@galshiff](https://github.com/galshiff)
+- [@jgribonvald](https://github.com/jgribonvald)
+- [@lucas-andre](https://github.com/lucas-andre)
+
+### 👩💻👨💻 Core Team 🤓
+
+- [@MartinSchoeler](https://github.com/MartinSchoeler)
+- [@d-gubert](https://github.com/d-gubert)
+- [@dougfabris](https://github.com/dougfabris)
+- [@frdmn](https://github.com/frdmn)
+- [@gabriellsh](https://github.com/gabriellsh)
+- [@geekgonecrazy](https://github.com/geekgonecrazy)
+- [@ggazzo](https://github.com/ggazzo)
+- [@graywolf336](https://github.com/graywolf336)
+- [@lolimay](https://github.com/lolimay)
+- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc)
+- [@rafaelblink](https://github.com/rafaelblink)
+- [@renatobecker](https://github.com/renatobecker)
+- [@rodrigok](https://github.com/rodrigok)
+- [@sampaiodiego](https://github.com/sampaiodiego)
+- [@tassoevan](https://github.com/tassoevan)
+- [@thassiov](https://github.com/thassiov)
+- [@tiagoevanp](https://github.com/tiagoevanp)
+
# 3.7.2
-`2020-11-13 · 4 🐛 · 3 👩💻👨💻`
+`2020-11-13 · 4 🐛 · 1 🔍 · 4 👩💻👨💻`
### Engine versions
- Node: `12.18.4`
@@ -19,11 +313,20 @@
- Update Polyfills and fix directory in IE ([#19525](https://github.com/RocketChat/Rocket.Chat/pull/19525))
+
+🔍 Minor changes
+
+
+- Release 3.7.2 ([#19529](https://github.com/RocketChat/Rocket.Chat/pull/19529))
+
+
+
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
- [@dougfabris](https://github.com/dougfabris)
- [@ggazzo](https://github.com/ggazzo)
+- [@sampaiodiego](https://github.com/sampaiodiego)
# 3.7.1
`2020-10-09 · 6 🐛 · 5 👩💻👨💻`
diff --git a/README.md b/README.md
index e5bf1cb5784d1..0cf726caac006 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
The Ultimate Open Source WebChat Platform
@@ -35,7 +35,6 @@
* [Raspberry Pi 4](#raspberry-pi-4)
* [Koozali SME](#koozali-sme)
* [Ubuntu VPS](#ubuntu-vps)
- * [WeDeploy](#wedeploy)
* [D2C.io](#d2cio)
* [Syncloud.org](#syncloudorg)
* [About Rocket.Chat](#about-rocketchat)
@@ -64,7 +63,7 @@ Rocket.Chat is moving to a single codebase. Get to know the reasons and how the
# Help Wanted
-At Rocket.Chat, our community drives *everything* we do. The Rocket.Chat team is expanding, and we know no better place to find qualified new team members than *right here* - in our Github community.
+At Rocket.Chat, our community drives *everything* we do. The Rocket.Chat team is expanding, and we know no better place to find qualified new team members than *right here* - in our GitHub community.
If you are passionate about our project, want to work with a world-leading open source team, and enjoy working remotely at a location of your choice, then we want to talk to you!
@@ -134,7 +133,8 @@ Host your own Rocket.Chat server in a few seconds.
## IndieHosters
Get your Rocket.Chat instance hosted in an "as a Service" style. You register and we manage it for you! (updates, backup...).
-[](https://indiehosters.net/shop/product/rocket-chat-21)
+
+
## Cloudron.io
@@ -201,11 +201,6 @@ Add Rocket.Chat to this world famous time tested small enterprise server today.
## Ubuntu VPS
Follow these [deployment instructions](https://rocket.chat/docs/installation/manual-installation/ubuntu/).
-## WeDeploy
-Install Rocket.Chat on [WeDeploy](https://wedeploy.com):
-
-[](https://console.wedeploy.com/deploy?repo=https://github.com/wedeploy-examples/rocketchat-example)
-
## D2C.io
Deploy Rocket.Chat stack to your server with [D2C](https://d2c.io/). Scale with a single click, check live logs and metrics:
@@ -319,11 +314,12 @@ Read about [how it all started](https://www.synopsys.com/blogs/software-security
## Issues
-[Github Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track bugs and tasks on the roadmap.
+[GitHub Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track bugs and tasks on the roadmap.
## Feature Requests
-[Feature Request Forums](https://forums.rocket.chat/c/feature-requests) are used to suggest, discuss and upvote feature suggestions.
+[RocketChat/feature-requests](https://github.com/RocketChat/feature-requests) is used to track Rocket.Chat feature requests and discussions. Click [here](https://github.com/RocketChat/feature-requests/issues/new?template=feature_request.md) to open a new feature request.
+[Feature Request Forums](https://forums.rocket.chat/c/feature-requests) stores the historical archives of old feature requests (up to 2018).
### Stack Overflow
diff --git a/app/2fa/server/code/EmailCheck.ts b/app/2fa/server/code/EmailCheck.ts
index 925520547e25d..c847a32658749 100644
--- a/app/2fa/server/code/EmailCheck.ts
+++ b/app/2fa/server/code/EmailCheck.ts
@@ -94,7 +94,7 @@ ${ t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email') }
return !!valid;
}
- public sendEmailCode(user: IUser): string[] {
+ public sendEmailCode(user: IUser): void {
const emails = this.getUserVerifiedEmails(user);
const random = Random._randomString(6, '0123456789');
const encryptedRandom = bcrypt.hashSync(random, Accounts._bcryptRounds());
@@ -108,8 +108,6 @@ ${ t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email') }
for (const address of emails) {
this.send2FAEmail(address, random, user);
}
-
- return emails;
}
public processInvalidCode(user: IUser): IProcessInvalidCodeResult {
diff --git a/app/2fa/server/functions/resetTOTP.ts b/app/2fa/server/functions/resetTOTP.ts
new file mode 100644
index 0000000000000..059077929347b
--- /dev/null
+++ b/app/2fa/server/functions/resetTOTP.ts
@@ -0,0 +1,68 @@
+import { Meteor } from 'meteor/meteor';
+import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
+
+import { settings } from '../../../settings/server';
+import * as Mailer from '../../../mailer';
+import { Users } from '../../../models/server/raw/index';
+import { IUser } from '../../../../definition/IUser';
+
+const sendResetNotification = async function(uid: string): Promise {
+ const user: IUser = await Users.findOneById(uid, { projection: { language: 1, emails: 1 } });
+ if (!user) {
+ throw new Meteor.Error('invalid-user');
+ }
+
+ const language = user.language || settings.get('Language') || 'en';
+ const addresses = user.emails?.filter(({ verified }: { verified: boolean}) => verified).map((e) => e.address);
+ if (!addresses?.length) {
+ return;
+ }
+
+ const t = (s: string): string => TAPi18n.__(s, { lng: language });
+ const text = `
+ ${ t('Your_TOTP_has_been_reset') }
+
+ ${ t('TOTP_Reset_Other_Key_Warning') }
+ `;
+ const html = `
+
${ t('Your_TOTP_has_been_reset') }
+
${ t('TOTP_Reset_Other_Key_Warning') }
+ `;
+
+ const from = settings.get('From_Email');
+ const subject = t('TOTP_reset_email');
+
+ for (const address of addresses) {
+ Meteor.defer(() => {
+ try {
+ Mailer.send({
+ to: address,
+ from,
+ subject,
+ text,
+ html,
+ } as any);
+ } catch (error) {
+ throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
+ function: 'resetUserTOTP',
+ message: error.message,
+ });
+ }
+ });
+ }
+};
+
+export async function resetTOTP(userId: string, notifyUser = false): Promise {
+ if (notifyUser) {
+ await sendResetNotification(userId);
+ }
+
+ const result = await Users.resetTOTPById(userId);
+
+ if (result?.modifiedCount === 1) {
+ await Users.removeResumeService(userId);
+ return true;
+ }
+
+ return false;
+}
diff --git a/app/api/server/v1/autotranslate.js b/app/api/server/v1/autotranslate.js
index da611dd89847f..6a65c21adc732 100644
--- a/app/api/server/v1/autotranslate.js
+++ b/app/api/server/v1/autotranslate.js
@@ -22,6 +22,7 @@ API.v1.addRoute('autotranslate.saveSettings', { authRequired: true }, {
if (!settings.get('AutoTranslate_Enabled')) {
return API.v1.failure('AutoTranslate is disabled.');
}
+
if (!roomId) {
return API.v1.failure('The bodyParam "roomId" is required.');
}
diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js
index b10a66c4e9332..447c10bd5d57a 100644
--- a/app/api/server/v1/chat.js
+++ b/app/api/server/v1/chat.js
@@ -445,6 +445,7 @@ API.v1.addRoute('chat.getThreadsList', { authRequired: true }, {
}
const typeThread = {
+ _hidden: { $ne: true },
...type === 'following' && { replies: { $in: [this.userId] } },
...type === 'unread' && { _id: { $in: Subscriptions.findOneByRoomIdAndUserId(room._id, user._id).tunread } },
...text && {
diff --git a/app/api/server/v1/rooms.js b/app/api/server/v1/rooms.js
index 0d9d6c36806fd..35707c919b82d 100644
--- a/app/api/server/v1/rooms.js
+++ b/app/api/server/v1/rooms.js
@@ -365,7 +365,7 @@ API.v1.addRoute('rooms.export', { authRequired: true }, {
throw new Meteor.Error('error-invalid-params');
}
- if (!hasPermission(this.userId, 'mail-messages')) {
+ if (!hasPermission(this.userId, 'mail-messages', rid)) {
throw new Meteor.Error('error-action-not-allowed', 'Mailing is not allowed');
}
diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js
index cf926f44d4785..c7a7ca33c97f8 100644
--- a/app/api/server/v1/settings.js
+++ b/app/api/server/v1/settings.js
@@ -7,6 +7,7 @@ import { Settings } from '../../../models/server';
import { hasPermission } from '../../../authorization';
import { API } from '../api';
import { SettingsEvents, settings } from '../../../settings/server';
+import { setValue } from '../../../settings/server/raw';
const fetchSettings = (query, sort, offset, count, fields) => {
const settings = Settings.find(query, {
@@ -150,6 +151,7 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, {
_id: this.urlParams._id,
value: this.bodyParams.value,
});
+ setValue(this.urlParams._id, this.bodyParams.value);
return API.v1.success();
}
diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js
index b4adea3365d3f..b182c36330504 100644
--- a/app/api/server/v1/users.js
+++ b/app/api/server/v1/users.js
@@ -23,6 +23,7 @@ import { findUsersToAutocomplete } from '../lib/users';
import { getUserForCheck, emailCheck } from '../../../2fa/server/code';
import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey';
import { setUserStatus } from '../../../../imports/users-presence/server/activeUsers';
+import { resetTOTP } from '../../../2fa/server/functions/resetTOTP';
API.v1.addRoute('users.create', { authRequired: true }, {
post() {
@@ -719,7 +720,9 @@ API.v1.addRoute('users.2fa.sendEmailCode', {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
- return API.v1.success(emailCheck.sendEmailCode(getUserForCheck(userId)));
+ emailCheck.sendEmailCode(getUserForCheck(userId));
+
+ return API.v1.success();
},
});
@@ -797,7 +800,7 @@ API.v1.addRoute('users.removeOtherTokens', { authRequired: true }, {
},
});
-API.v1.addRoute('users.resetE2EKey', { authRequired: true, twoFactorRequired: true }, {
+API.v1.addRoute('users.resetE2EKey', { authRequired: true, twoFactorRequired: true, twoFactorOptions: { disableRememberMe: true } }, {
post() {
// reset own keys
if (this.isUserFromParams()) {
@@ -826,3 +829,31 @@ API.v1.addRoute('users.resetE2EKey', { authRequired: true, twoFactorRequired: tr
return API.v1.success();
},
});
+
+API.v1.addRoute('users.resetTOTP', { authRequired: true, twoFactorRequired: true, twoFactorOptions: { disableRememberMe: true } }, {
+ post() {
+ // reset own keys
+ if (this.isUserFromParams()) {
+ Promise.await(resetTOTP(this.userId, false));
+ return API.v1.success();
+ }
+
+ // reset other user keys
+ const user = this.getUserFromParams();
+ if (!user) {
+ throw new Meteor.Error('error-invalid-user-id', 'Invalid user id');
+ }
+
+ if (!settings.get('Accounts_TwoFactorAuthentication_Enforce_Password_Fallback')) {
+ throw new Meteor.Error('error-not-allowed', 'Not allowed');
+ }
+
+ if (!hasPermission(Meteor.userId(), 'edit-other-user-totp')) {
+ throw new Meteor.Error('error-not-allowed', 'Not allowed');
+ }
+
+ Promise.await(resetTOTP(user._id, true));
+
+ return API.v1.success();
+ },
+});
diff --git a/app/apps/client/communication/websockets.js b/app/apps/client/communication/websockets.js
index aa1a23d451cbf..2539b94b72642 100644
--- a/app/apps/client/communication/websockets.js
+++ b/app/apps/client/communication/websockets.js
@@ -1,5 +1,5 @@
import { Meteor } from 'meteor/meteor';
-import EventEmitter from 'wolfy87-eventemitter';
+import { Emitter } from '@rocket.chat/emitter';
import { slashCommands, APIClient } from '../../../utils';
import { CachedCollectionManager } from '../../../ui-cached-collection';
@@ -16,7 +16,7 @@ export const AppEvents = Object.freeze({
COMMAND_REMOVED: 'command/removed',
});
-export class AppWebsocketReceiver extends EventEmitter {
+export class AppWebsocketReceiver extends Emitter {
constructor() {
super();
diff --git a/app/apps/server/bridges/bridges.js b/app/apps/server/bridges/bridges.js
index c495332e6d1b7..ed62b2c6479e2 100644
--- a/app/apps/server/bridges/bridges.js
+++ b/app/apps/server/bridges/bridges.js
@@ -16,6 +16,7 @@ import { AppUserBridge } from './users';
import { AppLivechatBridge } from './livechat';
import { AppUploadBridge } from './uploads';
import { UiInteractionBridge } from './uiInteraction';
+import { AppSchedulerBridge } from './scheduler';
export class RealAppBridges extends AppBridges {
constructor(orch) {
@@ -37,6 +38,7 @@ export class RealAppBridges extends AppBridges {
this._livechatBridge = new AppLivechatBridge(orch);
this._uploadBridge = new AppUploadBridge(orch);
this._uiInteractionBridge = new UiInteractionBridge(orch);
+ this._schedulerBridge = new AppSchedulerBridge(orch);
}
getCommandBridge() {
@@ -102,4 +104,8 @@ export class RealAppBridges extends AppBridges {
getUiInteractionBridge() {
return this._uiInteractionBridge;
}
+
+ getSchedulerBridge() {
+ return this._schedulerBridge;
+ }
}
diff --git a/app/apps/server/bridges/index.js b/app/apps/server/bridges/index.js
index 638c03c142c54..a845f0e4ca587 100644
--- a/app/apps/server/bridges/index.js
+++ b/app/apps/server/bridges/index.js
@@ -10,6 +10,7 @@ import { AppRoomBridge } from './rooms';
import { AppInternalBridge } from './internal';
import { AppSettingBridge } from './settings';
import { AppUserBridge } from './users';
+import { AppSchedulerBridge } from './scheduler';
export {
RealAppBridges,
@@ -24,4 +25,5 @@ export {
AppSettingBridge,
AppUserBridge,
AppInternalBridge,
+ AppSchedulerBridge,
};
diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js
index e3f3e722dc650..d40c1dde82f05 100644
--- a/app/apps/server/bridges/messages.js
+++ b/app/apps/server/bridges/messages.js
@@ -1,9 +1,8 @@
-import { Random } from 'meteor/random';
-
import { Messages, Users, Subscriptions } from '../../../models/server';
-import { Notifications } from '../../../notifications';
import { updateMessage } from '../../../lib/server/functions/updateMessage';
import { executeSendMessage } from '../../../lib/server/methods/sendMessage';
+import { api } from '../../../../server/sdk/api';
+import notifications from '../../../notifications/server/lib/Notifications';
export class AppMessageBridge {
constructor(orch) {
@@ -52,10 +51,8 @@ export class AppMessageBridge {
return;
}
- Notifications.notifyUser(user.id, 'message', {
+ api.broadcast('notify.ephemeralMessage', user.id, msg.rid, {
...msg,
- _id: Random.id(),
- ts: new Date(),
});
}
@@ -67,11 +64,6 @@ export class AppMessageBridge {
}
const msg = this.orch.getConverters().get('messages').convertAppMessage(message);
- const rmsg = Object.assign(msg, {
- _id: Random.id(),
- rid: room.id,
- ts: new Date(),
- });
const users = Subscriptions.findByRoomIdWhenUserIdExists(room.id, { fields: { 'u._id': 1 } })
.fetch()
@@ -80,7 +72,19 @@ export class AppMessageBridge {
Users.findByIds(users, { fields: { _id: 1 } })
.fetch()
.forEach(({ _id }) =>
- Notifications.notifyUser(_id, 'message', rmsg),
+ api.broadcast('notify.ephemeralMessage', _id, room.id, {
+ ...msg,
+ }),
);
}
+
+ async typing({ scope, id, username, isTyping }) {
+ switch (scope) {
+ case 'room':
+ notifications.notifyRoom(id, 'typing', username, isTyping);
+ return;
+ default:
+ throw new Error('Unrecognized typing scope provided');
+ }
+ }
}
diff --git a/app/apps/server/bridges/scheduler.js b/app/apps/server/bridges/scheduler.js
new file mode 100644
index 0000000000000..f59f61f0640d7
--- /dev/null
+++ b/app/apps/server/bridges/scheduler.js
@@ -0,0 +1,92 @@
+import Agenda from 'agenda';
+import { MongoInternals } from 'meteor/mongo';
+import { StartupType } from '@rocket.chat/apps-engine/definition/scheduler';
+
+function _callProcessor(processor) {
+ return (job) => processor(job?.attrs?.data || {});
+}
+
+export class AppSchedulerBridge {
+ constructor(orch) {
+ this.orch = orch;
+ this.scheduler = new Agenda({
+ mongo: MongoInternals.defaultRemoteCollectionDriver().mongo.client.db(),
+ db: { collection: 'rocketchat_apps_scheduler' },
+ // this ensures the same job doesn't get executed multiple times in a cluster
+ defaultConcurrency: 1,
+ });
+ this.isConnected = false;
+ }
+
+ async registerProcessors(processors = [], appId) {
+ const runAfterRegister = [];
+ this.orch.debugLog(`The App ${ appId } is registering job processors`, processors);
+ processors.forEach(({ id, processor, startupSetting }) => {
+ this.scheduler.define(id, _callProcessor(processor));
+
+ if (startupSetting) {
+ switch (startupSetting.type) {
+ case StartupType.ONETIME:
+ runAfterRegister.push(this.scheduleOnceAfterRegister({ id, when: startupSetting.when, data: startupSetting.data }, appId));
+ break;
+ case StartupType.RECURRING:
+ runAfterRegister.push(this.scheduleRecurring({ id, interval: startupSetting.interval, data: startupSetting.data }, appId));
+ break;
+ default:
+ break;
+ }
+ }
+ });
+
+ if (runAfterRegister.length) {
+ await Promise.all(runAfterRegister);
+ }
+ }
+
+ async scheduleOnce(job, appId) {
+ this.orch.debugLog(`The App ${ appId } is scheduling an onetime job`, job);
+ await this.startScheduler();
+ await this.scheduler.schedule(job.when, job.id, job.data || {});
+ }
+
+ async scheduleOnceAfterRegister(job, appId) {
+ const scheduledJobs = await this.scheduler.jobs({ name: job.id, type: 'normal' });
+ if (!scheduledJobs.length) {
+ await this.scheduleOnce(job, appId);
+ }
+ }
+
+ async scheduleRecurring(job, appId) {
+ this.orch.debugLog(`The App ${ appId } is scheduling a recurring job`, job);
+ await this.startScheduler();
+ await this.scheduler.every(job.interval, job.id, job.data || {});
+ }
+
+ async cancelJob(jobId, appId) {
+ this.orch.debugLog(`The App ${ appId } is canceling a job`, jobId);
+ await this.startScheduler();
+ try {
+ await this.scheduler.cancel({ name: jobId });
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ async cancelAllJobs(appId) {
+ this.orch.debugLog(`Canceling all jobs of App ${ appId }`);
+ await this.startScheduler();
+ const matcher = new RegExp(`_${ appId }$`);
+ try {
+ await this.scheduler.cancel({ name: { $regex: matcher } });
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ async startScheduler() {
+ if (!this.isConnected) {
+ await this.scheduler.start();
+ this.isConnected = true;
+ }
+ }
+}
diff --git a/app/apps/server/communication/methods.js b/app/apps/server/communication/methods.js
index 16170105caa61..2ed6a10bfbcbb 100644
--- a/app/apps/server/communication/methods.js
+++ b/app/apps/server/communication/methods.js
@@ -1,7 +1,8 @@
import { Meteor } from 'meteor/meteor';
-import { settings } from '../../../settings';
-import { hasPermission } from '../../../authorization';
+import { settings } from '../../../settings/server';
+import { hasPermission } from '../../../authorization/server';
+import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired';
const waitToLoad = function(orch) {
return new Promise((resolve) => {
@@ -54,7 +55,7 @@ export class AppMethods {
return instance.isLoaded();
},
- 'apps/go-enable'() {
+ 'apps/go-enable': twoFactorRequired(function _appsGoEnable() {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'apps/go-enable',
@@ -70,9 +71,9 @@ export class AppMethods {
settings.set('Apps_Framework_enabled', true);
Promise.await(waitToLoad(instance._orch));
- },
+ }),
- 'apps/go-disable'() {
+ 'apps/go-disable': twoFactorRequired(function _appsGoDisable() {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'apps/go-enable',
@@ -88,7 +89,7 @@ export class AppMethods {
settings.set('Apps_Framework_enabled', false);
Promise.await(waitToUnload(instance._orch));
- },
+ }),
});
}
}
diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js
index b5bdf523c21ff..e211ceea27996 100644
--- a/app/apps/server/communication/rest.js
+++ b/app/apps/server/communication/rest.js
@@ -60,6 +60,27 @@ export class AppsRestApi {
const manager = this._manager;
const fileHandler = this._handleFile;
+ const handleError = (message, e) => {
+ // when there is no `response` field in the error, it means the request
+ // couldn't even make it to the server
+ if (!e.hasOwnProperty('response')) {
+ orchestrator.getRocketChatLogger().error(message, e.message);
+ return API.v1.internalError('Could not reach the Marketplace');
+ }
+
+ orchestrator.getRocketChatLogger().error(message, e.response.data);
+
+ if (e.response.statusCode >= 500 && e.response.statusCode <= 599) {
+ return API.v1.internalError();
+ }
+
+ if (e.response.statusCode === 404) {
+ return API.v1.notFound();
+ }
+
+ return API.v1.failure();
+ };
+
this.api.addRoute('', { authRequired: true, permissionsRequired: ['manage-apps'] }, {
get() {
const baseUrl = orchestrator.getMarketplaceUrl();
@@ -78,8 +99,7 @@ export class AppsRestApi {
headers,
});
} catch (e) {
- orchestrator.getRocketChatLogger().error('Error getting the Apps:', e.response.data);
- return API.v1.internalError();
+ return handleError('Error getting the App information from the Marketplace:', e);
}
if (!result || result.statusCode !== 200) {
@@ -178,7 +198,7 @@ export class AppsRestApi {
const downloadPromise = new Promise((resolve, reject) => {
const token = getWorkspaceAccessToken(true, 'marketplace:download', false);
- HTTP.get(`${ baseUrl }/v1/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }?token=${ token }`, {
+ HTTP.get(`${ baseUrl }/v2/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }?token=${ token }`, {
headers,
npmRequestOptions: { encoding: null },
}, (error, result) => {
@@ -228,17 +248,13 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Failed to get a file to install for the App. ' });
}
- const aff = Promise.await(manager.add(buff.toString('base64'), true, marketplaceInfo));
+ const aff = Promise.await(manager.add(buff, true, marketplaceInfo));
const info = aff.getAppInfo();
if (aff.hasStorageError()) {
return API.v1.failure({ status: 'storage_error', messages: [aff.getStorageError()] });
}
- if (aff.getCompilerErrors().length) {
- return API.v1.failure({ status: 'compiler_error', messages: aff.getCompilerErrors() });
- }
-
if (aff.hasAppUserError()) {
return API.v1.failure({
status: 'app_user_error',
@@ -323,20 +339,6 @@ export class AppsRestApi {
},
});
- const handleError = (message, e) => {
- orchestrator.getRocketChatLogger().error(message, e.response.data);
-
- if (e.response.statusCode >= 500 && e.response.statusCode <= 599) {
- return API.v1.internalError();
- }
-
- if (e.response.statusCode === 404) {
- return API.v1.notFound();
- }
-
- return API.v1.failure();
- };
-
this.api.addRoute(':id', { authRequired: true, permissionsRequired: ['manage-apps'] }, {
get() {
if (this.queryParams.marketplace && this.queryParams.version) {
@@ -433,7 +435,7 @@ export class AppsRestApi {
let result;
try {
- result = HTTP.get(`${ baseUrl }/v1/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }`, {
+ result = HTTP.get(`${ baseUrl }/v2/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }`, {
headers,
npmRequestOptions: { encoding: null },
});
@@ -464,20 +466,27 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Failed to get a file to install for the App. ' });
}
- const aff = Promise.await(manager.update(buff.toString('base64')));
+ const aff = Promise.await(manager.update(buff));
const info = aff.getAppInfo();
- // Should the updated version have compiler errors, no App will be returned
- if (aff.getApp()) {
- info.status = aff.getApp().getStatus();
- } else {
- info.status = 'compiler_error';
+ if (aff.hasStorageError()) {
+ return API.v1.failure({ status: 'storage_error', messages: [aff.getStorageError()] });
+ }
+
+ if (aff.hasAppUserError()) {
+ return API.v1.failure({
+ status: 'app_user_error',
+ messages: [aff.getAppUserError().message],
+ payload: { username: aff.getAppUserError().username },
+ });
}
+ info.status = aff.getApp().getStatus();
+
return API.v1.success({
app: info,
implemented: aff.getImplementedInferfaces(),
- compilerErrors: aff.getCompilerErrors(),
+ licenseValidation: aff.getLicenseValidationResult(),
});
},
delete() {
diff --git a/app/apps/server/communication/websockets.js b/app/apps/server/communication/websockets.js
index e5615c0e2d91a..ead46d67e59d9 100644
--- a/app/apps/server/communication/websockets.js
+++ b/app/apps/server/communication/websockets.js
@@ -1,6 +1,7 @@
-import { Meteor } from 'meteor/meteor';
import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus';
+import notifications from '../../../notifications/server/lib/Notifications';
+
export const AppEvents = Object.freeze({
APP_ADDED: 'app/added',
APP_REMOVED: 'app/removed',
@@ -101,18 +102,10 @@ export class AppServerListener {
export class AppServerNotifier {
constructor(orch) {
- this.engineStreamer = new Meteor.Streamer('apps-engine', { retransmit: false });
- this.engineStreamer.serverOnly = true;
- this.engineStreamer.allowRead('none');
- this.engineStreamer.allowEmit('all');
- this.engineStreamer.allowWrite('none');
+ this.engineStreamer = notifications.streamAppsEngine;
// This is used to broadcast to the web clients
- this.clientStreamer = new Meteor.Streamer('apps', { retransmit: false });
- this.clientStreamer.serverOnly = true;
- this.clientStreamer.allowRead('all');
- this.clientStreamer.allowEmit('all');
- this.clientStreamer.allowWrite('none');
+ this.clientStreamer = notifications.streamApps;
this.received = new Map();
this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.received);
diff --git a/app/authentication/server/lib/restrictLoginAttempts.ts b/app/authentication/server/lib/restrictLoginAttempts.ts
index 0e1fa7ac237a9..ef3b9368816a0 100644
--- a/app/authentication/server/lib/restrictLoginAttempts.ts
+++ b/app/authentication/server/lib/restrictLoginAttempts.ts
@@ -18,10 +18,10 @@ export const isValidLoginAttemptByIp = async (ip: string): Promise => {
return true;
}
- const lastLogin = await Sessions.findLastLoginByIp(ip);
+ const lastLogin = await Sessions.findLastLoginByIp(ip) as {loginAt?: Date} | undefined;
let failedAttemptsSinceLastLogin;
- if (!lastLogin) {
+ if (!lastLogin || !lastLogin.loginAt) {
failedAttemptsSinceLastLogin = await ServerEvents.countFailedAttemptsByIp(ip);
} else {
failedAttemptsSinceLastLogin = await ServerEvents.countFailedAttemptsByIpSince(ip, new Date(lastLogin.loginAt));
diff --git a/app/authorization/lib/AuthorizationUtils.ts b/app/authorization/lib/AuthorizationUtils.ts
index daa19828305c0..41c96bd6fd3ae 100644
--- a/app/authorization/lib/AuthorizationUtils.ts
+++ b/app/authorization/lib/AuthorizationUtils.ts
@@ -1,15 +1,13 @@
-import { Meteor } from 'meteor/meteor';
-
const restrictedRolePermissions = new Map();
export const AuthorizationUtils = class {
- static addRolePermissionWhiteList(roleId: string, list: [string]): void {
+ static addRolePermissionWhiteList(roleId: string, list: string[]): void {
if (!roleId) {
- throw new Meteor.Error('invalid-param');
+ throw new Error('invalid-param');
}
if (!list) {
- throw new Meteor.Error('invalid-param');
+ throw new Error('invalid-param');
}
if (!restrictedRolePermissions.has(roleId)) {
@@ -25,7 +23,7 @@ export const AuthorizationUtils = class {
static isPermissionRestrictedForRole(permissionId: string, roleId: string): boolean {
if (!roleId || !permissionId) {
- throw new Meteor.Error('invalid-param');
+ throw new Error('invalid-param');
}
if (!restrictedRolePermissions.has(roleId)) {
@@ -40,9 +38,9 @@ export const AuthorizationUtils = class {
return !rules.has(permissionId);
}
- static isPermissionRestrictedForRoleList(permissionId: string, roleList: [string]): boolean {
+ static isPermissionRestrictedForRoleList(permissionId: string, roleList: string[]): boolean {
if (!roleList || !permissionId) {
- throw new Meteor.Error('invalid-param');
+ throw new Error('invalid-param');
}
for (const roleId of roleList) {
diff --git a/app/authorization/server/functions/canAccessRoom.js b/app/authorization/server/functions/canAccessRoom.js
deleted file mode 100644
index abfa3e70c631a..0000000000000
--- a/app/authorization/server/functions/canAccessRoom.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { hasPermissionAsync } from './hasPermission';
-import { Subscriptions } from '../../../models/server/raw';
-import { getValue } from '../../../settings/server/raw';
-
-export const roomAccessValidators = [
- async function(room, user = {}) {
- if (room && room.t === 'c') {
- const anonymous = await getValue('Accounts_AllowAnonymousRead');
- if (!user._id && anonymous === true) {
- return true;
- }
-
- return hasPermissionAsync(user._id, 'view-c-room');
- }
- },
- async function(room, user) {
- if (!room || !user) {
- return;
- }
-
- const exists = await Subscriptions.countByRoomIdAndUserId(room._id, user._id);
- if (exists) {
- return true;
- }
- },
-];
-
-export const canAccessRoomAsync = async (room, user, extraData) => {
- for (let i = 0, total = roomAccessValidators.length; i < total; i++) {
- // eslint-disable-next-line no-await-in-loop
- const permitted = await roomAccessValidators[i](room, user, extraData);
- if (permitted) {
- return true;
- }
- }
-};
-
-export const canAccessRoom = (room, user, extraData) => Promise.await(canAccessRoomAsync(room, user, extraData));
-
-export const addRoomAccessValidator = (validator) => roomAccessValidators.push(validator.bind(this));
diff --git a/app/authorization/server/functions/canAccessRoom.ts b/app/authorization/server/functions/canAccessRoom.ts
new file mode 100644
index 0000000000000..5a943ec031a66
--- /dev/null
+++ b/app/authorization/server/functions/canAccessRoom.ts
@@ -0,0 +1,8 @@
+import { Promise } from 'meteor/promise';
+
+import { Authorization } from '../../../../server/sdk';
+import { IAuthorization } from '../../../../server/sdk/types/IAuthorization';
+
+export const canAccessRoomAsync = Authorization.canAccessRoom;
+
+export const canAccessRoom = (...args: Parameters): boolean => Promise.await(canAccessRoomAsync(...args));
diff --git a/app/authorization/server/functions/hasPermission.js b/app/authorization/server/functions/hasPermission.js
index eb3d901916090..e14bf7f869a49 100644
--- a/app/authorization/server/functions/hasPermission.js
+++ b/app/authorization/server/functions/hasPermission.js
@@ -1,63 +1,8 @@
-import mem from 'mem';
+import { Authorization } from '../../../../server/sdk';
-import { Permissions, Users, Subscriptions } from '../../../models/server/raw';
-import { AuthorizationUtils } from '../../lib/AuthorizationUtils';
-
-const rolesHasPermission = mem(async (permission, roles) => {
- if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) {
- return false;
- }
-
- const result = await Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } });
- return !!result;
-}, {
- cacheKey: JSON.stringify,
- ...process.env.TEST_MODE === 'true' && { maxAge: 1 },
-});
-
-const getRoles = mem(async (uid, scope) => {
- const { roles: userRoles = [] } = await Users.findOne({ _id: uid }, { projection: { roles: 1 } });
- const { roles: subscriptionsRoles = [] } = (scope && await Subscriptions.findOne({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {};
- return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b));
-}, { maxAge: 1000, cacheKey: JSON.stringify });
-
-export const clearCache = () => {
- mem.clear(getRoles);
- mem.clear(rolesHasPermission);
-};
-
-async function atLeastOne(uid, permissions = [], scope) {
- const sortedRoles = await getRoles(uid, scope);
- for (const permission of permissions) {
- if (await rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line
- return true;
- }
- }
-
- return false;
-}
-
-async function all(uid, permissions = [], scope) {
- const sortedRoles = await getRoles(uid, scope);
- for (const permission of permissions) {
- if (!await rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line
- return false;
- }
- }
-
- return true;
-}
-
-function _hasPermission(userId, permissions, scope, strategy) {
- if (!userId) {
- return false;
- }
- return strategy(userId, [].concat(permissions), scope);
-}
-
-export const hasAllPermissionAsync = async (userId, permissions, scope) => _hasPermission(userId, permissions, scope, all);
-export const hasPermissionAsync = async (userId, permissionId, scope) => _hasPermission(userId, permissionId, scope, all);
-export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => _hasPermission(userId, permissions, scope, atLeastOne);
+export const hasAllPermissionAsync = async (userId, permissions, scope) => Authorization.hasAllPermission(userId, permissions, scope);
+export const hasPermissionAsync = async (userId, permissionId, scope) => Authorization.hasPermission(userId, permissionId, scope);
+export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => Authorization.hasAtLeastOnePermission(userId, permissions, scope);
export const hasAllPermission = (userId, permissions, scope) => Promise.await(hasAllPermissionAsync(userId, permissions, scope));
export const hasPermission = (userId, permissionId, scope) => Promise.await(hasPermissionAsync(userId, permissionId, scope));
diff --git a/app/authorization/server/index.js b/app/authorization/server/index.js
index c4ceb42c9a3d5..c248f1c6e0e90 100644
--- a/app/authorization/server/index.js
+++ b/app/authorization/server/index.js
@@ -1,6 +1,5 @@
import { addUserRoles } from './functions/addUserRoles';
import {
- addRoomAccessValidator,
canAccessRoom,
roomAccessValidators,
} from './functions/canAccessRoom';
@@ -32,7 +31,6 @@ export {
removeUserFromRoles,
canSendMessage,
validateRoomMessagePermissions,
- addRoomAccessValidator,
roomAccessValidators,
addUserRoles,
canAccessRoom,
diff --git a/app/authorization/server/lib/streamer.js b/app/authorization/server/lib/streamer.js
deleted file mode 100644
index f00103832be8e..0000000000000
--- a/app/authorization/server/lib/streamer.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-export const rolesStreamer = new Meteor.Streamer('roles');
-rolesStreamer.allowWrite('none');
-rolesStreamer.allowRead('logged');
diff --git a/app/authorization/server/methods/addUserToRole.js b/app/authorization/server/methods/addUserToRole.js
index b9d8302bc0b39..a7fdd21ec24dc 100644
--- a/app/authorization/server/methods/addUserToRole.js
+++ b/app/authorization/server/methods/addUserToRole.js
@@ -3,8 +3,8 @@ import _ from 'underscore';
import { Users, Roles } from '../../../models/server';
import { settings } from '../../../settings/server';
-import { Notifications } from '../../../notifications/server';
import { hasPermission } from '../functions/hasPermission';
+import { api } from '../../../../server/sdk/api';
Meteor.methods({
'authorization:addUserToRole'(roleName, username, scope) {
@@ -50,7 +50,7 @@ Meteor.methods({
const add = Roles.addUserRoles(user._id, roleName, scope);
if (settings.get('UI_DisplayRoles')) {
- Notifications.notifyLogged('roles-change', {
+ api.broadcast('user.roleUpdate', {
type: 'added',
_id: roleName,
u: {
diff --git a/app/authorization/server/methods/deleteRole.js b/app/authorization/server/methods/deleteRole.js
index 56ab719fe981c..8613e1761b0a5 100644
--- a/app/authorization/server/methods/deleteRole.js
+++ b/app/authorization/server/methods/deleteRole.js
@@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor';
import * as Models from '../../../models/server';
import { hasPermission } from '../functions/hasPermission';
-import { rolesStreamer } from '../lib/streamer';
Meteor.methods({
'authorization:deleteRole'(roleName) {
@@ -36,13 +35,6 @@ Meteor.methods({
});
}
- const removed = Models.Roles.remove(role.name);
- if (removed) {
- rolesStreamer.emit('roles', {
- type: 'removed',
- name: roleName,
- });
- }
- return removed;
+ return Models.Roles.remove(role.name);
},
});
diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.js
index 4ccec8b2a7c65..9a36a8895870c 100644
--- a/app/authorization/server/methods/removeUserFromRole.js
+++ b/app/authorization/server/methods/removeUserFromRole.js
@@ -3,8 +3,8 @@ import _ from 'underscore';
import { Roles } from '../../../models/server';
import { settings } from '../../../settings/server';
-import { Notifications } from '../../../notifications/server';
import { hasPermission } from '../functions/hasPermission';
+import { api } from '../../../../server/sdk/api';
Meteor.methods({
'authorization:removeUserFromRole'(roleName, username, scope) {
@@ -55,7 +55,7 @@ Meteor.methods({
const remove = Roles.removeUserRoles(user._id, roleName, scope);
if (settings.get('UI_DisplayRoles')) {
- Notifications.notifyLogged('roles-change', {
+ api.broadcast('user.roleUpdate', {
type: 'removed',
_id: roleName,
u: {
diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js
index 64cb3437c9dff..5e09f211240d7 100644
--- a/app/authorization/server/methods/saveRole.js
+++ b/app/authorization/server/methods/saveRole.js
@@ -2,9 +2,8 @@ import { Meteor } from 'meteor/meteor';
import { Roles } from '../../../models/server';
import { settings } from '../../../settings/server';
-import { Notifications } from '../../../notifications/server';
import { hasPermission } from '../functions/hasPermission';
-import { rolesStreamer } from '../lib/streamer';
+import { api } from '../../../../server/sdk/api';
Meteor.methods({
'authorization:saveRole'(roleData) {
@@ -27,15 +26,11 @@ Meteor.methods({
const update = Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa);
if (settings.get('UI_DisplayRoles')) {
- Notifications.notifyLogged('roles-change', {
+ api.broadcast('user.roleUpdate', {
type: 'changed',
_id: roleData.name,
});
}
- rolesStreamer.emit('roles', {
- type: 'changed',
- ...roleData,
- });
return update;
},
});
diff --git a/app/authorization/server/startup.js b/app/authorization/server/startup.js
index 9bc311170a69d..2ca73f01092fa 100644
--- a/app/authorization/server/startup.js
+++ b/app/authorization/server/startup.js
@@ -4,7 +4,6 @@ import { Meteor } from 'meteor/meteor';
import { Roles, Permissions, Settings } from '../../models/server';
import { settings } from '../../settings/server';
import { getSettingPermissionId, CONSTANTS } from '../lib';
-import { clearCache } from './functions/hasPermission';
Meteor.startup(function() {
// Note:
@@ -43,6 +42,7 @@ Meteor.startup(function() {
{ _id: 'edit-other-user-password', roles: ['admin'] },
{ _id: 'edit-other-user-avatar', roles: ['admin'] },
{ _id: 'edit-other-user-e2ee', roles: ['admin'] },
+ { _id: 'edit-other-user-totp', roles: ['admin'] },
{ _id: 'edit-privileged-setting', roles: ['admin'] },
{ _id: 'edit-room', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'edit-room-avatar', roles: ['admin', 'owner', 'moderator'] },
@@ -223,12 +223,4 @@ Meteor.startup(function() {
};
settings.onload('*', createPermissionForAddedSetting);
-
- Roles.on('change', ({ diff }) => {
- if (diff && Object.keys(diff).length === 1 && diff._updatedAt) {
- // avoid useless changes
- return;
- }
- clearCache();
- });
});
diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js
deleted file mode 100644
index aafdaf2741899..0000000000000
--- a/app/authorization/server/streamer/permissions/emitter.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import Settings from '../../../../models/server/models/Settings';
-import { Notifications } from '../../../../notifications/server';
-import { CONSTANTS } from '../../../lib';
-import Permissions from '../../../../models/server/models/Permissions';
-import { clearCache } from '../../functions/hasPermission';
-
-Permissions.on('change', ({ clientAction, id, data, diff }) => {
- if (diff && Object.keys(diff).length === 1 && diff._updatedAt) {
- // avoid useless changes
- return;
- }
- switch (clientAction) {
- case 'updated':
- case 'inserted':
- data = data ?? Permissions.findOneById(id);
- break;
-
- case 'removed':
- data = { _id: id };
- break;
- }
-
- clearCache();
-
- Notifications.notifyLoggedInThisInstance(
- 'permissions-changed',
- clientAction,
- data,
- );
-
- if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) {
- // if the permission changes, the effect on the visible settings depends on the role affected.
- // The selected-settings-based consumers have to react accordingly and either add or remove the
- // setting from the user's collection
- const setting = Settings.findOneNotHiddenById(data.settingId);
- if (!setting) {
- return;
- }
- Notifications.notifyLoggedInThisInstance(
- 'private-settings-changed',
- 'updated',
- setting,
- );
- }
-});
diff --git a/app/authorization/server/streamer/permissions/index.js b/app/authorization/server/streamer/permissions/index.js
index 09e7ccffc6c7a..edffbdfe3e734 100644
--- a/app/authorization/server/streamer/permissions/index.js
+++ b/app/authorization/server/streamer/permissions/index.js
@@ -1,7 +1,6 @@
import { Meteor } from 'meteor/meteor';
import Permissions from '../../../../models/server/models/Permissions';
-import './emitter';
Meteor.methods({
'permissions/get'(updatedAt) {
diff --git a/app/autolinker/client/client.js b/app/autolinker/client/client.js
index e743725b67e15..f18cd595e1600 100644
--- a/app/autolinker/client/client.js
+++ b/app/autolinker/client/client.js
@@ -7,6 +7,7 @@ import Autolinker from 'autolinker';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
+import { escapeRegExp } from '../../../client/lib/escapeRegExp';
let config;
@@ -33,7 +34,7 @@ const renderMessage = (message) => {
let msgParts;
let regexTokens;
if (message.tokens && message.tokens.length) {
- regexTokens = new RegExp(`(${ (message.tokens || []).map(({ token }) => RegExp.escape(token)) })`, 'g');
+ regexTokens = new RegExp(`(${ (message.tokens || []).map(({ token }) => escapeRegExp(token)) })`, 'g');
msgParts = message.html.split(regexTokens);
} else {
msgParts = [message.html];
diff --git a/app/autotranslate/client/lib/autotranslate.js b/app/autotranslate/client/lib/autotranslate.js
index 1ac9e7a6174e6..e9ef9d2124db4 100644
--- a/app/autotranslate/client/lib/autotranslate.js
+++ b/app/autotranslate/client/lib/autotranslate.js
@@ -6,7 +6,7 @@ import mem from 'mem';
import { Subscriptions, Messages } from '../../../models';
import { callbacks } from '../../../callbacks';
import { settings } from '../../../settings';
-import { hasAtLeastOnePermission } from '../../../authorization';
+import { hasAtLeastOnePermission, hasPermission } from '../../../authorization';
import { CachedCollectionManager } from '../../../ui-cached-collection';
let userLanguage = 'en';
@@ -60,12 +60,28 @@ export const AutoTranslate = {
},
init() {
- Meteor.call('autoTranslate.getSupportedLanguages', 'en', (err, languages) => {
- this.supportedLanguages = languages || [];
- });
+ this.supportedLanguages = [];
+ this.providersMetadata = {};
+
+ Tracker.autorun((c) => {
+ const uid = Meteor.userId();
+ if (!uid) {
+ return;
+ }
+
+ Meteor.call('autoTranslate.getProviderUiMetadata', (err, metadata) => {
+ this.providersMetadata = metadata;
+ });
+
+ if (!hasPermission('auto-translate')) {
+ return;
+ }
+
+ Meteor.call('autoTranslate.getSupportedLanguages', 'en', (err, languages) => {
+ this.supportedLanguages = languages || [];
+ });
- Meteor.call('autoTranslate.getProviderUiMetadata', (err, metadata) => {
- this.providersMetadata = metadata;
+ c.stop();
});
Tracker.autorun(() => {
diff --git a/app/cas/server/cas_rocketchat.js b/app/cas/server/cas_rocketchat.js
index 34d931cbf2105..c993e01df553f 100644
--- a/app/cas/server/cas_rocketchat.js
+++ b/app/cas/server/cas_rocketchat.js
@@ -13,6 +13,8 @@ Meteor.startup(function() {
this.add('CAS_login_url', '', { type: 'string', group: 'CAS', public: true });
this.add('CAS_version', '1.0', { type: 'select', values: [{ key: '1.0', i18nLabel: '1.0' }, { key: '2.0', i18nLabel: '2.0' }], group: 'CAS' });
this.add('CAS_trust_username', false, { type: 'boolean', group: 'CAS', public: true, i18nDescription: 'CAS_trust_username_description' });
+ // Enable/disable user creation
+ this.add('CAS_Creation_User_Enabled', true, { type: 'boolean', group: 'CAS' });
this.section('Attribute_handling', function() {
// Enable/disable sync
diff --git a/app/cas/server/cas_server.js b/app/cas/server/cas_server.js
index 549c7cc945985..646e87a8f0539 100644
--- a/app/cas/server/cas_server.js
+++ b/app/cas/server/cas_server.js
@@ -126,6 +126,7 @@ Accounts.registerLoginHandler(function(options) {
const sync_enabled = settings.get('CAS_Sync_User_Data_Enabled');
const trustUsername = settings.get('CAS_trust_username');
const verified = settings.get('Accounts_Verify_Email_For_External_Accounts');
+ const userCreationEnabled = settings.get('CAS_Creation_User_Enabled');
// We have these
const ext_attrs = {
@@ -206,7 +207,7 @@ Accounts.registerLoginHandler(function(options) {
Meteor.users.update(user, { $set: { emails: [{ address: int_attrs.email, verified }] } });
}
}
- } else {
+ } else if (userCreationEnabled) {
// Define new user
const newUser = {
username: result.username,
@@ -263,6 +264,11 @@ Accounts.registerLoginHandler(function(options) {
}
});
}
+ } else {
+ // Should fail as no user exist and can't be created
+ logger.debug(`User "${ result.username }" does not exist yet, will fail as no user creation is enabled`);
+ throw new Meteor.Error(Accounts.LoginCancelledError.numericError,
+ 'no matching user account found');
}
return { userId: user._id };
diff --git a/app/channel-settings/server/methods/saveRoomSettings.js b/app/channel-settings/server/methods/saveRoomSettings.js
index a5fbbbf8632a7..a418862d6fba5 100644
--- a/app/channel-settings/server/methods/saveRoomSettings.js
+++ b/app/channel-settings/server/methods/saveRoomSettings.js
@@ -104,6 +104,14 @@ const validators = {
});
}
},
+ roomAvatar({ userId, rid }) {
+ if (!hasPermission(userId, 'edit-room-avatar', rid)) {
+ throw new Meteor.Error('error-action-not-allowed', 'Editing a room avatar is not allowed', {
+ method: 'saveRoomSettings',
+ action: 'Editing_room',
+ });
+ }
+ },
};
const settingSavers = {
diff --git a/app/cloud/server/functions/connectWorkspace.js b/app/cloud/server/functions/connectWorkspace.js
index 17872bbce5840..6e428803dc0cb 100644
--- a/app/cloud/server/functions/connectWorkspace.js
+++ b/app/cloud/server/functions/connectWorkspace.js
@@ -13,6 +13,12 @@ export function connectWorkspace(token) {
Settings.updateValueById('Register_Server', true);
}
+ // shouldn't get here due to checking this on the method
+ // but this is just to double check
+ if (!token) {
+ return new Error('Invalid token; the registration token is required.');
+ }
+
const redirectUri = getRedirectUri();
const regInfo = {
diff --git a/app/cloud/server/methods.js b/app/cloud/server/methods.js
index 7e64e5d889dfa..7723566601f5a 100644
--- a/app/cloud/server/methods.js
+++ b/app/cloud/server/methods.js
@@ -70,6 +70,10 @@ Meteor.methods({
throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' });
}
+ if (!token) {
+ throw new Meteor.Error('error-invalid-payload', 'Token is required.', { method: 'cloud:connectServer' });
+ }
+
return connectWorkspace(token);
},
'cloud:disconnectWorkspace'() {
diff --git a/app/custom-oauth/server/custom_oauth_server.js b/app/custom-oauth/server/custom_oauth_server.js
index c37e43b620470..ad0f6b92c1be5 100644
--- a/app/custom-oauth/server/custom_oauth_server.js
+++ b/app/custom-oauth/server/custom_oauth_server.js
@@ -7,7 +7,7 @@ import { ServiceConfiguration } from 'meteor/service-configuration';
import _ from 'underscore';
import { normalizers, fromTemplate, renameInvalidProperties } from './transform_helpers';
-import { mapRolesFromSSO, updateRolesFromSSO } from './oauth_helpers';
+import { mapRolesFromSSO, mapSSOGroupsToChannels, updateRolesFromSSO } from './oauth_helpers';
import { Logger } from '../../logger';
import { Users } from '../../models';
import { isURL } from '../../utils/lib/isURL';
@@ -79,8 +79,21 @@ export class CustomOAuth {
this.avatarField = (options.avatarField || '').trim();
this.mergeUsers = options.mergeUsers;
this.mergeRoles = options.mergeRoles || false;
+ this.mapChannels = options.mapChannels || false;
this.rolesClaim = options.rolesClaim || 'roles';
+ this.groupsClaim = options.groupsClaim || 'groups';
this.accessTokenParam = options.accessTokenParam;
+ this.channelsAdmin = options.channelsAdmin || 'rocket.cat';
+
+ if (this.mapChannels) {
+ const channelsMap = (options.channelsMap || '{}').trim();
+ try {
+ this.channelsMap = JSON.parse(channelsMap);
+ } catch (err) {
+ logger.error(`Unexpected error : ${ err.message }`);
+ }
+ }
+
if (this.identityTokenSentVia == null || this.identityTokenSentVia === 'default') {
this.identityTokenSentVia = this.tokenSentVia;
@@ -330,6 +343,10 @@ export class CustomOAuth {
updateRolesFromSSO(user, serviceData, this.rolesClaim);
}
+ if (this.mapChannels) {
+ mapSSOGroupsToChannels(user, serviceData, this.groupsClaim, this.channelsMap, this.channelsAdmin);
+ }
+
// User already created or merged and has identical name as before
if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id && user.name === serviceData.name) {
return;
@@ -372,6 +389,10 @@ export class CustomOAuth {
user.roles = mapRolesFromSSO(user.services[this.name], this.rolesClaim);
}
+ if (this.mapChannels) {
+ mapSSOGroupsToChannels(user, user.services[this.name], this.groupsClaim, this.channelsMap, this.channelsAdmin);
+ }
+
return true;
});
}
diff --git a/app/custom-oauth/server/oauth_helpers.js b/app/custom-oauth/server/oauth_helpers.js
index 7b7a4abf9b08c..f00eea03b6b75 100644
--- a/app/custom-oauth/server/oauth_helpers.js
+++ b/app/custom-oauth/server/oauth_helpers.js
@@ -1,6 +1,9 @@
import { addUserRoles, removeUserFromRoles } from '../../authorization';
-import { Roles } from '../../models';
+import { Roles, Rooms } from '../../models';
+import { addUserToRoom, createRoom } from '../../lib/server/functions';
+import { Logger } from '../../logger';
+export const logger = new Logger('OAuth', {});
// Returns list of roles from SSO identity
export function mapRolesFromSSO(identity, roleClaimName) {
@@ -40,3 +43,31 @@ export function updateRolesFromSSO(user, identity, roleClaimName) {
});
}
}
+
+export function mapSSOGroupsToChannels(user, identity, groupClaimName, channelsMap, channelsAdmin) {
+ if (user && identity && groupClaimName) {
+ const groupsFromSSO = identity[groupClaimName] || [];
+
+ for (const ssoGroup in channelsMap) {
+ if (typeof ssoGroup === 'string') {
+ let channels = channelsMap[ssoGroup];
+ if (!Array.isArray(channels)) {
+ channels = [channels];
+ }
+ for (const channel of channels) {
+ let room = Rooms.findOneByNonValidatedName(channel);
+ if (!room) {
+ room = createRoom('c', channel, channelsAdmin, [], false);
+ if (!room || !room.rid) {
+ logger.error(`could not create channel ${ channel }`);
+ return;
+ }
+ }
+ if (Array.isArray(groupsFromSSO) && groupsFromSSO.includes(ssoGroup)) {
+ addUserToRoom(room._id, user);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/discussion/client/index.js b/app/discussion/client/index.js
index e8895fbe0f2ca..6d7642ac1b631 100644
--- a/app/discussion/client/index.js
+++ b/app/discussion/client/index.js
@@ -1,6 +1,5 @@
// Templates
import './views/creationDialog/CreateDiscussion';
-import './views/DiscussionList';
import './views/DiscussionTabbar';
// Other UI extensions
diff --git a/app/discussion/client/views/DiscussionList.html b/app/discussion/client/views/DiscussionList.html
deleted file mode 100644
index ebaeacb1e2f4d..0000000000000
--- a/app/discussion/client/views/DiscussionList.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
- {{#if shouldAppear}}
- {{#if rooms}}
-