From 031d99f801c1e8c9f5ee0fc7ab858e96b8b23d83 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 8 Apr 2024 19:27:10 -0300 Subject: [PATCH 01/85] chore: bump 7.0.0 --- apps/meteor/app/utils/rocketchat.info | 2 +- apps/meteor/package.json | 2 +- package.json | 2 +- packages/core-typings/package.json | 3 ++- packages/rest-typings/package.json | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/utils/rocketchat.info b/apps/meteor/app/utils/rocketchat.info index de3eb50fa5d18..34642c087e2e1 100644 --- a/apps/meteor/app/utils/rocketchat.info +++ b/apps/meteor/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "6.14.0-develop" + "version": "7.0.0-develop" } diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 3b9af8419230b..a2221374154f7 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/meteor", "description": "The Ultimate Open Source WebChat Platform", - "version": "6.14.0-develop", + "version": "7.0.0-develop", "private": true, "author": { "name": "Rocket.Chat", diff --git a/package.json b/package.json index 346b932166645..8079a03c805e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket.chat", - "version": "6.14.0-develop", + "version": "7.0.0-develop", "description": "Rocket.Chat Monorepo", "main": "index.js", "private": true, diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index f210ec03895a2..933a632b2e1ec 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -1,7 +1,8 @@ { "$schema": "https://json.schemastore.org/package", "name": "@rocket.chat/core-typings", - "version": "6.14.0-develop", + "private": true, + "version": "7.0.0-develop", "devDependencies": { "@rocket.chat/apps-engine": "workspace:^", "@rocket.chat/eslint-config": "workspace:^", diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json index 30753325cdf4e..255c524c97b0c 100644 --- a/packages/rest-typings/package.json +++ b/packages/rest-typings/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/rest-typings", - "version": "6.14.0-develop", + "version": "7.0.0-develop", "devDependencies": { "@rocket.chat/apps-engine": "workspace:^", "@rocket.chat/eslint-config": "workspace:~", From b2faa6a116a10437a49ca1e8291edaab63c42652 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 9 Apr 2024 13:47:21 -0300 Subject: [PATCH 02/85] fix!: api login should not suggest which credential is wrong (#32159) --- .changeset/fuzzy-cherries-buy.md | 7 +++++++ .../lib/server/lib/loginErrorMessageOverride.js | 14 -------------- .../lib/server/lib/loginErrorMessageOverride.ts | 16 ++++++++++++++++ .../client/meteorOverrides/login/google.ts | 10 ---------- .../externals/meteor/accounts-base.d.ts | 10 +++++++++- .../end-to-end/api/failed-login-attempts.ts | 2 +- 6 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 .changeset/fuzzy-cherries-buy.md delete mode 100644 apps/meteor/app/lib/server/lib/loginErrorMessageOverride.js create mode 100644 apps/meteor/app/lib/server/lib/loginErrorMessageOverride.ts diff --git a/.changeset/fuzzy-cherries-buy.md b/.changeset/fuzzy-cherries-buy.md new file mode 100644 index 0000000000000..e185a148c9172 --- /dev/null +++ b/.changeset/fuzzy-cherries-buy.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": major +--- + +Api login should not suggest which credential is wrong (password/username) + +Failed login attemps will always return `Unauthorized` instead of the internal fail reason diff --git a/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.js b/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.js deleted file mode 100644 index 4e054b81b2cf6..0000000000000 --- a/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.js +++ /dev/null @@ -1,14 +0,0 @@ -// Do not disclose if user exists when password is invalid -import { Accounts } from 'meteor/accounts-base'; -import { Meteor } from 'meteor/meteor'; - -const { _runLoginHandlers } = Accounts; -Accounts._runLoginHandlers = function (methodInvocation, options) { - const result = _runLoginHandlers.call(Accounts, methodInvocation, options); - - if (result.error && result.error.reason === 'Incorrect password') { - result.error = new Meteor.Error(403, 'User not found'); - } - - return result; -}; diff --git a/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.ts b/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.ts new file mode 100644 index 0000000000000..e2a6e0d105812 --- /dev/null +++ b/apps/meteor/app/lib/server/lib/loginErrorMessageOverride.ts @@ -0,0 +1,16 @@ +// Do not disclose if user exists when password is invalid +import { Accounts } from 'meteor/accounts-base'; +import { Meteor } from 'meteor/meteor'; + +const { _runLoginHandlers } = Accounts; + +Accounts._options.ambiguousErrorMessages = true; +Accounts._runLoginHandlers = async function (methodInvocation, options) { + const result = await _runLoginHandlers.call(Accounts, methodInvocation, options); + + if (result.error instanceof Meteor.Error) { + result.error = new Meteor.Error(401, 'User not found'); + } + + return result; +}; diff --git a/apps/meteor/client/meteorOverrides/login/google.ts b/apps/meteor/client/meteorOverrides/login/google.ts index 2742cade15d2c..4e99ac3a281b5 100644 --- a/apps/meteor/client/meteorOverrides/login/google.ts +++ b/apps/meteor/client/meteorOverrides/login/google.ts @@ -8,16 +8,6 @@ import { overrideLoginMethod, type LoginCallback } from '../../lib/2fa/overrideL import { wrapRequestCredentialFn } from '../../lib/wrapRequestCredentialFn'; import { createOAuthTotpLoginMethod } from './oauth'; -declare module 'meteor/accounts-base' { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Accounts { - export const _options: { - restrictCreationByEmailDomain?: string | (() => string); - forbidClientAccountCreation?: boolean | undefined; - }; - } -} - declare module 'meteor/meteor' { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Meteor { diff --git a/apps/meteor/definition/externals/meteor/accounts-base.d.ts b/apps/meteor/definition/externals/meteor/accounts-base.d.ts index 0d30eed0430de..29445a5a0218a 100644 --- a/apps/meteor/definition/externals/meteor/accounts-base.d.ts +++ b/apps/meteor/definition/externals/meteor/accounts-base.d.ts @@ -23,7 +23,7 @@ declare module 'meteor/accounts-base' { function _insertLoginToken(userId: string, token: { token: string; when: Date }): void; - function _runLoginHandlers(methodInvocation: T, loginRequest: Record): LoginMethodResult | undefined; + function _runLoginHandlers(methodInvocation: T, loginRequest: Record): Promise; function registerLoginHandler(name: string, handler: (options: any) => undefined | object): void; @@ -57,6 +57,14 @@ declare module 'meteor/accounts-base' { const _accountData: Record; + interface AccountsServerOptions { + ambiguousErrorMessages?: boolean; + restrictCreationByEmailDomain?: string | (() => string); + forbidClientAccountCreation?: boolean | undefined; + } + + export const _options: AccountsServerOptions; + // eslint-disable-next-line @typescript-eslint/no-namespace namespace oauth { function credentialRequestCompleteHandler( diff --git a/apps/meteor/tests/end-to-end/api/failed-login-attempts.ts b/apps/meteor/tests/end-to-end/api/failed-login-attempts.ts index ee0236c591b6a..ebdc1737391b3 100644 --- a/apps/meteor/tests/end-to-end/api/failed-login-attempts.ts +++ b/apps/meteor/tests/end-to-end/api/failed-login-attempts.ts @@ -54,7 +54,7 @@ describe('[Failed Login Attempts]', () => { .expect(401) .expect((res) => { expect(res.body).to.have.property('status', 'error'); - expect(res.body).to.have.property('message', 'Incorrect password'); + expect(res.body).to.have.property('message', 'Unauthorized'); }); } From 216cba13c26182ac5ef50b1ee4eafd9a312ffdf1 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:00:50 -0300 Subject: [PATCH 03/85] chore!: remove hipchat importer (#32154) --- .changeset/quiet-kings-rhyme.md | 5 + FEATURES.md | 1 - .../server/HipChatEnterpriseImporter.js | 348 ------------------ .../server/index.ts | 8 - .../server/classes/ImportDataConverter.ts | 7 - .../server/classes/VirtualDataConverter.ts | 10 - apps/meteor/package.json | 1 - apps/meteor/server/importPackages.ts | 1 - apps/meteor/server/models/raw/ImportData.ts | 13 +- packages/i18n/src/locales/af.i18n.json | 2 - packages/i18n/src/locales/ar.i18n.json | 2 - packages/i18n/src/locales/az.i18n.json | 2 - packages/i18n/src/locales/be-BY.i18n.json | 2 - packages/i18n/src/locales/bg.i18n.json | 2 - packages/i18n/src/locales/bs.i18n.json | 2 - packages/i18n/src/locales/ca.i18n.json | 2 - packages/i18n/src/locales/cs.i18n.json | 2 - packages/i18n/src/locales/cy.i18n.json | 2 - packages/i18n/src/locales/da.i18n.json | 2 - packages/i18n/src/locales/de-AT.i18n.json | 2 - packages/i18n/src/locales/de-IN.i18n.json | 2 - packages/i18n/src/locales/de.i18n.json | 2 - packages/i18n/src/locales/el.i18n.json | 2 - packages/i18n/src/locales/en.i18n.json | 3 - packages/i18n/src/locales/eo.i18n.json | 2 - packages/i18n/src/locales/es.i18n.json | 2 - packages/i18n/src/locales/fa.i18n.json | 2 - packages/i18n/src/locales/fi.i18n.json | 2 - packages/i18n/src/locales/fr.i18n.json | 2 - packages/i18n/src/locales/hi-IN.i18n.json | 2 - packages/i18n/src/locales/hr.i18n.json | 2 - packages/i18n/src/locales/hu.i18n.json | 2 - packages/i18n/src/locales/id.i18n.json | 2 - packages/i18n/src/locales/it.i18n.json | 2 - packages/i18n/src/locales/ja.i18n.json | 2 - packages/i18n/src/locales/ka-GE.i18n.json | 2 - packages/i18n/src/locales/km.i18n.json | 2 - packages/i18n/src/locales/ko.i18n.json | 2 - packages/i18n/src/locales/ku.i18n.json | 2 - packages/i18n/src/locales/lo.i18n.json | 2 - packages/i18n/src/locales/lt.i18n.json | 2 - packages/i18n/src/locales/lv.i18n.json | 2 - packages/i18n/src/locales/mn.i18n.json | 2 - packages/i18n/src/locales/ms-MY.i18n.json | 2 - packages/i18n/src/locales/nl.i18n.json | 2 - packages/i18n/src/locales/nn.i18n.json | 2 - packages/i18n/src/locales/no.i18n.json | 2 - packages/i18n/src/locales/pl.i18n.json | 2 - packages/i18n/src/locales/pt-BR.i18n.json | 2 - packages/i18n/src/locales/pt.i18n.json | 2 - packages/i18n/src/locales/ro.i18n.json | 2 - packages/i18n/src/locales/ru.i18n.json | 2 - packages/i18n/src/locales/se.i18n.json | 3 - packages/i18n/src/locales/sk-SK.i18n.json | 2 - packages/i18n/src/locales/sl-SI.i18n.json | 2 - packages/i18n/src/locales/sq.i18n.json | 2 - packages/i18n/src/locales/sr.i18n.json | 2 - packages/i18n/src/locales/sv.i18n.json | 2 - packages/i18n/src/locales/ta-IN.i18n.json | 2 - packages/i18n/src/locales/th-TH.i18n.json | 2 - packages/i18n/src/locales/tr.i18n.json | 2 - packages/i18n/src/locales/uk.i18n.json | 2 - packages/i18n/src/locales/vi-VN.i18n.json | 2 - packages/i18n/src/locales/zh-HK.i18n.json | 2 - packages/i18n/src/locales/zh-TW.i18n.json | 2 - packages/i18n/src/locales/zh.i18n.json | 2 - .../src/models/IImportDataModel.ts | 1 - yarn.lock | 3 +- 68 files changed, 7 insertions(+), 507 deletions(-) create mode 100644 .changeset/quiet-kings-rhyme.md delete mode 100644 apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js delete mode 100644 apps/meteor/app/importer-hipchat-enterprise/server/index.ts diff --git a/.changeset/quiet-kings-rhyme.md b/.changeset/quiet-kings-rhyme.md new file mode 100644 index 0000000000000..0fd7fda7ea66c --- /dev/null +++ b/.changeset/quiet-kings-rhyme.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removed the ability to import data in the HipChat Enterprise format, as it was discontinued over five years ago. diff --git a/FEATURES.md b/FEATURES.md index 5601cc0c7cccb..67848389b593b 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -33,7 +33,6 @@ - Incoming / Outgoing Webhooks - Data Importer - Import from Slack - - Import from Hipchat - Slack Bridge - Profiles - Custom avatars diff --git a/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js b/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js deleted file mode 100644 index 663300e44154e..0000000000000 --- a/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js +++ /dev/null @@ -1,348 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Readable } from 'stream'; - -import { Settings } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { Importer, ProgressStep } from '../../importer/server'; -import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; - -/** @deprecated HipChat was discontinued at 2019-02-15 */ -export class HipChatEnterpriseImporter extends Importer { - constructor(info, importRecord, converterOptions = {}) { - super(info, importRecord, converterOptions); - - this.Readable = Readable; - this.zlib = require('zlib'); - this.tarStream = require('tar-stream'); - this.extract = this.tarStream.extract(); - this.path = path; - } - - parseData(data) { - const dataString = data.toString(); - try { - this.logger.debug('parsing file contents'); - return JSON.parse(dataString); - } catch (e) { - this.logger.error(e); - return false; - } - } - - async prepareUsersFile(file) { - await super.updateProgress(ProgressStep.PREPARING_USERS); - let count = 0; - - for (const u of file) { - const newUser = { - emails: [], - importIds: [String(u.User.id)], - username: u.User.mention_name, - name: u.User.name, - avatarUrl: u.User.avatar && `data:image/png;base64,${u.User.avatar.replace(/\n/g, '')}`, - bio: u.User.title || undefined, - deleted: u.User.is_deleted, - type: 'user', - }; - count++; - - if (u.User.email) { - newUser.emails.push(u.User.email); - } - - this.converter.addUser(newUser); - } - - const { value } = await Settings.incrementValueById('Hipchat_Enterprise_Importer_Count', count, { returnDocument: 'after' }); - if (value) { - void notifyOnSettingChanged(value); - } - - await super.updateRecord({ 'count.users': count }); - await super.addCountToTotal(count); - } - - async prepareRoomsFile(file) { - await super.updateProgress(ProgressStep.PREPARING_CHANNELS); - let count = 0; - - for await (const r of file) { - await this.converter.addChannel({ - u: { - _id: r.Room.owner, - }, - importIds: [String(r.Room.id)], - name: r.Room.name, - users: r.Room.members, - t: r.Room.privacy === 'private' ? 'p' : 'c', - topic: r.Room.topic, - ts: new Date(r.Room.created), - archived: r.Room.is_archived, - }); - - count++; - } - - await super.updateRecord({ 'count.channels': count }); - await super.addCountToTotal(count); - } - - async prepareUserMessagesFile(file) { - this.logger.debug(`preparing room with ${file.length} messages `); - let count = 0; - const dmRooms = []; - - for await (const m of file) { - if (!m.PrivateUserMessage) { - continue; - } - - // If the message id is already on the list, skip it - if (this.preparedMessages[m.PrivateUserMessage.id] !== undefined) { - continue; - } - this.preparedMessages[m.PrivateUserMessage.id] = true; - - const senderId = String(m.PrivateUserMessage.sender.id); - const receiverId = String(m.PrivateUserMessage.receiver.id); - const users = [senderId, receiverId].sort(); - - if (!dmRooms[receiverId]) { - dmRooms[receiverId] = await this.converter.findDMForImportedUsers(senderId, receiverId); - - if (!dmRooms[receiverId]) { - const room = { - importIds: [users.join('')], - users, - t: 'd', - ts: new Date(m.PrivateUserMessage.timestamp.split(' ')[0]), - }; - await this.converter.addChannel(room); - dmRooms[receiverId] = room; - } - } - - const rid = dmRooms[receiverId].importIds[0]; - const newMessage = this.convertImportedMessage(m.PrivateUserMessage, rid, 'private'); - count++; - await this.converter.addMessage(newMessage); - } - - return count; - } - - async loadTurndownService() { - const TurndownService = (await import('turndown')).default; - - const turndownService = new TurndownService({ - strongDelimiter: '*', - hr: '', - br: '\n', - }); - - turndownService.addRule('strikethrough', { - filter: 'img', - - replacement(content, node) { - const src = node.getAttribute('src') || ''; - const alt = node.alt || node.title || src; - return src ? `[${alt}](${src})` : ''; - }, - }); - - this.turndownService = turndownService; - - return turndownService; - } - - convertImportedMessage(importedMessage, rid, type) { - const idType = type === 'private' ? type : `${rid}-${type}`; - const newId = `hipchatenterprise-${idType}-${importedMessage.id}`; - - const newMessage = { - _id: newId, - rid, - ts: new Date(importedMessage.timestamp.split(' ')[0]), - u: { - _id: String(importedMessage.sender.id), - }, - }; - - const text = importedMessage.message; - - if (importedMessage.message_format === 'html') { - newMessage.msg = this.turndownService.turndown(text); - } else if (text.startsWith('/me ')) { - newMessage.msg = `${text.replace(/\/me /, '_')}_`; - } else { - newMessage.msg = text; - } - - if (importedMessage.attachment?.url) { - const fileId = `${importedMessage.id}-${importedMessage.attachment.name || 'attachment'}`; - - newMessage._importFile = { - downloadUrl: importedMessage.attachment.url, - id: `${fileId}`, - size: importedMessage.attachment.size || 0, - name: importedMessage.attachment.name, - external: false, - source: 'hipchat-enterprise', - original: { - ...importedMessage.attachment, - }, - }; - } - - return newMessage; - } - - async prepareRoomMessagesFile(file, rid) { - this.logger.debug(`preparing room with ${file.length} messages `); - let count = 0; - - await this.loadTurndownService(); - - for await (const m of file) { - if (m.UserMessage) { - const newMessage = this.convertImportedMessage(m.UserMessage, rid, 'user'); - await this.converter.addMessage(newMessage); - count++; - } else if (m.NotificationMessage) { - const newMessage = this.convertImportedMessage(m.NotificationMessage, rid, 'notif'); - newMessage.u._id = 'rocket.cat'; - newMessage.alias = m.NotificationMessage.sender; - - await this.converter.addMessage(newMessage); - count++; - } else if (m.TopicRoomMessage) { - const newMessage = this.convertImportedMessage(m.TopicRoomMessage, rid, 'topic'); - newMessage.t = 'room_changed_topic'; - - await this.converter.addMessage(newMessage); - count++; - } else if (m.ArchiveRoomMessage) { - this.logger.warn('Archived Room Notification was ignored.'); - } else if (m.GuestAccessMessage) { - this.logger.warn('Guess Access Notification was ignored.'); - } else { - this.logger.error({ msg: "HipChat Enterprise importer isn't configured to handle this message:", file: m }); - } - } - - return count; - } - - async prepareMessagesFile(file, info) { - await super.updateProgress(ProgressStep.PREPARING_MESSAGES); - - const [type, id] = info.dir.split('/'); - const roomIdentifier = `${type}/${id}`; - - await super.updateRecord({ messagesstatus: roomIdentifier }); - - switch (type) { - case 'users': - return this.prepareUserMessagesFile(file); - case 'rooms': - return this.prepareRoomMessagesFile(file, id); - default: - this.logger.error(`HipChat Enterprise importer isn't configured to handle "${type}" files (${info.dir}).`); - return 0; - } - } - - async prepareFile(info, data, fileName) { - const file = this.parseData(data); - if (file === false) { - this.logger.error('failed to parse data'); - return false; - } - - switch (info.base) { - case 'users.json': - await this.prepareUsersFile(file); - break; - case 'rooms.json': - await this.prepareRoomsFile(file); - break; - case 'history.json': - return this.prepareMessagesFile(file, info); - case 'emoticons.json': - case 'metadata.json': - break; - default: - this.logger.error(`HipChat Enterprise importer doesn't know what to do with the file "${fileName}"`); - break; - } - - return 0; - } - - async prepareUsingLocalFile(fullFilePath) { - this.logger.debug('start preparing import operation'); - await this.converter.clearImportData(); - - // HipChat duplicates direct messages (one for each user) - // This object will keep track of messages that have already been prepared so it doesn't try to do it twice - this.preparedMessages = {}; - let messageCount = 0; - - const promise = new Promise((resolve, reject) => { - this.extract.on('entry', (header, stream, next) => { - this.logger.debug(`new entry from import file: ${header.name}`); - if (!header.name.endsWith('.json')) { - stream.resume(); - return next(); - } - - const info = this.path.parse(header.name); - let pos = 0; - let data = Buffer.allocUnsafe(header.size); - - stream.on('data', (chunk) => { - data.fill(chunk, pos, pos + chunk.length); - pos += chunk.length; - }); - - stream.on('end', async () => { - this.logger.info(`Processing the file: ${header.name}`); - const newMessageCount = await this.prepareFile(info, data, header.name); - - messageCount += newMessageCount; - await super.updateRecord({ 'count.messages': messageCount }); - await super.addCountToTotal(newMessageCount); - - data = undefined; - - this.logger.debug('next import entry'); - next(); - }); - - stream.on('error', () => next()); - stream.resume(); - }); - - this.extract.on('error', (err) => { - this.logger.error({ msg: 'extract error:', err }); - reject(new Meteor.Error('error-import-file-extract-error')); - }); - - this.extract.on('finish', resolve); - - const rs = fs.createReadStream(fullFilePath); - const gunzip = this.zlib.createGunzip(); - - gunzip.on('error', (err) => { - this.logger.error({ msg: 'extract error:', err }); - reject(new Meteor.Error('error-import-file-extract-error')); - }); - this.logger.debug('start extracting import file'); - rs.pipe(gunzip).pipe(this.extract); - }); - - return promise; - } -} diff --git a/apps/meteor/app/importer-hipchat-enterprise/server/index.ts b/apps/meteor/app/importer-hipchat-enterprise/server/index.ts deleted file mode 100644 index e50a9b9c4bd39..0000000000000 --- a/apps/meteor/app/importer-hipchat-enterprise/server/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Importers } from '../../importer/server'; -import { HipChatEnterpriseImporter } from './HipChatEnterpriseImporter'; - -Importers.add({ - key: 'hipchatenterprise', - name: 'HipChat (tar.gz)', - importer: HipChatEnterpriseImporter, -}); diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index 6de47e33b2b62..35e151538c6c8 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -839,13 +839,6 @@ export class ImportDataConverter { await this.updateRoomId(room._id, roomData); } - public async findDMForImportedUsers(...users: Array): Promise { - const record = await ImportData.findDMForImportedUsers(...users); - if (record) { - return record.data; - } - } - async findImportedRoomId(importId: string): Promise { if (this._roomCache.has(importId)) { return this._roomCache.get(importId) as string; diff --git a/apps/meteor/app/importer/server/classes/VirtualDataConverter.ts b/apps/meteor/app/importer/server/classes/VirtualDataConverter.ts index ef850226be5ce..8ca9f3b5894c7 100644 --- a/apps/meteor/app/importer/server/classes/VirtualDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/VirtualDataConverter.ts @@ -6,7 +6,6 @@ import type { IImportRecord, IImportRecordType, IImportData, - IImportChannel, } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; @@ -47,15 +46,6 @@ export class VirtualDataConverter extends ImportDataConverter { this.clearVirtualData(); } - public async findDMForImportedUsers(...users: Array): Promise { - if (!this.useVirtual) { - return super.findDMForImportedUsers(...users); - } - - // The original method is only used by the hipchat importer so we probably don't need to implement this on the virtual converter. - return undefined; - } - public addUserSync(data: IImportUser, options?: Record): void { return this.addObjectSync('user', data, options); } diff --git a/apps/meteor/package.json b/apps/meteor/package.json index a2221374154f7..1581d0e1d15e1 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -427,7 +427,6 @@ "string-strip-html": "^7.0.3", "suretype": "~2.4.1", "swiper": "^9.4.1", - "tar-stream": "^1.6.2", "textarea-caret": "^3.1.0", "tinykeys": "^1.4.0", "turndown": "^7.2.0", diff --git a/apps/meteor/server/importPackages.ts b/apps/meteor/server/importPackages.ts index 2b4e3106ed45f..dfda85e52a9e6 100644 --- a/apps/meteor/server/importPackages.ts +++ b/apps/meteor/server/importPackages.ts @@ -26,7 +26,6 @@ import '../app/google-oauth/server'; import '../app/iframe-login/server'; import '../app/importer/server'; import '../app/importer-csv/server'; -import '../app/importer-hipchat-enterprise/server'; import '../app/importer-pending-files/server'; import '../app/importer-pending-avatars/server'; import '../app/importer-slack/server'; diff --git a/apps/meteor/server/models/raw/ImportData.ts b/apps/meteor/server/models/raw/ImportData.ts index e38670662a3f6..19ec573fa239f 100644 --- a/apps/meteor/server/models/raw/ImportData.ts +++ b/apps/meteor/server/models/raw/ImportData.ts @@ -6,7 +6,7 @@ import type { RocketChatRecordDeleted, } from '@rocket.chat/core-typings'; import type { IImportDataModel } from '@rocket.chat/model-typings'; -import type { Collection, FindCursor, Db, Filter, IndexDescription } from 'mongodb'; +import type { Collection, FindCursor, Db, IndexDescription } from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -102,15 +102,4 @@ export class ImportDataRaw extends BaseRaw implements IImportData return channel?.data?.importIds?.shift(); } - - findDMForImportedUsers(...users: Array): Promise { - const query: Filter = { - 'dataType': 'channel', - 'data.users': { - $all: users, - }, - }; - - return this.findOne(query); - } } diff --git a/packages/i18n/src/locales/af.i18n.json b/packages/i18n/src/locales/af.i18n.json index 661360c109b2d..ecd47b5d96df8 100644 --- a/packages/i18n/src/locales/af.i18n.json +++ b/packages/i18n/src/locales/af.i18n.json @@ -1271,8 +1271,6 @@ "Importer_done": "Invoer voltooi!", "Importer_finishing": "Voltooi die invoer.", "Importer_From_Description": "Invoer {{from}} data in Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Wees asseblief bewus daarvan dat hierdie invoer nog steeds 'n werk aan die gang is, meld asseblief enige foute wat in GitHub voorkom:", - "Importer_HipChatEnterprise_Information": "Die opgelaaide lêer moet 'n gedecodeerde tar.gz wees, lees asseblief die dokumentasie vir verdere inligting:", "Importer_import_cancelled": "Invoer gekanselleer.", "Importer_import_failed": "'N Fout het voorgekom terwyl die invoer uitgevoer word.", "Importer_importing_channels": "Die kanale invoer.", diff --git a/packages/i18n/src/locales/ar.i18n.json b/packages/i18n/src/locales/ar.i18n.json index 0b8667bad64af..d78474c2429ae 100644 --- a/packages/i18n/src/locales/ar.i18n.json +++ b/packages/i18n/src/locales/ar.i18n.json @@ -2143,8 +2143,6 @@ "Importer_finishing": "يتم إنهاء الاستيراد.", "Importer_From_Description": "استيراد بيانات {{from}} إلى Rocket.Chat.", "Importer_From_Description_CSV": "استيراد بيانات CSV إلى Rocket.Chat. يجب أن يكون الملف الذي تم رفعه بتنسيق ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "يرجى العلم أن هذا الاستيراد لا يزال قيد التقدم، يُرجى الإبلاغ عن أي أخطاء تحدث في GitHub:", - "Importer_HipChatEnterprise_Information": "يجب أن يكون الملف الذي تم تحميله بتنسيق tar.gz غير مشفر، تُرجى قراءة الوثائق للحصول على مزيد من المعلومات:", "Importer_import_cancelled": "تم إلغاء الاستيراد.", "Importer_import_failed": "حدث خطأ أثناء تشغيل الاستيراد.", "Importer_importing_channels": "يتم استيراد القنوات.", diff --git a/packages/i18n/src/locales/az.i18n.json b/packages/i18n/src/locales/az.i18n.json index 44e9e412a2df2..7117c9fe19222 100644 --- a/packages/i18n/src/locales/az.i18n.json +++ b/packages/i18n/src/locales/az.i18n.json @@ -1271,8 +1271,6 @@ "Importer_done": "Tamamilə idxal!", "Importer_finishing": "İthalatı bitirmək.", "Importer_From_Description": "Rocket.Chat'a {{from}} data verin.", - "Importer_HipChatEnterprise_BetaWarning": "Xahiş edirik, bu idxal hələ də davam edən bir iş olduğundan xəbərdar edin, GitHub'da meydana gələn hər hansı bir səhv bildirin:", - "Importer_HipChatEnterprise_Information": "Yüklənən fayl şifresi çözülmüş tar.gz olmalıdır, daha ətraflı məlumat üçün sənədləri oxuyun:", "Importer_import_cancelled": "İxrac ləğv edildi.", "Importer_import_failed": "İçeceğinizde çalışırken bir səhv baş verdi.", "Importer_importing_channels": "Kanalları idxal etmək.", diff --git a/packages/i18n/src/locales/be-BY.i18n.json b/packages/i18n/src/locales/be-BY.i18n.json index 26d9ea55e1c2a..9700320e06b5c 100644 --- a/packages/i18n/src/locales/be-BY.i18n.json +++ b/packages/i18n/src/locales/be-BY.i18n.json @@ -1287,8 +1287,6 @@ "Importer_done": "Імпарт завершаны!", "Importer_finishing": "Завяршэнне імпарту.", "Importer_From_Description": "Імпартуе {{from}} дадзеныя ў Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Звярніце ўвагу, што гэты імпарт па-ранейшаму знаходзіцца ў стадыі распрацоўкі, просьба паведамляць пра памылкі, якія адбываюцца ў GitHub:", - "Importer_HipChatEnterprise_Information": "Загружаны файл павінен быць расшыфраваны tar.gz, калі ласка, прачытайце дакументацыю для атрымання дадатковай інфармацыі:", "Importer_import_cancelled": "Імпарт адменены.", "Importer_import_failed": "пры выкананні імпарту адбылася памылка.", "Importer_importing_channels": "Імпарт каналаў.", diff --git a/packages/i18n/src/locales/bg.i18n.json b/packages/i18n/src/locales/bg.i18n.json index c779a93f1efa8..92b08e2f4171c 100644 --- a/packages/i18n/src/locales/bg.i18n.json +++ b/packages/i18n/src/locales/bg.i18n.json @@ -1269,8 +1269,6 @@ "Importer_CSV_Information": "CSV вносителят изисква специален формат, прочетете документацията за това как да структурирате файла с цип:", "Importer_done": "Импортирането е завършено!", "Importer_finishing": "Завършване на импортирането.", - "Importer_HipChatEnterprise_BetaWarning": "Имайте предвид, че този внос все още е в процес на разработка, моля, отчетете всички грешки, които възникват в GitHub:", - "Importer_HipChatEnterprise_Information": "Каченият файл трябва да е декриптиран tar.gz, моля прочетете документацията за допълнителна информация:", "Importer_import_cancelled": "Импортирането бе отменено.", "Importer_import_failed": "Възникна грешка при изпълнение на импортирането.", "Importer_importing_channels": "Импортиране на каналите.", diff --git a/packages/i18n/src/locales/bs.i18n.json b/packages/i18n/src/locales/bs.i18n.json index 8fdafb52ed6ef..ece277df928ac 100644 --- a/packages/i18n/src/locales/bs.i18n.json +++ b/packages/i18n/src/locales/bs.i18n.json @@ -1267,8 +1267,6 @@ "Importer_done": "Uvoz dovršen!", "Importer_finishing": "Završavanje uvoza.", "Importer_From_Description": "Uvezi {{from}} podatke u Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Imajte na umu da je taj uvoz još uvijek u tijeku, prijavite sve pogreške koje se pojavljuju u GitHubu:", - "Importer_HipChatEnterprise_Information": "Prenesena datoteka mora biti dekriptirana tar.gz, pročitajte dokumentaciju za daljnje informacije:", "Importer_import_cancelled": "Uvoz je otkazan.", "Importer_import_failed": "Došlo je do pogreške pri izvršavanju uvoza.", "Importer_importing_channels": "Prebacivanje kanala.", diff --git a/packages/i18n/src/locales/ca.i18n.json b/packages/i18n/src/locales/ca.i18n.json index c9603ff976e06..a9f616759b98a 100644 --- a/packages/i18n/src/locales/ca.i18n.json +++ b/packages/i18n/src/locales/ca.i18n.json @@ -2111,8 +2111,6 @@ "Importer_finishing": "Finalitza la importació.", "Importer_From_Description": "Importa les dades de {{from}} a Rocket.Chat.", "Importer_From_Description_CSV": "Importa dades CSV a Rocket.Chat. El fitxer penjat ha de ser un fitxer ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Tingueu en compte que aquest sistema d'importació encara està en desenvolupament. Si us plau, notifiqueu-nos a GitHub els errors que es produeixin:", - "Importer_HipChatEnterprise_Information": "L'arxiu carregat ha de ser un tar.gz desxifrat, llegiu la documentació per obtenir més informació:", "Importer_import_cancelled": "Importació cancel·lada.", "Importer_import_failed": "S'ha produït un error durant la importació.", "Importer_importing_channels": "Important els canals.", diff --git a/packages/i18n/src/locales/cs.i18n.json b/packages/i18n/src/locales/cs.i18n.json index 0c9fbd9ea3c16..60ffac0aebef0 100644 --- a/packages/i18n/src/locales/cs.i18n.json +++ b/packages/i18n/src/locales/cs.i18n.json @@ -1814,8 +1814,6 @@ "Importer_ExternalUrl_Description": "Můžete také použít adresu URL pro veřejně přístupný soubor:", "Importer_finishing": "Dokončuji import.", "Importer_From_Description": "Import dat {{from}} do Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Mějte prosím na paměti, že tato funkcionalita je stále ve vývoji, prosím nahlašte nám jakékoliv chyby přes github:", - "Importer_HipChatEnterprise_Information": "Nahraný soubor musí být nešifrovaný tar.gz, Více informací naleznete v dokumentaci:", "Importer_import_cancelled": "Import zrušen.", "Importer_import_failed": "Došlo k chybě při importu.", "Importer_importing_channels": "Importuji místnosti.", diff --git a/packages/i18n/src/locales/cy.i18n.json b/packages/i18n/src/locales/cy.i18n.json index 0c188e3575dd1..c8e3efc5f7a61 100644 --- a/packages/i18n/src/locales/cy.i18n.json +++ b/packages/i18n/src/locales/cy.i18n.json @@ -1267,8 +1267,6 @@ "Importer_done": "Mewnforio yn gyflawn!", "Importer_finishing": "Gorffen y mewnforio.", "Importer_From_Description": "Mewnforion {{from}} data i Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Cofiwch fod y mewnforio hwn yn dal i fod yn waith ar y gweill, rhowch wybod am unrhyw wallau sy'n digwydd yn GitHub:", - "Importer_HipChatEnterprise_Information": "Rhaid i'r ffeil a lwythir i fyny fod yn tar.gz dadgryptiedig, darllenwch y dogfennau i gael rhagor o wybodaeth:", "Importer_import_cancelled": "Canslo mewnforio.", "Importer_import_failed": "Digwyddodd gwall wrth redeg y mewnforio.", "Importer_importing_channels": "Mewnforio y sianeli.", diff --git a/packages/i18n/src/locales/da.i18n.json b/packages/i18n/src/locales/da.i18n.json index dcd6f262bbca9..af2cdc5893acd 100644 --- a/packages/i18n/src/locales/da.i18n.json +++ b/packages/i18n/src/locales/da.i18n.json @@ -1906,8 +1906,6 @@ "Importer_ExternalUrl_Description": "Du kan også bruge en URL til en offentlig tilgængelig fil:", "Importer_finishing": "Afslutter importen.", "Importer_From_Description": "Importerer {{from}} data til Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Vær opmærksom på, at denne import stadig er under udvikling. Rapporter venligst eventuelle fejl der opstår til GitHub:", - "Importer_HipChatEnterprise_Information": "Den uploadede fil skal være en dekrypteret tar.gz. Du kan få mere at vide i dokumentationen:", "Importer_import_cancelled": "Import annulleret.", "Importer_import_failed": "Der opstod en fejl under udførelsen af importen.", "Importer_importing_channels": "Importerer kanalerne.", diff --git a/packages/i18n/src/locales/de-AT.i18n.json b/packages/i18n/src/locales/de-AT.i18n.json index 3c9fc7236c14a..b45c4db185ea9 100644 --- a/packages/i18n/src/locales/de-AT.i18n.json +++ b/packages/i18n/src/locales/de-AT.i18n.json @@ -1273,8 +1273,6 @@ "Importer_done": "Die Daten wurden erfolgreich importiert!", "Importer_finishing": "Import abgeschlossen.", "Importer_From_Description": "Importiert Daten von {{from}} nach Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Bitte beachten Sie, dass dieser Import noch in Arbeit ist. Bitte melden Sie alle Fehler, die in GitHub auftreten:", - "Importer_HipChatEnterprise_Information": "Die hochgeladene Datei muss eine entschlüsselte tar.gz sein. Bitte lesen Sie die Dokumentation für weitere Informationen:", "Importer_import_cancelled": "Der Import wurde abgebrochen.", "Importer_import_failed": "Während des Importierens ist ein Fehler aufgetreten.", "Importer_importing_channels": "Importiere die Kanäle.", diff --git a/packages/i18n/src/locales/de-IN.i18n.json b/packages/i18n/src/locales/de-IN.i18n.json index edccd128e3149..94949713940ae 100644 --- a/packages/i18n/src/locales/de-IN.i18n.json +++ b/packages/i18n/src/locales/de-IN.i18n.json @@ -1463,8 +1463,6 @@ "Importer_ExternalUrl_Description": "Du kannst eine öffentlich erreichbare URL zur Datei angeben.", "Importer_finishing": "Import abgeschlossen.", "Importer_From_Description": "Importiert Daten von {{from}} nach Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Bitte beachte, dass sich dieser Importer noch in der Entwicklung befindet. Bitte melde Fehler auf GitHub: ", - "Importer_HipChatEnterprise_Information": "Die hochgeladene Datei muss ein nicht-verschlüsseltes tar.gz sein. Bitte lies die Dokumentation für weiterführende Informationen.", "Importer_import_cancelled": "Der Import wurde abgebrochen.", "Importer_import_failed": "Während des Importierens ist ein Fehler aufgetreten.", "Importer_importing_channels": "Importiere die Kanäle.", diff --git a/packages/i18n/src/locales/de.i18n.json b/packages/i18n/src/locales/de.i18n.json index 87a2cb04196b6..7d83471fc3d49 100644 --- a/packages/i18n/src/locales/de.i18n.json +++ b/packages/i18n/src/locales/de.i18n.json @@ -2389,8 +2389,6 @@ "Importer_finishing": "Import abgeschlossen.", "Importer_From_Description": "Importiert Daten von {{from}} nach Rocket.Chat.", "Importer_From_Description_CSV": "Importiert CSV-Daten in Rocket.Chat. Die hochgeladene Fatei muss eine ZIP-Datei sein.", - "Importer_HipChatEnterprise_BetaWarning": "Bitte beachten Sie, dass sich dieser Importer noch in der Entwicklung befindet. Bitte berichten Sie über Fehler auf GitHub: ", - "Importer_HipChatEnterprise_Information": "Die hochgeladene Datei muss ein nicht verschlüsseltes tar.gz sein. Bitte lesen Sie die Dokumentation für weiterführende Informationen:", "Importer_import_cancelled": "Der Import wurde abgebrochen.", "Importer_import_failed": "Während des Importierens ist ein Fehler aufgetreten.", "Importer_importing_channels": "Importiere die Kanäle.", diff --git a/packages/i18n/src/locales/el.i18n.json b/packages/i18n/src/locales/el.i18n.json index 437657eda5027..f0da1f90ba5f2 100644 --- a/packages/i18n/src/locales/el.i18n.json +++ b/packages/i18n/src/locales/el.i18n.json @@ -1278,8 +1278,6 @@ "Importer_done": "Εισαγωγή πλήρης!", "Importer_finishing": "Ολοκλήρωση της εισαγωγής.", "Importer_From_Description": "Οι εισαγωγές {{from}} δεδομένων's σε Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Λάβετε υπόψη ότι αυτή η εισαγωγή εξακολουθεί να είναι εργασία σε εξέλιξη, αναφέρετε τυχόν λάθη που εμφανίζονται στο GitHub:", - "Importer_HipChatEnterprise_Information": "Το φορτωμένο αρχείο πρέπει να είναι αποκρυπτογραφημένο tar.gz, διαβάστε την τεκμηρίωση για περισσότερες πληροφορίες:", "Importer_import_cancelled": "Εισαγωγής ακυρωθεί.", "Importer_import_failed": "Παρουσιάστηκε σφάλμα κατά την εκτέλεση της εισαγωγής.", "Importer_importing_channels": "Εισάγει τα κανάλια.", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index b89b760caadf1..5ee9a24b8eaaf 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2624,7 +2624,6 @@ "Highlights": "Highlights", "Highlights_How_To": "To be notified when someone mentions a word or phrase, add it here. You can separate words or phrases with commas. Highlight Words are not case sensitive.", "Highlights_List": "Highlight words", - "HipChat (tar.gz)": "HipChat (tar.gz)", "History": "History", "Hold_Time": "Hold Time", "Hold": "Hold", @@ -2690,8 +2689,6 @@ "Importer_finishing": "Finishing up the import.", "Importer_From_Description": "Imports {{from}} data into Rocket.Chat.", "Importer_From_Description_CSV": "Imports CSV data into Rocket.Chat. The uploaded file must be a ZIP file.", - "Importer_HipChatEnterprise_BetaWarning": "Please be aware that this import is still a work in progress, please report any errors which occur in GitHub:", - "Importer_HipChatEnterprise_Information": "The file uploaded must be a decrypted tar.gz, please read the documentation for further information:", "Importer_import_cancelled": "Import cancelled.", "Importer_import_failed": "An error occurred while running the import.", "Importer_importing_channels": "Importing the channels.", diff --git a/packages/i18n/src/locales/eo.i18n.json b/packages/i18n/src/locales/eo.i18n.json index 152d40e1466c6..3895650a69499 100644 --- a/packages/i18n/src/locales/eo.i18n.json +++ b/packages/i18n/src/locales/eo.i18n.json @@ -1271,8 +1271,6 @@ "Importer_done": "Importante kompletan!", "Importer_finishing": "Finante la importadon.", "Importer_From_Description": "Importas {{from}}-datumojn en Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Bonvolu konscii, ke ĉi tiu importado ankoraŭ estas progreso, bonvolu raporti iujn erarojn, kiuj okazas en GitHub:", - "Importer_HipChatEnterprise_Information": "La dosiero alŝutita devas esti senĉifrita tar.gz, bonvolu legi la dokumentadon por pliaj informoj:", "Importer_import_cancelled": "Importi nuligita.", "Importer_import_failed": "Eraro okazis dum ĝi funkciis la importadon.", "Importer_importing_channels": "Importante la kanaloj.", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index 88d5ea217f505..a4cfab497f2b3 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -2135,8 +2135,6 @@ "Importer_finishing": "Terminando la importación.", "Importer_From_Description": "Importa datos de {{from}} a Rocket.Chat.", "Importer_From_Description_CSV": "Importa datos en formato CSV a Rocket.Chat. El archivo subido debe ser un archivo ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Ten en cuenta que esta importación aún se está desarrollando. Informa acerca de cualquier error que ocurra en GitHub:", - "Importer_HipChatEnterprise_Information": "El archivo subido debe ser un archivo tar.gz descifrado. Consulta la documentación para obtener más información:", "Importer_import_cancelled": "Importación cancelada.", "Importer_import_failed": "Se ha producido un error durante la ejecución de la importación.", "Importer_importing_channels": "Importando los canales.", diff --git a/packages/i18n/src/locales/fa.i18n.json b/packages/i18n/src/locales/fa.i18n.json index b14bc31d7733e..0e85d3bc17ed2 100644 --- a/packages/i18n/src/locales/fa.i18n.json +++ b/packages/i18n/src/locales/fa.i18n.json @@ -1530,8 +1530,6 @@ "Importer_done": "وارد کردن تمام شد!", "Importer_finishing": "پایان دادن به وارد کردن.", "Importer_From_Description": "داده های {{from}} را وارد Rocket.chat می کند.", - "Importer_HipChatEnterprise_BetaWarning": "لطفا توجه داشته باشید که این واردات همچنان یک کار در حال انجام است، لطفا هر خطایی که در GitHub رخ می دهد گزارش دهید:", - "Importer_HipChatEnterprise_Information": "فایل آپلود شده باید یک tar.gz رمزگشایی شود، لطفا مستندات بیشتری برای اطلاعات بیشتر بخوانید:", "Importer_import_cancelled": "وارد کردن لغو شد.", "Importer_import_failed": "هنگام وارد کردن خطایی رخ داد.", "Importer_importing_channels": "وارد کردن کانال ها.", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index 90dd4b4f94bde..e8cb35434aef6 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -2426,8 +2426,6 @@ "Importer_finishing": "Viimeistellään tuontia.", "Importer_From_Description": "Tuo tiedot kohteesta {{from}} Rocket.Chatiin.", "Importer_From_Description_CSV": "Tuo CSV-tiedot Rocket.Chatiin. Ladatun tiedoston on oltava ZIP-tiedosto.", - "Importer_HipChatEnterprise_BetaWarning": "Huomaa, että tuontitoiminto on edelleen kehitteillä. Ilmoita virheistä GitHubissa:", - "Importer_HipChatEnterprise_Information": "Ladattavan tiedoston on oltava purettu tar.gz-tiedosto. Katso lisätietoja oppaista:", "Importer_import_cancelled": "Tuonti peruutettu.", "Importer_import_failed": "Virhe tuotaessa.", "Importer_importing_channels": "Tuodaan kanavia.", diff --git a/packages/i18n/src/locales/fr.i18n.json b/packages/i18n/src/locales/fr.i18n.json index 378071205b601..8e3f609dd663a 100644 --- a/packages/i18n/src/locales/fr.i18n.json +++ b/packages/i18n/src/locales/fr.i18n.json @@ -2129,8 +2129,6 @@ "Importer_finishing": "Finalisation de l'importation.", "Importer_From_Description": "Importer les données de {{from}} dans Rocket.Chat.", "Importer_From_Description_CSV": "Importe des données CSV dans Rocket.Chat. Le fichier chargé doit être un fichier ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Notez que la fonction d'importation est toujours en cours de développement, veuillez signaler toute erreur dans GitHub :", - "Importer_HipChatEnterprise_Information": "Le fichier chargé doit être au format tar.gz déchiffré, lisez la documentation pour plus d'informations :", "Importer_import_cancelled": "Importation annulée.", "Importer_import_failed": "Une erreur est survenue lors de l'importation.", "Importer_importing_channels": "Importation des canaux.", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index 6f63eeadd9ed5..2ff12e76f855c 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -2529,8 +2529,6 @@ "Importer_finishing": "आयात समाप्त करना.", "Importer_From_Description": "Rocket.Chat में {{from}} डेटा आयात करता है।", "Importer_From_Description_CSV": "Rocket.Chat में CSV डेटा आयात करता है। अपलोड की गई फ़ाइल एक ज़िप फ़ाइल होनी चाहिए.", - "Importer_HipChatEnterprise_BetaWarning": "कृपया ध्यान रखें कि इस आयात पर अभी भी काम चल रहा है, कृपया GitHub में होने वाली किसी भी त्रुटि की रिपोर्ट करें:", - "Importer_HipChatEnterprise_Information": "अपलोड की गई फ़ाइल डिक्रिप्टेड tar.gz होनी चाहिए, कृपया अधिक जानकारी के लिए दस्तावेज़ पढ़ें:", "Importer_import_cancelled": "आयात रद्द कर दिया गया.", "Importer_import_failed": "आयात चलाते समय एक त्रुटि उत्पन्न हुई.", "Importer_importing_channels": "चैनल आयात करना.", diff --git a/packages/i18n/src/locales/hr.i18n.json b/packages/i18n/src/locales/hr.i18n.json index c9547b1042e91..c13f5f016da0f 100644 --- a/packages/i18n/src/locales/hr.i18n.json +++ b/packages/i18n/src/locales/hr.i18n.json @@ -1401,8 +1401,6 @@ "Importer_done": "Uvoz dovršen!", "Importer_finishing": "Završavanje uvoza.", "Importer_From_Description": "Uvezi {{from}} podatke u Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Imajte na umu da je taj uvoz još uvijek u tijeku, prijavite sve pogreške koje se pojavljuju u GitHubu:", - "Importer_HipChatEnterprise_Information": "Prenesena datoteka mora biti dekriptirana tar.gz, pročitajte dokumentaciju za daljnje informacije:", "Importer_import_cancelled": "Uvoz je otkazan.", "Importer_import_failed": "Došlo je do pogreške pri izvršavanju uvoza.", "Importer_importing_channels": "Prebacivanje kanala.", diff --git a/packages/i18n/src/locales/hu.i18n.json b/packages/i18n/src/locales/hu.i18n.json index 78da1d401147c..ba8ccf5e3e057 100644 --- a/packages/i18n/src/locales/hu.i18n.json +++ b/packages/i18n/src/locales/hu.i18n.json @@ -2340,8 +2340,6 @@ "Importer_finishing": "Az importálás befejezése.", "Importer_From_Description": "{{from}}-adatokat importál a Rocket.Chatbe.", "Importer_From_Description_CSV": "CSV-adatokat importál a Rocket.Chatbe. A feltöltött fájlnak ZIP-fájlnak kell lennie.", - "Importer_HipChatEnterprise_BetaWarning": "Felhívjuk a figyelmét, hogy ennek az importálásnak a munkálatai még folyamatban vannak, az esetleges hibákat a GitHubon jelentse:", - "Importer_HipChatEnterprise_Information": "A feltöltött fájlnak visszafejtett tar.gz-nek kell lennie, olvassa el a dokumentációt további információkért:", "Importer_import_cancelled": "Az importálás megszakítva.", "Importer_import_failed": "Hiba történt az importálás futása során.", "Importer_importing_channels": "A csatornák importálása.", diff --git a/packages/i18n/src/locales/id.i18n.json b/packages/i18n/src/locales/id.i18n.json index 6eb8dd75ec6ef..2cbf226f01632 100644 --- a/packages/i18n/src/locales/id.i18n.json +++ b/packages/i18n/src/locales/id.i18n.json @@ -1271,8 +1271,6 @@ "Importer_done": "Mengimpor lengkap!", "Importer_finishing": "Menyelesaikan impor.", "Importer_From_Description": "Impor {{from}} Data's menjadi Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Perlu diketahui bahwa impor ini masih dalam proses penyelesaian, laporkan kesalahan yang terjadi di GitHub:", - "Importer_HipChatEnterprise_Information": "File yang diunggah harus berupa tar.gz terdekrip, baca dokumentasi untuk informasi lebih lanjut:", "Importer_import_cancelled": "Impor dibatalkan.", "Importer_import_failed": "Terjadi kesalahan saat menjalankan impor.", "Importer_importing_channels": "Mengimpor saluran.", diff --git a/packages/i18n/src/locales/it.i18n.json b/packages/i18n/src/locales/it.i18n.json index ec48aa15bb7b8..a7c9c71c9fb9e 100644 --- a/packages/i18n/src/locales/it.i18n.json +++ b/packages/i18n/src/locales/it.i18n.json @@ -1677,8 +1677,6 @@ "Importer_done": "Importazione completata!", "Importer_finishing": "In fase di terminazione dell'importazione.", "Importer_From_Description": "Importa i dati da {{from}} in Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Nota che questa importazione è in sviluppo, riporta ogni errori che può avvenire su GitHub:", - "Importer_HipChatEnterprise_Information": "Il file caricato deve essere un file tar.gz decriptato, leggi la documentazione per altre informazioni:", "Importer_import_cancelled": "Importazione annullata.", "Importer_import_failed": "Si è verificato un errore durante l'esecuzione dell'importazione.", "Importer_importing_channels": "In fase d'importazione dei canali.", diff --git a/packages/i18n/src/locales/ja.i18n.json b/packages/i18n/src/locales/ja.i18n.json index e049ea98c74b6..3d6b7a69aeb2a 100644 --- a/packages/i18n/src/locales/ja.i18n.json +++ b/packages/i18n/src/locales/ja.i18n.json @@ -2105,8 +2105,6 @@ "Importer_finishing": "インポートを終了しています。", "Importer_From_Description": "{{from}}のデータをRocket.Chatへインポートします。", "Importer_From_Description_CSV": "CSVデータをRocket.Chatにインポートします。アップロードするファイルはZIPファイルである必要があります。", - "Importer_HipChatEnterprise_BetaWarning": "このインポートはまだ進行中です。GitHubで発生したエラーを報告してください。", - "Importer_HipChatEnterprise_Information": "アップロードされるファイルは暗号化を解除されたtar.gzでなければなりません。詳細はドキュメントを参照してください。", "Importer_import_cancelled": "インポートをキャンセルしました。", "Importer_import_failed": "インポートの実行中にエラーが発生しました。", "Importer_importing_channels": "チャネルをインポートしています。", diff --git a/packages/i18n/src/locales/ka-GE.i18n.json b/packages/i18n/src/locales/ka-GE.i18n.json index 8f2098ff495f5..cd5f35d0a97a3 100644 --- a/packages/i18n/src/locales/ka-GE.i18n.json +++ b/packages/i18n/src/locales/ka-GE.i18n.json @@ -1716,8 +1716,6 @@ "Importer_ExternalUrl_Description": "თქვენ ასევე შეგიძლიათ ნახოთ ბმული საჯარო წვდომის ფაილისთვის", "Importer_finishing": "იმპორტი სრულდება", "Importer_From_Description": "მონაცემებს აიმპორტებს {{from}}-დან Rocket.Chat-ში", - "Importer_HipChatEnterprise_BetaWarning": "გთხოვთ გაითვალისწინოთ, რომ ეს იმპორტი კვლავ შემუშავების პროცესშია \n გთხოვთ, აცნობეთ შეცდომების შესახებ GitHub– ში:", - "Importer_HipChatEnterprise_Information": "ატვირთული ფაილი უნდა იყოს გაშიფრული tar.gz, დამატებითი ინფორმაციისთვის წაიკითხეთ დოკუმენტაცია:", "Importer_import_cancelled": "იმპორტი გაუქმდა.", "Importer_import_failed": "იმპორტისას მოხდა შეცდომა.", "Importer_importing_channels": "არხების იმპორტი.", diff --git a/packages/i18n/src/locales/km.i18n.json b/packages/i18n/src/locales/km.i18n.json index 583d34a5d70e1..509814035ad4b 100644 --- a/packages/i18n/src/locales/km.i18n.json +++ b/packages/i18n/src/locales/km.i18n.json @@ -1526,8 +1526,6 @@ "Importer_ExternalUrl_Description": "អ្នកក៏អាចប្រើ URL សម្រាប់ឯកសារអាចចូលដំណើរការជាសាធារណៈបានដែរ:", "Importer_finishing": "បញ្ចប់ការនាំចូល។", "Importer_From_Description": "ការនាំចូល {{from}} ទិន្នន័យ's បានចូលទៅក្នុង Rocket.Chat ។", - "Importer_HipChatEnterprise_BetaWarning": "សូមជ្រាបថាការនាំចូលនេះនៅតែជាការងារកំពុងដំណើរការសូមរាយការណ៍កំហុសទាំងឡាយដែលកើតឡើងនៅក្នុង GitHub:", - "Importer_HipChatEnterprise_Information": "ឯកសារដែលបានផ្ទុកឡើងត្រូវតែជាឌីជីថលដែលបានឌិគ្រីបសូមអានឯកសារសម្រាប់ព័ត៌មានបន្ថែម:", "Importer_import_cancelled": "នាំចូលលុបចោល។", "Importer_import_failed": "កំហុសមួយបានកើតឡើងខណៈពេលកំពុងរត់ការនាំចូល។", "Importer_importing_channels": "ការនាំចូលបណ្តាញនេះ។", diff --git a/packages/i18n/src/locales/ko.i18n.json b/packages/i18n/src/locales/ko.i18n.json index e9e498d814919..054d079e469f0 100644 --- a/packages/i18n/src/locales/ko.i18n.json +++ b/packages/i18n/src/locales/ko.i18n.json @@ -1867,8 +1867,6 @@ "Importer_ExternalUrl_Description": "외부에서 접근할 수 있는 파일의 URL을 사용할 수 있습니다.", "Importer_finishing": "가져오기 마무리 중..", "Importer_From_Description": "{{from}} 데이터를 Rocket.Chat으로 가져옵니다.", - "Importer_HipChatEnterprise_BetaWarning": "이 가져 오기가 아직 진행 중이므로 GitHub에서 발생하는 모든 오류를보고하십시오.", - "Importer_HipChatEnterprise_Information": "업로드 된 파일은 해독 된 tar.gz 여야합니다. 자세한 내용은 설명서를 참조하십시오.", "Importer_import_cancelled": "가져오기가 취소되었습니다.", "Importer_import_failed": "가져오기를 실행하는 동안 오류가 발생했습니다.", "Importer_importing_channels": "채널 가져오기", diff --git a/packages/i18n/src/locales/ku.i18n.json b/packages/i18n/src/locales/ku.i18n.json index 184ea2fb736a8..2b520a699a550 100644 --- a/packages/i18n/src/locales/ku.i18n.json +++ b/packages/i18n/src/locales/ku.i18n.json @@ -1266,8 +1266,6 @@ "Importer_done": "Importing temam!", "Importer_finishing": "Despêk xwe import.", "Importer_From_Description": "Imports {{from}} welat's nav Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Ji kerema xwe bizanibin ku ev import jî hîn jî di pêşveçûnê de ye, kerema ku li GitHubê pêk tê rapor bikin:", - "Importer_HipChatEnterprise_Information": "Divê belgeyê belaş divê tar.gz, ji kerema xwe belgeyên bêtir agahdarî belgeyên bixwînin:", "Importer_import_cancelled": "Import betalkirin.", "Importer_import_failed": "dema ku li import An error teqîn pêk hat.", "Importer_importing_channels": "Importing kanalên.", diff --git a/packages/i18n/src/locales/lo.i18n.json b/packages/i18n/src/locales/lo.i18n.json index 1f185faebfa3b..4c7819cea53ab 100644 --- a/packages/i18n/src/locales/lo.i18n.json +++ b/packages/i18n/src/locales/lo.i18n.json @@ -1305,8 +1305,6 @@ "Importer_done": "ການນໍາເຂົ້າທີ່ສົມບູນ!", "Importer_finishing": "ສໍາເລັດເຖິງການນໍາເຂົ້າ.", "Importer_From_Description": "ການນໍາເຂົ້າ {{from}} ຂໍ້ມູນ's ເຂົ້າໄປໃນ Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "ໂປດທາບວ່າການນໍາເຂົ້ານີ້ຍັງຄົງເປັນວຽກທີ່ກໍາລັງດໍາເນີນການ, ກະລຸນາລາຍງານຂໍ້ຜິດພາດທີ່ເກີດຂື້ນໃນ GitHub:", - "Importer_HipChatEnterprise_Information": "ໄຟລ໌ທີ່ອັບໂຫລດຕ້ອງເປັນ tar.gz ທີ່ຖືກລະຫັດ, ກະລຸນາອ່ານເອກສານສໍາລັບຂໍ້ມູນເພີ່ມເຕີມ:", "Importer_import_cancelled": "ການນໍາເຂົ້າຍົກເລີກ.", "Importer_import_failed": "ເກີດຄວາມຜິດພາດໃນຂະນະທີ່ເຮັດວຽກການນໍາເຂົ້າ.", "Importer_importing_channels": "ການນໍາເຂົ້າຊ່ອງທາງການ.", diff --git a/packages/i18n/src/locales/lt.i18n.json b/packages/i18n/src/locales/lt.i18n.json index 0394a78663874..ea0f7775cc9dc 100644 --- a/packages/i18n/src/locales/lt.i18n.json +++ b/packages/i18n/src/locales/lt.i18n.json @@ -1326,8 +1326,6 @@ "Importer_done": "Importavimas baigtas!", "Importer_finishing": "Užbaigti importą.", "Importer_From_Description": "Importuoja {{from}} duomenis į Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Atminkite, kad šis importas vis dar vyksta, prašome pranešti apie klaidas, kurios atsiranda \"GitHub\":", - "Importer_HipChatEnterprise_Information": "Įkeltas failas turi būti iššifruotas tar.gz, prašome perskaityti dokumentus, kad gautumėte papildomos informacijos:", "Importer_import_cancelled": "Importas atšauktas.", "Importer_import_failed": "Paleidus importą įvyko klaida.", "Importer_importing_channels": "Kanalų importavimas.", diff --git a/packages/i18n/src/locales/lv.i18n.json b/packages/i18n/src/locales/lv.i18n.json index eef5bccb6684c..c61b464997994 100644 --- a/packages/i18n/src/locales/lv.i18n.json +++ b/packages/i18n/src/locales/lv.i18n.json @@ -1282,8 +1282,6 @@ "Importer_CSV_Information": "CSV importer ir nepieciešams noteikts formāts, lūdzu, izlasiet dokumentāciju, kā veidot savu zip failu:", "Importer_done": "Importēšana ir pabeigta.", "Importer_finishing": "Importēšanas pabeigšana.", - "Importer_HipChatEnterprise_BetaWarning": "Lūdzu, ņemiet vērā, ka šis importēšana joprojām turpinās darbu, lūdzu, ziņojiet par visām kļūdām, kas rodas GitHub:", - "Importer_HipChatEnterprise_Information": "Augšupielādētajam failam jābūt atšifrētam tar.gz, lūdzu, izlasiet dokumentāciju, lai saņemtu sīkāku informāciju:", "Importer_import_cancelled": "Imports ir atcelts.", "Importer_import_failed": "Veicot importēšanu, radās kļūda.", "Importer_importing_channels": "Kanālu importēšana.", diff --git a/packages/i18n/src/locales/mn.i18n.json b/packages/i18n/src/locales/mn.i18n.json index d8277677b38d8..1516959dd2143 100644 --- a/packages/i18n/src/locales/mn.i18n.json +++ b/packages/i18n/src/locales/mn.i18n.json @@ -1266,8 +1266,6 @@ "Importer_CSV_Information": "CSV-ийн импортлогч тодорхой форматыг шаарддаг бөгөөд таны zip файлыг хэрхэн бүтээх талаар баримтжуулалтыг уншина уу:", "Importer_done": "Бүрэн импортлох!", "Importer_finishing": "Импортыг дуусгах.", - "Importer_HipChatEnterprise_BetaWarning": "Энэ импорт нь одоо ч ажил дуусч байгаа гэдгийг анхаарна уу, GitHub-д тохиолддог аливаа алдааг мэдээлнэ үү:", - "Importer_HipChatEnterprise_Information": "Байршуулсан файл нь шифрлэгдсэн tar.gz байна, дэлгэрэнгүй мэдээллийг баримтжуулж уншина уу:", "Importer_import_cancelled": "Импорт цуцлагдсан.", "Importer_import_failed": "Импортыг ажиллуулж байхад алдаа гарлаа.", "Importer_importing_channels": "Суваг оруулах.", diff --git a/packages/i18n/src/locales/ms-MY.i18n.json b/packages/i18n/src/locales/ms-MY.i18n.json index 911a9cdf88797..ba690e8e21d76 100644 --- a/packages/i18n/src/locales/ms-MY.i18n.json +++ b/packages/i18n/src/locales/ms-MY.i18n.json @@ -1269,8 +1269,6 @@ "Importer_done": "Mengimport lengkap!", "Importer_finishing": "Hampir selesai import.", "Importer_From_Description": "Import {{from}} data's ke dalam Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Harap maklum bahawa import ini masih merupakan kerja yang sedang berjalan, sila laporkan kesilapan yang berlaku di GitHub:", - "Importer_HipChatEnterprise_Information": "Fail yang dimuat naik mestilah tar.gz disahsulit, sila baca dokumentasi untuk maklumat lanjut:", "Importer_import_cancelled": "Import dibatalkan.", "Importer_import_failed": "Ralat berlaku semasa berjalan import.", "Importer_importing_channels": "Mengimport saluran.", diff --git a/packages/i18n/src/locales/nl.i18n.json b/packages/i18n/src/locales/nl.i18n.json index 64fae6187ae61..c2c8c2cbb4a57 100644 --- a/packages/i18n/src/locales/nl.i18n.json +++ b/packages/i18n/src/locales/nl.i18n.json @@ -2121,8 +2121,6 @@ "Importer_finishing": "Afwerking van de import.", "Importer_From_Description": "Impoort {{from}}-gegevens in Rocket.Chat.", "Importer_From_Description_CSV": "Importeert CSV-gegevens in Rocket.Chat. De geüploade file moet een ZIP-bestand zijn.", - "Importer_HipChatEnterprise_BetaWarning": "Houd er rekening mee dat deze import nog steeds in uitvoering is, meld eventuele fouten die optreden op GitHub:", - "Importer_HipChatEnterprise_Information": "Het geüploade bestand moet een gedecodeerde tar.gz zijn, lees de documentatie voor meer informatie:", "Importer_import_cancelled": "Import geannuleerd.", "Importer_import_failed": "Er is een fout opgetreden tijdens het importeren.", "Importer_importing_channels": "Kanalen aan het importeren.", diff --git a/packages/i18n/src/locales/nn.i18n.json b/packages/i18n/src/locales/nn.i18n.json index 32db3455abd7d..1ef3ae519b21a 100644 --- a/packages/i18n/src/locales/nn.i18n.json +++ b/packages/i18n/src/locales/nn.i18n.json @@ -2213,8 +2213,6 @@ "Importer_done": "Importerer komplett!", "Importer_finishing": "Fullfører importen.", "Importer_From_Description": "Importerer data fra {{from}} til Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Vær oppmerksom på at denne importen fortsatt er et pågående arbeid, vennligst rapporter eventuelle feil som oppstår i GitHub:", - "Importer_HipChatEnterprise_Information": "Filen som lastes opp må være en dekryptert tar.gz, vennligst les dokumentasjonen for ytterligere informasjon:", "Importer_import_cancelled": "Import avbrutt.", "Importer_import_failed": "Det oppsto en feil under kjøring av importen.", "Importer_importing_channels": "Importerer kanalene.", diff --git a/packages/i18n/src/locales/no.i18n.json b/packages/i18n/src/locales/no.i18n.json index 32db3455abd7d..1ef3ae519b21a 100644 --- a/packages/i18n/src/locales/no.i18n.json +++ b/packages/i18n/src/locales/no.i18n.json @@ -2213,8 +2213,6 @@ "Importer_done": "Importerer komplett!", "Importer_finishing": "Fullfører importen.", "Importer_From_Description": "Importerer data fra {{from}} til Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Vær oppmerksom på at denne importen fortsatt er et pågående arbeid, vennligst rapporter eventuelle feil som oppstår i GitHub:", - "Importer_HipChatEnterprise_Information": "Filen som lastes opp må være en dekryptert tar.gz, vennligst les dokumentasjonen for ytterligere informasjon:", "Importer_import_cancelled": "Import avbrutt.", "Importer_import_failed": "Det oppsto en feil under kjøring av importen.", "Importer_importing_channels": "Importerer kanalene.", diff --git a/packages/i18n/src/locales/pl.i18n.json b/packages/i18n/src/locales/pl.i18n.json index 2f58f3ba93027..667f78c5b514e 100644 --- a/packages/i18n/src/locales/pl.i18n.json +++ b/packages/i18n/src/locales/pl.i18n.json @@ -2369,8 +2369,6 @@ "Importer_finishing": "Kończąc się na import.", "Importer_From_Description": "Import danych {{from}} 's do Rocket.Chat.", "Importer_From_Description_CSV": "Importuje dane CSV do Rocket.Chat. Przesłany plik musi być plikiem ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Należy pamiętać, że ten import jest nadal w toku, zgłoś wszelkie błędy występujące w GitHub:", - "Importer_HipChatEnterprise_Information": "Przesłany plik musi być odszyfrowanym tar.gz. Aby uzyskać więcej informacji, przeczytaj dokumentację:", "Importer_import_cancelled": "Importowanie anulowane.", "Importer_import_failed": "Wystąpił błąd podczas wykonywania importu.", "Importer_importing_channels": "Importowanie kanałów.", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 4d986be292192..0f9bba883b9c1 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -2203,8 +2203,6 @@ "Importer_finishing": "Terminando a importação.", "Importer_From_Description": "Importa dados de {{from}} para o Rocket.Chat.", "Importer_From_Description_CSV": "Importa dados de CSV para o Rocket.Chat. O arquivo importado deve ser um arquivo ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Saiba que esta importação é ainda um trabalho em andamento; informe quaisquer erros que ocorrerem no GitHub:", - "Importer_HipChatEnterprise_Information": "O arquivo carregado deve ser um tar.gz descriptografado; leia a documentação para obter mais informações:", "Importer_import_cancelled": "Importação cancelada.", "Importer_import_failed": "Ocorreu um erro durante a execução da importação.", "Importer_importing_channels": "Importando os canais.", diff --git a/packages/i18n/src/locales/pt.i18n.json b/packages/i18n/src/locales/pt.i18n.json index 5c282e5a9275e..3e81cead35644 100644 --- a/packages/i18n/src/locales/pt.i18n.json +++ b/packages/i18n/src/locales/pt.i18n.json @@ -1514,8 +1514,6 @@ "Importer_ExternalUrl_Description": "Você também pode usar um URL para um arquivo acessível publicamente:", "Importer_finishing": "A terminar importação.", "Importer_From_Description": "Importar dados de {{from}} para o Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Lembre-se de que esta importação ainda é um trabalho em andamento, informe quaisquer erros que ocorram no GitHub:", - "Importer_HipChatEnterprise_Information": "O arquivo carregado deve ser um tar.gz descienciado, leia a documentação para obter mais informações:", "Importer_import_cancelled": "Importação cancelada.", "Importer_import_failed": "Ocorreu um erro durante a execução da importação.", "Importer_importing_channels": "A importar os canais.", diff --git a/packages/i18n/src/locales/ro.i18n.json b/packages/i18n/src/locales/ro.i18n.json index c5c84a33a0945..70c05dcf1d64b 100644 --- a/packages/i18n/src/locales/ro.i18n.json +++ b/packages/i18n/src/locales/ro.i18n.json @@ -1270,8 +1270,6 @@ "Importer_done": "Import complet!", "Importer_finishing": "Se finalizează import.", "Importer_From_Description": "Importuri {{from}} 's date în Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Rețineți că acest import este încă o operațiune în curs, vă rugăm să raportați orice erori apărute în GitHub:", - "Importer_HipChatEnterprise_Information": "Fișierul încărcat trebuie să fie un tar.gz decriptat, citiți documentația pentru mai multe informații:", "Importer_import_cancelled": "Import anulat.", "Importer_import_failed": "A Apărut o eroare în timpul importului.", "Importer_importing_channels": "Se importă canalele.", diff --git a/packages/i18n/src/locales/ru.i18n.json b/packages/i18n/src/locales/ru.i18n.json index ebc651aa2e084..dd7700a1c7d36 100644 --- a/packages/i18n/src/locales/ru.i18n.json +++ b/packages/i18n/src/locales/ru.i18n.json @@ -2278,8 +2278,6 @@ "Importer_finishing": "Завершить импорт данных.", "Importer_From_Description": "Импортировать данные из {{from}} в Rocket.Chat.", "Importer_From_Description_CSV": "Импортирует данные файла CSV в Rocket.Chat. Загруженный файл должен быть в формате ZIP.", - "Importer_HipChatEnterprise_BetaWarning": "Имейте в виду, что импорт всё ещё продолжается. Пожалуйста, сообщите о любых возникающих ошибках на GitHub:", - "Importer_HipChatEnterprise_Information": "Загруженный файл должен быть расшифрованным tar.gz, пожалуйста, прочитайте документацию для получения дополнительной информации:", "Importer_import_cancelled": "Импорт данных отменен.", "Importer_import_failed": "Во время импорта данных возникла ошибка.", "Importer_importing_channels": "Импортировать каналы.", diff --git a/packages/i18n/src/locales/se.i18n.json b/packages/i18n/src/locales/se.i18n.json index b890eb5b10f08..99dc1b976155e 100644 --- a/packages/i18n/src/locales/se.i18n.json +++ b/packages/i18n/src/locales/se.i18n.json @@ -2627,7 +2627,6 @@ "Highlights": "Highlights", "Highlights_How_To": "To be notified when someone mentions a word or phrase, add it here. You can separate words or phrases with commas. Highlight Words are not case sensitive.", "Highlights_List": "Highlight words", - "HipChat (tar.gz)": "HipChat (tar.gz)", "History": "History", "Hold_Time": "Hold Time", "Hold": "Hold", @@ -2693,8 +2692,6 @@ "Importer_finishing": "Finishing up the import.", "Importer_From_Description": "Imports {{from}} data into Rocket.Chat.", "Importer_From_Description_CSV": "Imports CSV data into Rocket.Chat. The uploaded file must be a ZIP file.", - "Importer_HipChatEnterprise_BetaWarning": "Please be aware that this import is still a work in progress, please report any errors which occur in GitHub:", - "Importer_HipChatEnterprise_Information": "The file uploaded must be a decrypted tar.gz, please read the documentation for further information:", "Importer_import_cancelled": "Import cancelled.", "Importer_import_failed": "An error occurred while running the import.", "Importer_importing_channels": "Importing the channels.", diff --git a/packages/i18n/src/locales/sk-SK.i18n.json b/packages/i18n/src/locales/sk-SK.i18n.json index ea85c9bcbd859..8bd9d313857dd 100644 --- a/packages/i18n/src/locales/sk-SK.i18n.json +++ b/packages/i18n/src/locales/sk-SK.i18n.json @@ -1281,8 +1281,6 @@ "Importer_done": "Import je dokončený!", "Importer_finishing": "Dokončenie importu.", "Importer_From_Description": "Importuje {{from}} dáta do Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Majte na pamäti, že tento import je stále prebiehajúcim procesom, nahláste všetky chyby, ktoré sa vyskytli v GitHub:", - "Importer_HipChatEnterprise_Information": "Nahraný súbor musí byť dešifrovaný tar.gz, prečítajte si prosím dokumentáciu pre ďalšie informácie:", "Importer_import_cancelled": "Import bol zrušený.", "Importer_import_failed": "Počas spúšťania importu sa vyskytla chyba.", "Importer_importing_channels": "Importovanie kanálov.", diff --git a/packages/i18n/src/locales/sl-SI.i18n.json b/packages/i18n/src/locales/sl-SI.i18n.json index 341e415aafd5e..2a9f043970a27 100644 --- a/packages/i18n/src/locales/sl-SI.i18n.json +++ b/packages/i18n/src/locales/sl-SI.i18n.json @@ -1262,8 +1262,6 @@ "Importer_done": "Uvoz končan!", "Importer_finishing": "Končujem uvoz. ", "Importer_From_Description": "Uvozi {{from}} podatki v Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Upoštevajte, da uvoz še kar poteka, prijavite napake, ki so se zgodile v GitHub:", - "Importer_HipChatEnterprise_Information": "Naložena datoteka mora biti dešifrirana tar.gz, za več informacij preberite navodila. ", "Importer_import_cancelled": "Uvoz preklican. ", "Importer_import_failed": "Napaka med uvozom.", "Importer_importing_channels": "Uvažanje kanalov. ", diff --git a/packages/i18n/src/locales/sq.i18n.json b/packages/i18n/src/locales/sq.i18n.json index 32dd0a32fff37..9665ce7a84f70 100644 --- a/packages/i18n/src/locales/sq.i18n.json +++ b/packages/i18n/src/locales/sq.i18n.json @@ -1270,8 +1270,6 @@ "Importer_done": "Importimi i plotë!", "Importer_finishing": "Përfunduar importin.", "Importer_From_Description": "Importet {{from}} dhënat's në Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Ju lutemi, kini parasysh se ky import ende është një punë në vazhdim, ju lutem raportoni çdo gabim që ndodh në GitHub:", - "Importer_HipChatEnterprise_Information": "Skedari i ngarkuar duhet të jetë tar.gz i dekriptuar, ju lutem lexoni dokumentacionin për informacione të mëtejshme:", "Importer_import_cancelled": "Import anuluar.", "Importer_import_failed": "Ndodhi një gabim gjatë drejtimin e importit.", "Importer_importing_channels": "Importimin e kanaleve.", diff --git a/packages/i18n/src/locales/sr.i18n.json b/packages/i18n/src/locales/sr.i18n.json index 74efaa8ffe903..e4ebf289babf8 100644 --- a/packages/i18n/src/locales/sr.i18n.json +++ b/packages/i18n/src/locales/sr.i18n.json @@ -1132,8 +1132,6 @@ "Importer_CSV_Information": "ЦСВ увознику је потребан одређени формат, молимо прочитајте документацију како структурирати своју зип датотеку:", "Importer_done": "Увоз завршен!", "Importer_finishing": "Завршавање увоза.", - "Importer_HipChatEnterprise_BetaWarning": "Имајте на уму да је овај увоз и даље посао у току, пријавите грешке које се јављају у ГитХуб-у:", - "Importer_HipChatEnterprise_Information": "Датотека која је отпремљена мора бити дешифрована тар.гз, прочитајте документацију за додатне информације:", "Importer_import_cancelled": "Увоз отказан.", "Importer_import_failed": "Дошло је до грешке приликом увоза.", "Importer_importing_channels": "Увожење канала.", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index e16c30490b174..fe5d100c127b3 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -2432,8 +2432,6 @@ "Importer_finishing": "Avslutar importen.", "Importer_From_Description": "Importera {{from}}s data till Rocket.Chat.", "Importer_From_Description_CSV": "Importerar CSV-data till Rocket.Chat. Den uppladdade filen måste vara en zip-fil.", - "Importer_HipChatEnterprise_BetaWarning": "Var medveten om att denna import fortfarande är ett pågående arbete, var god rapportera eventuella fel som uppstår i GitHub:", - "Importer_HipChatEnterprise_Information": "Den uppladdade filen måste vara en dekrypterad tar.gz, läs dokumentationen för ytterligare information:", "Importer_import_cancelled": "Import avbruten.", "Importer_import_failed": "Ett fel uppstod under importen.", "Importer_importing_channels": "Importerar kanalerna.", diff --git a/packages/i18n/src/locales/ta-IN.i18n.json b/packages/i18n/src/locales/ta-IN.i18n.json index c4c941c40d7b0..a653cfa811769 100644 --- a/packages/i18n/src/locales/ta-IN.i18n.json +++ b/packages/i18n/src/locales/ta-IN.i18n.json @@ -1270,8 +1270,6 @@ "Importer_done": "முழுமையான இறக்குமதி!", "Importer_finishing": "இறக்குமதி முடிக்கிறது.", "Importer_From_Description": "இறக்குமதி Rocket.Chat ஒரு 'கள் தரவு {{from}}.", - "Importer_HipChatEnterprise_BetaWarning": "இந்த இறக்குமதி இன்னமும் முன்னேற்றம் அடைந்ததை நினைவில் கொள்ளுங்கள், தயவுசெய்து GitHub இல் ஏற்படும் எந்த பிழைகளையும் தெரிவிக்கவும்:", - "Importer_HipChatEnterprise_Information": "பதிவேற்றிய கோப்பு ஒரு குறியாக்கப்பட்ட tar.gz ஆக இருக்க வேண்டும், மேலும் தகவலுக்கு ஆவணங்களைப் படிக்கவும்:", "Importer_import_cancelled": "இறக்குமதி ரத்துசெய்யப்பட்டது.", "Importer_import_failed": "இறக்குமதி இயங்கும் போது ஒரு பிழை ஏற்பட்டது.", "Importer_importing_channels": "சேனல்கள் இறக்குமதி.", diff --git a/packages/i18n/src/locales/th-TH.i18n.json b/packages/i18n/src/locales/th-TH.i18n.json index 699a6a408effa..24692225c49d6 100644 --- a/packages/i18n/src/locales/th-TH.i18n.json +++ b/packages/i18n/src/locales/th-TH.i18n.json @@ -1265,8 +1265,6 @@ "Importer_CSV_Information": "ตัวนำเข้า CSV ต้องการรูปแบบเฉพาะโปรดอ่านเอกสารสำหรับวิธีจัดโครงสร้างไฟล์ซิปของคุณ:", "Importer_done": "การนำเข้าเสร็จสมบูรณ์!", "Importer_finishing": "เสร็จสิ้นการนำเข้า", - "Importer_HipChatEnterprise_BetaWarning": "โปรดทราบว่าการนำเข้านี้ยังคงเป็นงานระหว่างดำเนินการโปรดรายงานข้อผิดพลาดที่เกิดขึ้นใน GitHub:", - "Importer_HipChatEnterprise_Information": "ไฟล์ที่อัปโหลดต้องเป็น tar.gad ถอดรหัสลับโปรดอ่านเอกสารประกอบสำหรับข้อมูลเพิ่มเติม:", "Importer_import_cancelled": "ยกเลิกการนำเข้าแล้ว", "Importer_import_failed": "เกิดข้อผิดพลาดขณะเรียกใช้งานนำเข้า", "Importer_importing_channels": "การนำเข้าช่อง", diff --git a/packages/i18n/src/locales/tr.i18n.json b/packages/i18n/src/locales/tr.i18n.json index 8bba28e0c950b..955b07dd744f5 100644 --- a/packages/i18n/src/locales/tr.i18n.json +++ b/packages/i18n/src/locales/tr.i18n.json @@ -1538,8 +1538,6 @@ "Importer_ExternalUrl_Description": "Herkesin erişebileceği dosya için bir URL de kullanabilirsiniz:", "Importer_finishing": "İçe aktarım tamamlanıyor.", "Importer_From_Description": "{{from}} verisini Rocket.Chat'e aktarır.", - "Importer_HipChatEnterprise_BetaWarning": "Bu içe aktarma işlemi halen devam etmekte olan bir çalışma olduğuna dikkat edin, lütfen GitHub'ta oluşan hataları bildirin:", - "Importer_HipChatEnterprise_Information": "Yüklenen dosya, şifresi çözülmüş bir tar.gz olmalıdır; daha fazla bilgi için lütfen belgeleri okuyun:", "Importer_import_cancelled": "İçe aktarım iptal edildi.", "Importer_import_failed": "İçe aktarım sırasında bir hata oluştu.", "Importer_importing_channels": "Kanallar içe aktarılıyor.", diff --git a/packages/i18n/src/locales/uk.i18n.json b/packages/i18n/src/locales/uk.i18n.json index 4ef4d2bc3084b..17540025f3585 100644 --- a/packages/i18n/src/locales/uk.i18n.json +++ b/packages/i18n/src/locales/uk.i18n.json @@ -1683,8 +1683,6 @@ "Importer_ExternalUrl_Description": "Ви також можете використовувати URL-адресу для загальнодоступного файлу:", "Importer_finishing": "Завершення імпорту.", "Importer_From_Description": "Imports {{from}} data в Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Будь ласка, пам'ятайте, що цей імпорт все ще працює, повідомте про всі помилки, які виникають в GitHub:", - "Importer_HipChatEnterprise_Information": "Завантажений файл повинен бути розшифрованим tar.gz, будь ласка, ознайомтеся з документацією для отримання додаткової інформації:", "Importer_import_cancelled": "Імпорт скасований.", "Importer_import_failed": "Сталася помилка під час виконання імпорту.", "Importer_importing_channels": "Імпорт каналів.", diff --git a/packages/i18n/src/locales/vi-VN.i18n.json b/packages/i18n/src/locales/vi-VN.i18n.json index 8f4968e913966..4e6240e950ed9 100644 --- a/packages/i18n/src/locales/vi-VN.i18n.json +++ b/packages/i18n/src/locales/vi-VN.i18n.json @@ -1364,8 +1364,6 @@ "Importer_done": "Nhập đầy đủ!", "Importer_finishing": "Hoàn thành việc nhập dữ liệu.", "Importer_From_Description": "Nhập dữ liệu {{from}} vào Rocket.Chat.", - "Importer_HipChatEnterprise_BetaWarning": "Xin lưu ý rằng phần nhập dữ liệu này vẫn đang trong quá trình hoàn thiện, hãy báo cáo bất kỳ lỗi nào xảy ra tại GitHub:", - "Importer_HipChatEnterprise_Information": "Tệp được tải lên phải là tệp được giải mã tar.gz, vui lòng đọc tài liệu để biết thêm thông tin:", "Importer_import_cancelled": "Nhập dữ liệu bị hủy.", "Importer_import_failed": "Đã xảy ra lỗi khi chạy.", "Importer_importing_channels": "Nhập các kênh.", diff --git a/packages/i18n/src/locales/zh-HK.i18n.json b/packages/i18n/src/locales/zh-HK.i18n.json index c1419e790e413..8b872f759de6d 100644 --- a/packages/i18n/src/locales/zh-HK.i18n.json +++ b/packages/i18n/src/locales/zh-HK.i18n.json @@ -1291,8 +1291,6 @@ "Importer_done": "导入完成!", "Importer_finishing": "完成进口。", "Importer_From_Description": "将{{from}}数据导入Rocket.Chat。", - "Importer_HipChatEnterprise_BetaWarning": "请注意,此导入仍在进行中,请报告GitHub中发生的任何错误:", - "Importer_HipChatEnterprise_Information": "上传的文件必须是解密的tar.gz,请阅读文档以获取更多信息:", "Importer_import_cancelled": "导入已取消。", "Importer_import_failed": "运行导入时发生错误。", "Importer_importing_channels": "导入频道。", diff --git a/packages/i18n/src/locales/zh-TW.i18n.json b/packages/i18n/src/locales/zh-TW.i18n.json index cfa0fab38c3b5..0a4e68014d38e 100644 --- a/packages/i18n/src/locales/zh-TW.i18n.json +++ b/packages/i18n/src/locales/zh-TW.i18n.json @@ -2082,8 +2082,6 @@ "Importer_finishing": "完成了匯入。", "Importer_From_Description": "匯入{{from}}的資料轉換成Rocket.Chat。", "Importer_From_Description_CSV": "匯入 CSV 資料到 Rocket.Chat。上傳的檔案必須為 ZIP 檔。", - "Importer_HipChatEnterprise_BetaWarning": "請注意,此匯入仍在進行中,請報告GitHub中發生的任何錯誤:", - "Importer_HipChatEnterprise_Information": "上傳的文件必須是解密的tar.gz,請閱讀文件以獲取更多訊息:", "Importer_import_cancelled": "匯入已取消。", "Importer_import_failed": "在執行匯入時出錯。", "Importer_importing_channels": "匯入頻道。", diff --git a/packages/i18n/src/locales/zh.i18n.json b/packages/i18n/src/locales/zh.i18n.json index 9e4ebbc4e647d..83df0b124ccc7 100644 --- a/packages/i18n/src/locales/zh.i18n.json +++ b/packages/i18n/src/locales/zh.i18n.json @@ -1899,8 +1899,6 @@ "Importer_ExternalUrl_Description": "您还可以将URL用于可公开访问的文件:", "Importer_finishing": "导入即将完成。", "Importer_From_Description": "将 {{from}} 数据导入 Rocket.Chat。", - "Importer_HipChatEnterprise_BetaWarning": "请注意,导入功能尚处于开发阶段,如有错误发生请在 GitHub 向我们反馈:", - "Importer_HipChatEnterprise_Information": "上传的文件必须为未加密的 tar.gz 文件,请查阅文档进一步了解相关信息:", "Importer_import_cancelled": "已取消导入。", "Importer_import_failed": "导入过程中发生错误!", "Importer_importing_channels": "正在导入频道。", diff --git a/packages/model-typings/src/models/IImportDataModel.ts b/packages/model-typings/src/models/IImportDataModel.ts index 160eaaf604a0c..95eaa8a4ce0d8 100644 --- a/packages/model-typings/src/models/IImportDataModel.ts +++ b/packages/model-typings/src/models/IImportDataModel.ts @@ -12,5 +12,4 @@ export interface IImportDataModel extends IBaseModel { checkIfDirectMessagesExists(): Promise; countMessages(): Promise; findChannelImportIdByNameOrImportId(channelIdentifier: string): Promise; - findDMForImportedUsers(...users: Array): Promise; } diff --git a/yarn.lock b/yarn.lock index c8ce498bf255c..d6055fb1c90bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9981,7 +9981,6 @@ __metadata: supports-color: ~7.2.0 suretype: ~2.4.1 swiper: ^9.4.1 - tar-stream: ^1.6.2 template-file: ^6.0.1 textarea-caret: ^3.1.0 tinykeys: ^1.4.0 @@ -41079,7 +41078,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^1.5.2, tar-stream@npm:^1.6.2": +"tar-stream@npm:^1.5.2": version: 1.6.2 resolution: "tar-stream@npm:1.6.2" dependencies: From c9e65138881005153da8e88b0701666d0ad77be9 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 11 Apr 2024 23:57:14 -0300 Subject: [PATCH 04/85] chore!: Removed Mongo 4.4. support and added 7.0 (#32162) Co-authored-by: Diego Sampaio --- .changeset/fair-seahorses-laugh.md | 13 +++++++++++++ .changeset/fluffy-knives-count.md | 5 +++++ .github/workflows/ci-test-e2e.yml | 8 ++++---- .github/workflows/ci.yml | 10 +++++----- apps/meteor/server/startup/serverRunning.js | 10 +++++----- docker-compose-local.yml | 8 ++++---- 6 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 .changeset/fair-seahorses-laugh.md create mode 100644 .changeset/fluffy-knives-count.md diff --git a/.changeset/fair-seahorses-laugh.md b/.changeset/fair-seahorses-laugh.md new file mode 100644 index 0000000000000..8f93695a8e174 --- /dev/null +++ b/.changeset/fair-seahorses-laugh.md @@ -0,0 +1,13 @@ +--- +'@rocket.chat/meteor': major +--- + +As per MongoDB Lifecycle Schedules ([mongodb.com/legal/support-policy/lifecycles](https://www.mongodb.com/legal/support-policy/lifecycles)) we're removing official support to MongoDB version 4.4 that has reached end of life in February 2024. + +We recommend upgrading to at least MongoDB 6.0+, though 5.0 is still a supported version. + +Here are official docs on how to upgrade to some of the supported versions: + +- [mongodb.com/docs/manual/release-notes/5.0-upgrade-replica-set](https://www.mongodb.com/docs/manual/release-notes/5.0-upgrade-replica-set/) +- [mongodb.com/docs/manual/release-notes/6.0-upgrade-replica-set](https://www.mongodb.com/docs/manual/release-notes/6.0-upgrade-replica-set/) +- [mongodb.com/docs/manual/release-notes/7.0-upgrade-replica-set](https://www.mongodb.com/docs/manual/release-notes/7.0-upgrade-replica-set/) diff --git a/.changeset/fluffy-knives-count.md b/.changeset/fluffy-knives-count.md new file mode 100644 index 0000000000000..4e4e8aad36319 --- /dev/null +++ b/.changeset/fluffy-knives-count.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Added MongoDB 7.0 support diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index f219f39c06145..b3b7db74eb95c 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -32,7 +32,7 @@ on: transporter: type: string mongodb-version: - default: "['4.4', '6.0']" + default: "['5.0', '7.0']" required: false type: string release: @@ -83,8 +83,8 @@ jobs: test: runs-on: ubuntu-20.04 env: - RC_DOCKERFILE: ${{ matrix.mongodb-version == '6.0' && inputs.rc-dockerfile-alpine || inputs.rc-dockerfile }} - RC_DOCKER_TAG: ${{ matrix.mongodb-version == '6.0' && inputs.rc-docker-tag-alpine || inputs.rc-docker-tag }} + RC_DOCKERFILE: ${{ matrix.mongodb-version == '7.0' && inputs.rc-dockerfile-alpine || inputs.rc-dockerfile }} + RC_DOCKER_TAG: ${{ matrix.mongodb-version == '7.0' && inputs.rc-docker-tag-alpine || inputs.rc-docker-tag }} strategy: fail-fast: false @@ -92,7 +92,7 @@ jobs: mongodb-version: ${{ fromJSON(inputs.mongodb-version) }} shard: ${{ fromJSON(inputs.shard) }} - name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.db-watcher-disabled == 'true' && ' [no watchers]' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})${{ matrix.mongodb-version == '6.0' && ' - Alpine' || '' }} + name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.db-watcher-disabled == 'true' && ' [no watchers]' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})${{ matrix.mongodb-version == '7.0' && ' - Alpine' || '' }} steps: - name: Collect Workflow Telemetry diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b6fa426ca969..5d3c0b89ade10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: fi; curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ - "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"compatibleMongoVersions\": [\"4.4\", \"5.0\", \"6.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"draft\", \"draftAs\": \"$RC_RELEASE\"}" \ + "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"compatibleMongoVersions\": [\"5.0\", \"6.0\", \"7.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"draft\", \"draftAs\": \"$RC_RELEASE\"}" \ https://releases.rocket.chat/update packages-build: @@ -384,7 +384,7 @@ jobs: release: ee transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} - mongodb-version: "['4.4']" + mongodb-version: "['5.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} @@ -409,7 +409,7 @@ jobs: enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4, 5]' total-shard: 5 - mongodb-version: "['4.4']" + mongodb-version: "['5.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} @@ -440,7 +440,7 @@ jobs: enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4, 5]' total-shard: 5 - mongodb-version: "['6.0']" + mongodb-version: "['7.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} @@ -825,7 +825,7 @@ jobs: fi; curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ - "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"compatibleMongoVersions\": [\"4.4\", \"5.0\", \"6.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \ + "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"compatibleMongoVersions\": [\"5.0\", \"6.0\", \"7.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \ https://releases.rocket.chat/update # Makes build fail if the release isn't there diff --git a/apps/meteor/server/startup/serverRunning.js b/apps/meteor/server/startup/serverRunning.js index 8d572036f16a8..d642c006c4d2e 100644 --- a/apps/meteor/server/startup/serverRunning.js +++ b/apps/meteor/server/startup/serverRunning.js @@ -78,8 +78,8 @@ Meteor.startup(async () => { exitIfNotBypassed(process.env.BYPASS_NODEJS_VALIDATION); } - if (!semver.satisfies(semver.coerce(mongoVersion), '>=4.4.0')) { - msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED,', 'PLEASE UPGRADE TO VERSION 4.4 OR LATER'].join('\n'); + if (semver.satisfies(semver.coerce(mongoVersion), '<5.0.0')) { + msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED,', 'PLEASE UPGRADE TO VERSION 5.0 OR LATER'].join('\n'); showErrorBox('SERVER ERROR', msg); exitIfNotBypassed(process.env.BYPASS_MONGO_VALIDATION); @@ -88,11 +88,11 @@ Meteor.startup(async () => { showSuccessBox('SERVER RUNNING', msg); // Deprecation - if (!skipMongoDbDeprecationCheck && !semver.satisfies(semver.coerce(mongoVersion), '>=5.0.0')) { + if (!skipMongoDbDeprecationCheck && semver.satisfies(semver.coerce(mongoVersion), '<6.0.0')) { msg = [ `YOUR CURRENT MONGODB VERSION (${mongoVersion}) IS DEPRECATED.`, - 'IT WILL NOT BE SUPPORTED ON ROCKET.CHAT VERSION 7.0.0 AND GREATER,', - 'PLEASE UPGRADE MONGODB TO VERSION 5.0 OR GREATER', + 'IT WILL NOT BE SUPPORTED ON ROCKET.CHAT VERSION 8.0.0 AND GREATER,', + 'PLEASE UPGRADE MONGODB TO VERSION 6.0 OR GREATER', ].join('\n'); showWarningBox('DEPRECATION', msg); diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 16c64187461a9..812899c7d9467 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -112,7 +112,7 @@ services: - 'host.docker.internal:host-gateway' depends_on: - nats - + queue-worker-service: platform: linux/amd64 build: @@ -128,7 +128,7 @@ services: - 'host.docker.internal:host-gateway' depends_on: - nats - + omnichannel-transcript-service: platform: linux/amd64 build: @@ -144,9 +144,9 @@ services: - 'host.docker.internal:host-gateway' depends_on: - nats - + mongo: - image: docker.io/bitnami/mongodb:4.4 + image: docker.io/bitnami/mongodb:7.0 restart: on-failure environment: MONGODB_REPLICA_SET_MODE: primary From ed5f16fc11cd920df66d3d226dbeed4f63ab0967 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 May 2024 10:30:41 -0300 Subject: [PATCH 05/85] chore!: Improve permissions check on channels endpoints (#32330) * chore: Improve permission check on channels endpoints --- apps/meteor/app/api/server/v1/channels.ts | 41 ++- apps/meteor/tests/end-to-end/api/channels.ts | 282 +++++++++++++------ 2 files changed, 218 insertions(+), 105 deletions(-) diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 931cf4be20191..a713bfd29ac60 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -27,7 +27,7 @@ import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { removeUserFromRoomMethod } from '../../../../server/methods/removeUserFromRoom'; import { canAccessRoomAsync } from '../../../authorization/server'; -import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { addUsersToRoomMethod } from '../../../lib/server/methods/addUsersToRoom'; @@ -272,6 +272,7 @@ API.v1.addRoute( { authRequired: true, validateParams: isChannelsMessagesProps, + permissionsRequired: ['view-c-room'], }, { async get() { @@ -292,9 +293,6 @@ API.v1.addRoute( ) { return API.v1.unauthorized(); } - if (!(await hasPermissionAsync(this.userId, 'view-c-room'))) { - return API.v1.unauthorized(); - } const { cursor, totalCount } = await Messages.findPaginated(ourQuery, { sort: sort || { ts: -1 }, @@ -477,13 +475,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isChannelsConvertToTeamProps, + permissionsRequired: ['create-team'], }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'create-team'))) { - return API.v1.unauthorized(); - } - const { channelId, channelName } = this.bodyParams; if (!channelId && !channelName) { @@ -855,20 +850,22 @@ API.v1.addRoute( API.v1.addRoute( 'channels.getIntegrations', - { authRequired: true }, { - async get() { - if ( - !(await hasAtLeastOnePermissionAsync(this.userId, [ + authRequired: true, + permissionsRequired: { + GET: { + permissions: [ 'manage-outgoing-integrations', 'manage-own-outgoing-integrations', 'manage-incoming-integrations', 'manage-own-incoming-integrations', - ])) - ) { - return API.v1.unauthorized(); - } - + ], + operation: 'hasAny', + }, + }, + }, + { + async get() { const findResult = await findChannelByIdOrName({ params: this.queryParams, checkedArchived: false, @@ -954,7 +951,12 @@ API.v1.addRoute( API.v1.addRoute( 'channels.list', - { authRequired: true }, + { + authRequired: true, + permissionsRequired: { + GET: { permissions: ['view-c-room', 'view-joined-room'], operation: 'hasAny' }, + }, + }, { async get() { const { offset, count } = await getPaginationItems(this.queryParams); @@ -964,9 +966,6 @@ API.v1.addRoute( const ourQuery: Record = { ...query, t: 'c' }; if (!hasPermissionToSeeAllPublicChannels) { - if (!(await hasPermissionAsync(this.userId, 'view-joined-room'))) { - return API.v1.unauthorized(); - } const roomIds = ( await Subscriptions.findByUserIdAndType(this.userId, 'c', { projection: { rid: 1 }, diff --git a/apps/meteor/tests/end-to-end/api/channels.ts b/apps/meteor/tests/end-to-end/api/channels.ts index 59ba102fe23af..7d4e6f3fa0c60 100644 --- a/apps/meteor/tests/end-to-end/api/channels.ts +++ b/apps/meteor/tests/end-to-end/api/channels.ts @@ -340,21 +340,98 @@ describe('[Channels]', () => { .end(done); }); - it('/channels.list', (done) => { - void request - .get(api('channels.list')) - .set(credentials) - .query({ - roomId: channel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - }) - .end(done); + describe('/channels.list', () => { + let testChannel: IRoom; + before(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + await updatePermission('view-joined-room', ['guest', 'bot', 'app', 'anonymous']); + testChannel = (await createRoom({ type: 'c', name: `channels.messages.test.${Date.now()}` })).body.channel; + }); + + after(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + await updatePermission('view-joined-room', ['guest', 'bot', 'app', 'anonymous']); + await deleteRoom({ type: 'c', roomId: testChannel._id }); + }); + + it('should succesfully return a list of channels', async () => { + await request + .get(api('channels.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channels').that.is.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + }); + }); + + it('should correctly filter channel by id', async () => { + await request + .get(api('channels.list')) + .set(credentials) + .query({ + query: JSON.stringify({ + _id: testChannel._id, + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channels').that.is.an('array').of.length(1); + expect(res.body).to.have.property('count', 1); + expect(res.body).to.have.property('total'); + }); + }); + + it('should not be succesful when user does NOT have the permission to view channels or joined rooms', async () => { + await updatePermission('view-c-room', []); + await updatePermission('view-joined-room', []); + await request + .get(api('channels.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + + it('should be succesful when user does NOT have the permission to view channels, but can view joined rooms', async () => { + await updatePermission('view-c-room', []); + await updatePermission('view-joined-room', ['admin']); + await request + .get(api('channels.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channels').that.is.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + }); + }); + + it('should be succesful when user does NOT have the permission to view joined rooms, but can view channels', async () => { + await updatePermission('view-c-room', ['admin']); + await updatePermission('view-joined-room', []); + await request + .get(api('channels.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channels').that.is.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + }); + }); }); it('/channels.list.joined', (done) => { @@ -1400,79 +1477,70 @@ describe('[Channels]', () => { ]); }); - it('should return the list of integrations of created channel and it should contain the integration created by user when the admin DOES have the permission', (done) => { - void updatePermission('manage-incoming-integrations', ['admin']).then(() => { - void request - .get(api('channels.getIntegrations')) - .set(credentials) - .query({ - roomId: createdChannel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - const integrationCreated = (res.body.integrations as IIntegration[]).find( - (createdIntegration) => createdIntegration._id === integrationCreatedByAnUser._id, - ); - assert.isDefined(integrationCreated); - expect(integrationCreated).to.be.an('object'); - expect(integrationCreated._id).to.be.equal(integrationCreatedByAnUser._id); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - }) - .end(done); - }); + it('should return the list of integrations of created channel and it should contain the integration created by user when the admin DOES have the permission', async () => { + await updatePermission('manage-incoming-integrations', ['admin']); + await request + .get(api('channels.getIntegrations')) + .set(credentials) + .query({ + roomId: createdChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + const integrationCreated = (res.body.integrations as IIntegration[]).find( + (createdIntegration) => createdIntegration._id === integrationCreatedByAnUser._id, + ); + assert.isDefined(integrationCreated); + expect(integrationCreated).to.be.an('object'); + expect(integrationCreated._id).to.be.equal(integrationCreatedByAnUser._id); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + }); }); - it('should return the list of integrations created by the user only', (done) => { - void updatePermission('manage-own-incoming-integrations', ['admin']).then(() => { - void updatePermission('manage-incoming-integrations', []).then(() => { - void request - .get(api('channels.getIntegrations')) - .set(credentials) - .query({ - roomId: createdChannel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - const integrationCreated = (res.body.integrations as IIntegration[]).find( - (createdIntegration) => createdIntegration._id === integrationCreatedByAnUser._id, - ); - assert.isUndefined(integrationCreated); - expect(integrationCreated).to.be.equal(undefined); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - }) - .end(done); + it('should return the list of integrations created by the user only', async () => { + await updatePermission('manage-own-incoming-integrations', ['admin']); + await updatePermission('manage-incoming-integrations', []); + await request + .get(api('channels.getIntegrations')) + .set(credentials) + .query({ + roomId: createdChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + const integrationCreated = (res.body.integrations as IIntegration[]).find( + (createdIntegration) => createdIntegration._id === integrationCreatedByAnUser._id, + ); + expect(integrationCreated).to.be.equal(undefined); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); }); - }); }); - it('should return unauthorized error when the user does not have any integrations permissions', (done) => { - void updatePermission('manage-incoming-integrations', []).then(() => { - void updatePermission('manage-own-incoming-integrations', []).then(() => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void updatePermission('manage-own-outgoing-integrations', []).then(() => { - void request - .get(api('channels.getIntegrations')) - .set(credentials) - .query({ - roomId: createdChannel._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - }); + it('should return unauthorized error when the user does not have any integrations permissions', async () => { + await Promise.all([ + updatePermission('manage-incoming-integrations', []), + updatePermission('manage-own-incoming-integrations', []), + updatePermission('manage-outgoing-integrations', []), + updatePermission('manage-own-outgoing-integrations', []), + ]); + await request + .get(api('channels.getIntegrations')) + .set(credentials) + .query({ + roomId: createdChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); - }); }); }); @@ -2048,7 +2116,7 @@ describe('[Channels]', () => { ]); }); - it('should fail to convert channel if lacking edit-room permission', async () => { + it('should fail to convert channel if lacking create-team permission', async () => { await updatePermission('create-team', []); await updatePermission('edit-room', ['admin']); @@ -2062,7 +2130,7 @@ describe('[Channels]', () => { }); }); - it('should fail to convert channel if lacking create-team permission', async () => { + it('should fail to convert channel if lacking edit-room permission', async () => { await updatePermission('create-team', ['admin']); await updatePermission('edit-room', []); @@ -2243,4 +2311,50 @@ describe('[Channels]', () => { }); }); }); + + describe('[/channels.messages]', () => { + let testChannel: IRoom; + before(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + testChannel = (await createRoom({ type: 'c', name: `channels.messages.test.${Date.now()}` })).body.channel; + }); + + after(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + await deleteRoom({ type: 'c', roomId: testChannel._id }); + }); + + it('should return an empty array of messages when inspecting a new room', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array').that.is.empty; + expect(res.body).to.have.property('count', 0); + expect(res.body).to.have.property('total', 0); + }); + }); + + it('should not return message when the user does NOT have the necessary permission', async () => { + await updatePermission('view-c-room', []); + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); }); From 2f396ca446e8e8c3561e704678be3c1295f17b95 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 May 2024 16:50:47 -0300 Subject: [PATCH 06/85] chore: Improve permissions check on cloud endpoints (#32331) --- apps/meteor/app/api/server/v1/cloud.ts | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/meteor/app/api/server/v1/cloud.ts b/apps/meteor/app/api/server/v1/cloud.ts index 257612628bffe..c0694deef4fae 100644 --- a/apps/meteor/app/api/server/v1/cloud.ts +++ b/apps/meteor/app/api/server/v1/cloud.ts @@ -2,7 +2,6 @@ import { check } from 'meteor/check'; import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../server/lib/logger/system'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { getCheckoutUrl } from '../../../cloud/server/functions/getCheckoutUrl'; import { getConfirmationPoll } from '../../../cloud/server/functions/getConfirmationPoll'; @@ -20,17 +19,13 @@ import { API } from '../api'; API.v1.addRoute( 'cloud.manualRegister', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['register-on-cloud'] }, { async post() { check(this.bodyParams, { cloudBlob: String, }); - if (!(await hasPermissionAsync(this.userId, 'register-on-cloud'))) { - return API.v1.unauthorized(); - } - const registrationInfo = await retrieveRegistrationStatus(); if (registrationInfo.workspaceRegistered) { @@ -48,7 +43,7 @@ API.v1.addRoute( API.v1.addRoute( 'cloud.createRegistrationIntent', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['manage-cloud'] }, { async post() { check(this.bodyParams, { @@ -56,10 +51,6 @@ API.v1.addRoute( email: String, }); - if (!(await hasPermissionAsync(this.userId, 'manage-cloud'))) { - return API.v1.unauthorized(); - } - const intentData = await startRegisterWorkspaceSetupWizard(this.bodyParams.resend, this.bodyParams.email); if (intentData) { @@ -73,13 +64,9 @@ API.v1.addRoute( API.v1.addRoute( 'cloud.registerPreIntent', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['manage-cloud'] }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'manage-cloud'))) { - return API.v1.unauthorized(); - } - return API.v1.success({ offline: !(await registerPreIntentWorkspaceWizard()) }); }, }, @@ -87,7 +74,7 @@ API.v1.addRoute( API.v1.addRoute( 'cloud.confirmationPoll', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['manage-cloud'] }, { async get() { const { deviceCode } = this.queryParams; @@ -95,10 +82,6 @@ API.v1.addRoute( deviceCode: String, }); - if (!(await hasPermissionAsync(this.userId, 'manage-cloud'))) { - return API.v1.unauthorized(); - } - if (!deviceCode) { return API.v1.failure('Invalid query'); } From 791792b0f59acffbfc67eb9b06d329ebdd5e44ff Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 May 2024 17:05:32 -0300 Subject: [PATCH 07/85] chore: Improve permissions check on instances endpoints (#32334) --- apps/meteor/app/api/server/v1/instances.ts | 7 +------ apps/meteor/tests/end-to-end/api/miscellaneous.ts | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/api/server/v1/instances.ts b/apps/meteor/app/api/server/v1/instances.ts index e5404ab3e53c3..47f98c856f446 100644 --- a/apps/meteor/app/api/server/v1/instances.ts +++ b/apps/meteor/app/api/server/v1/instances.ts @@ -1,7 +1,6 @@ import { InstanceStatus } from '@rocket.chat/models'; import { isRunningMs } from '../../../../server/lib/isRunningMs'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { API } from '../api'; import { getInstanceList } from '../helpers/getInstanceList'; @@ -15,13 +14,9 @@ const getConnections = (() => { API.v1.addRoute( 'instances.get', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-statistics'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-statistics'))) { - return API.v1.unauthorized(); - } - const instanceRecords = await InstanceStatus.find().toArray(); const connections = await getConnections(); diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index d933f1f3c4b34..efa7bdb93d5b9 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -541,7 +541,7 @@ describe('miscellaneous', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); From 577fdcad6f3d8601954dfa42f1466e387afac8d7 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 May 2024 17:29:50 -0300 Subject: [PATCH 08/85] chore: Improve permissions check on LDAP endpoints (#32335) --- apps/meteor/app/api/server/v1/ldap.ts | 13 +----- apps/meteor/tests/end-to-end/api/LDAP.ts | 55 +++++++++++++++++++++++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/apps/meteor/app/api/server/v1/ldap.ts b/apps/meteor/app/api/server/v1/ldap.ts index 9a057c9b0afc4..4b112cfbf3351 100644 --- a/apps/meteor/app/api/server/v1/ldap.ts +++ b/apps/meteor/app/api/server/v1/ldap.ts @@ -2,23 +2,18 @@ import { LDAP } from '@rocket.chat/core-services'; import { Match, check } from 'meteor/check'; import { SystemLogger } from '../../../../server/lib/logger/system'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; import { API } from '../api'; API.v1.addRoute( 'ldap.testConnection', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['test-admin-options'] }, { async post() { if (!this.userId) { throw new Error('error-invalid-user'); } - if (!(await hasPermissionAsync(this.userId, 'test-admin-options'))) { - throw new Error('error-not-authorized'); - } - if (settings.get('LDAP_Enable') !== true) { throw new Error('LDAP_disabled'); } @@ -39,7 +34,7 @@ API.v1.addRoute( API.v1.addRoute( 'ldap.testSearch', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['test-admin-options'] }, { async post() { check( @@ -53,10 +48,6 @@ API.v1.addRoute( throw new Error('error-invalid-user'); } - if (!(await hasPermissionAsync(this.userId, 'test-admin-options'))) { - throw new Error('error-not-authorized'); - } - if (settings.get('LDAP_Enable') !== true) { throw new Error('LDAP_disabled'); } diff --git a/apps/meteor/tests/end-to-end/api/LDAP.ts b/apps/meteor/tests/end-to-end/api/LDAP.ts index fc5c9d127836e..d9ac0c65b8ca3 100644 --- a/apps/meteor/tests/end-to-end/api/LDAP.ts +++ b/apps/meteor/tests/end-to-end/api/LDAP.ts @@ -1,10 +1,12 @@ import { expect } from 'chai'; -import { before, describe, it } from 'mocha'; +import { before, after, describe, it } from 'mocha'; import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { updatePermission } from '../../data/permissions.helper'; -describe('LDAP', () => { +describe('LDAP', function () { + this.retries(0); before((done) => getCredentials(done)); describe('[/ldap.syncNow]', () => { @@ -42,4 +44,53 @@ describe('LDAP', () => { }); }); }); + + describe('[/ldap.testSearch]', () => { + before(async () => { + return updatePermission('test-admin-options', ['admin']); + }); + + after(async () => { + return updatePermission('test-admin-options', ['admin']); + }); + + it('should not allow testing LDAP search if user does NOT have the test-admin-options permission', async () => { + await updatePermission('test-admin-options', []); + await request + .post(api('ldap.testSearch')) + .set(credentials) + .send({ + username: 'test-search', + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); + + describe('[/ldap.testConnection]', () => { + before(async () => { + return updatePermission('test-admin-options', ['admin']); + }); + + after(async () => { + return updatePermission('test-admin-options', ['admin']); + }); + + it('should not allow testing LDAP connection if user does NOT have the test-admin-options permission', async () => { + await updatePermission('test-admin-options', []); + await request + .post(api('ldap.testConnection')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); }); From ae10671239c4f6e67ec0d308d4cc21f2be6611d6 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 May 2024 17:30:48 -0300 Subject: [PATCH 09/85] chore!: Improve permissions check on mailer endpoints (#32336) --- apps/meteor/app/api/server/v1/mailer.ts | 6 +--- .../end-to-end/api/livechat/12-mailer.ts | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/api/server/v1/mailer.ts b/apps/meteor/app/api/server/v1/mailer.ts index 767868090b91d..56229e26dc316 100644 --- a/apps/meteor/app/api/server/v1/mailer.ts +++ b/apps/meteor/app/api/server/v1/mailer.ts @@ -1,6 +1,5 @@ import { isMailerProps, isMailerUnsubscribeProps } from '@rocket.chat/rest-typings'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { API } from '../api'; API.v1.addRoute( @@ -8,13 +7,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isMailerProps, + permissionsRequired: ['send-mail'], }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'send-mail'))) { - throw new Error('error-not-allowed'); - } - const { from, subject, body, dryrun, query } = this.bodyParams; const result = await Meteor.callAsync('Mailer.sendMail', from, subject, body, Boolean(dryrun), query); diff --git a/apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts b/apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts index 01a47594620d6..c9bdaa6139858 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts @@ -1,13 +1,22 @@ import { expect } from 'chai'; -import { before, describe, it } from 'mocha'; +import { before, after, describe, it } from 'mocha'; import type { Response } from 'supertest'; import { api, request, credentials, getCredentials } from '../../../data/api-data'; +import { updatePermission } from '../../../data/permissions.helper'; describe('Mailer', () => { before((done) => getCredentials(done)); - describe('POST mailer', () => { + describe('POST mailer', async () => { + before(async () => { + return updatePermission('send-mail', ['admin']); + }); + + after(async () => { + return updatePermission('send-mail', ['admin']); + }); + it('should send an email if the payload is correct', async () => { await request .post(api('mailer')) @@ -58,6 +67,25 @@ describe('Mailer', () => { expect(res.body).to.have.property('success', false); }); }); + it('should throw an error if user does NOT have the send-mail permission', async () => { + await updatePermission('send-mail', []); + await request + .post(api('mailer')) + .set(credentials) + .send({ + from: 'test-mail@test.com', + subject: 'Test email subject', + body: 'Test email body', + dryrun: true, + query: '', + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); }); }); From fbf42dac618008f940b2c4bd356a3b8bae9de060 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 6 May 2024 21:24:13 -0300 Subject: [PATCH 10/85] chore: Improve permissions check on users endpoints (#32353) --- apps/meteor/app/api/server/v1/users.ts | 38 +++++++---------------- apps/meteor/tests/end-to-end/api/users.ts | 11 ++++--- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 530e3dc0770cb..172a55024ccea 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -324,13 +324,9 @@ API.v1.addRoute( API.v1.addRoute( 'users.delete', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['delete-user'] }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'delete-user'))) { - return API.v1.unauthorized(); - } - const user = await getUserFromParams(this.bodyParams); const { confirmRelinquish = false } = this.bodyParams; @@ -365,16 +361,15 @@ API.v1.addRoute( API.v1.addRoute( 'users.setActiveStatus', - { authRequired: true, validateParams: isUserSetActiveStatusParamsPOST }, + { + authRequired: true, + validateParams: isUserSetActiveStatusParamsPOST, + permissionsRequired: { + POST: { permissions: ['edit-other-user-active-status', 'manage-moderation-actions'], operation: 'hasAny' }, + }, + }, { async post() { - if ( - !(await hasPermissionAsync(this.userId, 'edit-other-user-active-status')) && - !(await hasPermissionAsync(this.userId, 'manage-moderation-actions')) - ) { - return API.v1.unauthorized(); - } - const { userId, activeStatus, confirmRelinquish = false } = this.bodyParams; await Meteor.callAsync('setUserActiveStatus', userId, activeStatus, confirmRelinquish); @@ -391,13 +386,9 @@ API.v1.addRoute( API.v1.addRoute( 'users.deactivateIdle', - { authRequired: true, validateParams: isUserDeactivateIdleParamsPOST }, + { authRequired: true, validateParams: isUserDeactivateIdleParamsPOST, permissionsRequired: ['edit-other-user-active-status'] }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'edit-other-user-active-status'))) { - return API.v1.unauthorized(); - } - const { daysIdle, role = 'user' } = this.bodyParams; const lastLoggedIn = new Date(); @@ -469,13 +460,10 @@ API.v1.addRoute( { authRequired: true, queryOperations: ['$or', '$and'], + permissionsRequired: ['view-d-room'], }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-d-room'))) { - return API.v1.unauthorized(); - } - if ( settings.get('API_Apply_permission_view-outside-room_on_users-list') && !(await hasPermissionAsync(this.userId, 'view-outside-room')) @@ -835,13 +823,9 @@ API.v1.addRoute( API.v1.addRoute( 'users.getPersonalAccessTokens', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['create-personal-access-tokens'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'create-personal-access-tokens'))) { - throw new Meteor.Error('not-authorized', 'Not Authorized'); - } - const user = (await Users.getLoginTokensByUserId(this.userId).toArray())[0] as unknown as IUser | undefined; const isPersonalAccessToken = (loginToken: ILoginToken | IPersonalAccessToken): loginToken is IPersonalAccessToken => diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index 77e2b330cc0ad..afb2d5fd4b37a 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -2982,7 +2982,7 @@ describe('[Users]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); }); @@ -3245,10 +3245,10 @@ describe('[Users]', () => { .get(api('users.getPersonalAccessTokens')) .set(credentials) .expect('Content-Type', 'application/json') - .expect(400) + .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('not-authorized'); + expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -3369,6 +3369,7 @@ describe('[Users]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -3385,6 +3386,7 @@ describe('[Users]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -3401,6 +3403,7 @@ describe('[Users]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -3615,7 +3618,7 @@ describe('[Users]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); From 51afc7a27788c3317f64b2f51362fc73d27c9909 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 7 May 2024 12:54:20 -0300 Subject: [PATCH 11/85] chore!: Improve permissions check on groups endpoints (#32332) --- apps/meteor/app/api/server/v1/groups.ts | 31 +++++------- apps/meteor/tests/end-to-end/api/groups.ts | 58 ++++++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 34deb57304fcb..df03e38dc1470 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -9,11 +9,7 @@ import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { removeUserFromRoomMethod } from '../../../../server/methods/removeUserFromRoom'; import { canAccessRoomAsync, roomAccessAttributes } from '../../../authorization/server'; -import { - hasAllPermissionAsync, - hasAtLeastOnePermissionAsync, - hasPermissionAsync, -} from '../../../authorization/server/functions/hasPermission'; +import { hasAllPermissionAsync, hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup'; @@ -412,20 +408,22 @@ API.v1.addRoute( API.v1.addRoute( 'groups.getIntegrations', - { authRequired: true }, { - async get() { - if ( - !(await hasAtLeastOnePermissionAsync(this.userId, [ + authRequired: true, + permissionsRequired: { + GET: { + permissions: [ 'manage-outgoing-integrations', 'manage-own-outgoing-integrations', 'manage-incoming-integrations', 'manage-own-incoming-integrations', - ])) - ) { - return API.v1.unauthorized(); - } - + ], + operation: 'hasAny', + }, + }, + }, + { + async get() { const findResult = await findPrivateGroupByIdOrName({ params: this.queryParams, userId: this.userId, @@ -670,12 +668,9 @@ API.v1.addRoute( API.v1.addRoute( 'groups.listAll', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-room-administration'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-room-administration'))) { - return API.v1.unauthorized(); - } const { offset, count } = await getPaginationItems(this.queryParams); const { sort, fields, query } = await this.parseJsonQuery(); const ourQuery = Object.assign({}, query, { t: 'p' as RoomType }); diff --git a/apps/meteor/tests/end-to-end/api/groups.ts b/apps/meteor/tests/end-to-end/api/groups.ts index 39fd30a3ae2f3..af22c65c2a84c 100644 --- a/apps/meteor/tests/end-to-end/api/groups.ts +++ b/apps/meteor/tests/end-to-end/api/groups.ts @@ -1154,33 +1154,37 @@ describe('[Groups]', () => { }); describe('/groups.listAll', () => { - it('should fail if the user doesnt have view-room-administration permission', (done) => { - void updatePermission('view-room-administration', []).then(() => { - void request - .get(api('groups.listAll')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); + before(async () => { + return updatePermission('view-room-administration', ['admin']); }); - it('should succeed if user has view-room-administration permission', (done) => { - void updatePermission('view-room-administration', ['admin']).then(() => { - void request - .get(api('groups.listAll')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('groups').and.to.be.an('array'); - }) - .end(done); - }); + + after(async () => { + return updatePermission('view-room-administration', ['admin']); + }); + + it('should succeed if user has view-room-administration permission', async () => { + await request + .get(api('groups.listAll')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('groups').and.to.be.an('array'); + }); + }); + + it('should fail if the user doesnt have view-room-administration permission', async () => { + await updatePermission('view-room-administration', []); + await request + .get(api('groups.listAll')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); }); }); @@ -1345,7 +1349,7 @@ describe('[Groups]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); }); }); From 20e37ded2da04a74778f0f889d0c8e07d4329805 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 7 May 2024 15:13:42 -0300 Subject: [PATCH 12/85] chore!: Improve permissions check on integrations endpoints (#32355) --- apps/meteor/app/api/server/v1/integrations.ts | 54 ++++++++------- .../end-to-end/api/incoming-integrations.ts | 41 +++++------ .../end-to-end/api/outgoing-integrations.ts | 69 +++++++++---------- 3 files changed, 81 insertions(+), 83 deletions(-) diff --git a/apps/meteor/app/api/server/v1/integrations.ts b/apps/meteor/app/api/server/v1/integrations.ts index f7eb9e5a14675..5306e7fbbea39 100644 --- a/apps/meteor/app/api/server/v1/integrations.ts +++ b/apps/meteor/app/api/server/v1/integrations.ts @@ -11,7 +11,6 @@ import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { Filter } from 'mongodb'; -import { hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { mountIntegrationHistoryQueryBasedOnPermissions, mountIntegrationQueryBasedOnPermissions, @@ -43,15 +42,17 @@ API.v1.addRoute( API.v1.addRoute( 'integrations.history', - { authRequired: true, validateParams: isIntegrationsHistoryProps }, + { + authRequired: true, + validateParams: isIntegrationsHistoryProps, + permissionsRequired: { + GET: { permissions: ['manage-outgoing-integrations', 'manage-own-outgoing-integrations'], operation: 'hasAny' }, + }, + }, { async get() { const { userId, queryParams } = this; - if (!(await hasAtLeastOnePermissionAsync(userId, ['manage-outgoing-integrations', 'manage-own-outgoing-integrations']))) { - return API.v1.unauthorized(); - } - if (!queryParams.id || queryParams.id.trim() === '') { return API.v1.failure('Invalid integration id.'); } @@ -83,20 +84,22 @@ API.v1.addRoute( API.v1.addRoute( 'integrations.list', - { authRequired: true }, { - async get() { - if ( - !(await hasAtLeastOnePermissionAsync(this.userId, [ + authRequired: true, + permissionsRequired: { + GET: { + permissions: [ 'manage-outgoing-integrations', 'manage-own-outgoing-integrations', 'manage-incoming-integrations', 'manage-own-incoming-integrations', - ])) - ) { - return API.v1.unauthorized(); - } - + ], + operation: 'hasAny', + }, + }, + }, + { + async get() { const { offset, count } = await getPaginationItems(this.queryParams); const { sort, fields: projection, query } = await this.parseJsonQuery(); @@ -124,20 +127,23 @@ API.v1.addRoute( API.v1.addRoute( 'integrations.remove', - { authRequired: true, validateParams: isIntegrationsRemoveProps }, { - async post() { - if ( - !(await hasAtLeastOnePermissionAsync(this.userId, [ + authRequired: true, + validateParams: isIntegrationsRemoveProps, + permissionsRequired: { + POST: { + permissions: [ 'manage-outgoing-integrations', 'manage-own-outgoing-integrations', 'manage-incoming-integrations', 'manage-own-incoming-integrations', - ])) - ) { - return API.v1.unauthorized(); - } - + ], + operation: 'hasAny', + }, + }, + }, + { + async post() { const { bodyParams } = this; let integration: IIntegration | null = null; diff --git a/apps/meteor/tests/end-to-end/api/incoming-integrations.ts b/apps/meteor/tests/end-to-end/api/incoming-integrations.ts index ff475945a9ac6..814f246623bb4 100644 --- a/apps/meteor/tests/end-to-end/api/incoming-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/incoming-integrations.ts @@ -308,7 +308,7 @@ describe('[Incoming Integrations]', () => { }); describe('[/integrations.history]', () => { - it('should return an error when trying to get history of incoming integrations', (done) => { + it('should return an error when trying to get history of incoming integrations if user does NOT have enough permissions', (done) => { void request .get(api('integrations.history')) .set(credentials) @@ -319,7 +319,7 @@ describe('[Incoming Integrations]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -397,25 +397,22 @@ describe('[Incoming Integrations]', () => { }); }); - it('should return unauthorized error when the user does not have any integrations permissions', (done) => { - void updatePermission('manage-incoming-integrations', []).then(() => { - void updatePermission('manage-own-incoming-integrations', []).then(() => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void request - .get(api('integrations.list')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - }); + it('should return unauthorized error when the user does not have any integrations permissions', async () => { + await Promise.all([ + updatePermission('manage-incoming-integrations', []), + updatePermission('manage-own-incoming-integrations', []), + updatePermission('manage-outgoing-integrations', []), + updatePermission('manage-outgoing-integrations', []), + ]); + await request + .get(api('integrations.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); - }); }); }); @@ -578,7 +575,7 @@ describe('[Incoming Integrations]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -597,7 +594,7 @@ describe('[Incoming Integrations]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); diff --git a/apps/meteor/tests/end-to-end/api/outgoing-integrations.ts b/apps/meteor/tests/end-to-end/api/outgoing-integrations.ts index 5ed7f8604db11..76a67c8b330da 100644 --- a/apps/meteor/tests/end-to-end/api/outgoing-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/outgoing-integrations.ts @@ -289,47 +289,42 @@ describe('[Outgoing Integrations]', () => { }); }); - it('should return unauthorized error when the user does not have any integrations permissions', (done) => { - void updatePermission('manage-incoming-integrations', []).then(() => { - void updatePermission('manage-own-incoming-integrations', []).then(() => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void request - .get(api('integrations.list')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - }); + it('should return unauthorized error when the user does not have any integrations permissions', async () => { + await Promise.all([ + updatePermission('manage-incoming-integrations', []), + updatePermission('manage-own-incoming-integrations', []), + updatePermission('manage-outgoing-integrations', []), + updatePermission('manage-outgoing-integrations', []), + ]); + + await request + .get(api('integrations.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); - }); }); }); describe('[/integrations.history]', () => { - it('should return an error when the user DOES NOT the necessary permission', (done) => { - void updatePermission('manage-outgoing-integrations', []).then(() => { - void updatePermission('manage-own-outgoing-integrations', []).then(() => { - void request - .get(api('integrations.history')) - .set(credentials) - .query({ - id: integration._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + it('should return an error when the user DOES NOT the necessary permission', async () => { + await updatePermission('manage-outgoing-integrations', []); + await updatePermission('manage-own-outgoing-integrations', []); + await request + .get(api('integrations.history')) + .set(credentials) + .query({ + id: integration._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); - }); }); it('should return the history of outgoing integrations', (done) => { @@ -467,7 +462,7 @@ describe('[Outgoing Integrations]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -486,7 +481,7 @@ describe('[Outgoing Integrations]', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); From a0114fa25b188edb515916fadce6433df041e628 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Thu, 9 May 2024 17:48:28 -0300 Subject: [PATCH 13/85] chore!: Improve permissions check on oauth-apps endpoints (#32338) Co-authored-by: Marcos Spessatto Defendi --- apps/meteor/app/api/server/v1/oauthapps.ts | 21 +---- apps/meteor/tests/end-to-end/api/oauthapps.ts | 83 +++++++++++++++---- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index 4113b945a4db1..97d489295d424 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -8,13 +8,9 @@ import { API } from '../api'; API.v1.addRoute( 'oauth-apps.list', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['manage-oauth-apps'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { - throw new Error('error-not-allowed'); - } - return API.v1.success({ oauthApps: await OAuthApps.find().toArray(), }); @@ -54,13 +50,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isUpdateOAuthAppParams, + permissionsRequired: ['manage-oauth-apps'], }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { - return API.v1.unauthorized(); - } - const { appId } = this.bodyParams; const result = await Meteor.callAsync('updateOAuthApp', appId, this.bodyParams); @@ -75,13 +68,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isDeleteOAuthAppParams, + permissionsRequired: ['manage-oauth-apps'], }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { - return API.v1.unauthorized(); - } - const { appId } = this.bodyParams; const result = await Meteor.callAsync('deleteOAuthApp', appId); @@ -96,13 +86,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isOauthAppsAddParams, + permissionsRequired: ['manage-oauth-apps'], }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { - return API.v1.unauthorized(); - } - const application = await addOAuthApp(this.bodyParams, this.userId); return API.v1.success({ application }); diff --git a/apps/meteor/tests/end-to-end/api/oauthapps.ts b/apps/meteor/tests/end-to-end/api/oauthapps.ts index db714d1107bdd..5e42069d99341 100644 --- a/apps/meteor/tests/end-to-end/api/oauthapps.ts +++ b/apps/meteor/tests/end-to-end/api/oauthapps.ts @@ -27,10 +27,10 @@ describe('[OAuthApps]', () => { void request .get(api('oauth-apps.list')) .set(credentials) - .expect(400) + .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('error-not-allowed'); + expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); @@ -136,7 +136,11 @@ describe('[OAuthApps]', () => { active: false, }) .expect('Content-Type', 'application/json') - .expect(403); + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]'); + }); await updatePermission('manage-oauth-apps', ['admin']); }); @@ -219,11 +223,12 @@ describe('[OAuthApps]', () => { describe('[/oauth-apps.update]', () => { let appId: IOAuthApps['_id']; - before((done) => { + before(async () => { + await updatePermission('manage-oauth-apps', ['admin']); const name = 'test-oauth-app'; const redirectUri = 'https://test.com'; const active = true; - void request + const res = await request .post(api('oauth-apps.create')) .set(credentials) .send({ @@ -232,12 +237,13 @@ describe('[OAuthApps]', () => { active, }) .expect('Content-Type', 'application/json') - .expect(200) - .end((_err, res) => { - appId = res.body.application._id; - createdAppsIds.push(appId); - done(); - }); + .expect(200); + appId = res.body.application._id; + createdAppsIds.push(appId); + }); + + after(async () => { + await updatePermission('manage-oauth-apps', ['admin']); }); it("should update an app's name, its Active and Redirect URI fields correctly by its id", async () => { @@ -263,16 +269,40 @@ describe('[OAuthApps]', () => { expect(res.body).to.have.property('name', name); }); }); + + it('should fail updating an app if user does NOT have the manage-oauth-apps permission', async () => { + const name = `new app ${Date.now()}`; + const redirectUri = 'http://localhost:3000'; + const active = false; + + await updatePermission('manage-oauth-apps', []); + await request + .post(api(`oauth-apps.update`)) + .set(credentials) + .send({ + appId, + name, + redirectUri, + active, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]'); + }); + }); }); describe('[/oauth-apps.delete]', () => { let appId: IOAuthApps['_id']; - before((done) => { + before(async () => { + await updatePermission('manage-oauth-apps', ['admin']); const name = 'test-oauth-app'; const redirectUri = 'https://test.com'; const active = true; - void request + const res = await request .post(api('oauth-apps.create')) .set(credentials) .send({ @@ -281,11 +311,12 @@ describe('[OAuthApps]', () => { active, }) .expect('Content-Type', 'application/json') - .expect(200) - .end((_err, res) => { - appId = res.body.application._id; - done(); - }); + .expect(200); + appId = res.body.application._id; + }); + + after(async () => { + await updatePermission('manage-oauth-apps', ['admin']); }); it('should delete an app by its id', async () => { @@ -301,5 +332,21 @@ describe('[OAuthApps]', () => { expect(res.body).to.equals(true); }); }); + + it('should fail deleting an app by its id if user does NOT have the manage-oauth-apps permission', async () => { + await updatePermission('manage-oauth-apps', []); + await request + .post(api(`oauth-apps.delete`)) + .set(credentials) + .send({ + appId, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]'); + }); + }); }); }); From 44480d248cdef7873ed8b9d2ab64ad4213ae2452 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Thu, 9 May 2024 17:49:56 -0300 Subject: [PATCH 14/85] chore!: Improve permissions check on teams endpoints (#32351) --- apps/meteor/app/api/server/v1/teams.ts | 17 +- apps/meteor/tests/end-to-end/api/teams.ts | 3489 +++++++++++---------- 2 files changed, 1786 insertions(+), 1720 deletions(-) diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index acb6cba2bac7e..0b534e599ac9e 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -45,13 +45,9 @@ API.v1.addRoute( API.v1.addRoute( 'teams.listAll', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-all-teams'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-all-teams'))) { - return API.v1.unauthorized(); - } - const { offset, count } = await getPaginationItems(this.queryParams); const { records, total } = await Team.listAll({ offset, count }); @@ -68,13 +64,9 @@ API.v1.addRoute( API.v1.addRoute( 'teams.create', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['create-team'] }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'create-team'))) { - return API.v1.unauthorized(); - } - check( this.bodyParams, Match.ObjectIncluding({ @@ -291,10 +283,7 @@ API.v1.addRoute( const allowPrivateTeam: boolean = await hasPermissionAsync(this.userId, 'view-all-teams', team.roomId); - let getAllRooms = false; - if (await hasPermissionAsync(this.userId, 'view-all-team-channels', team.roomId)) { - getAllRooms = true; - } + const getAllRooms = await hasPermissionAsync(this.userId, 'view-all-team-channels', team.roomId); const listFilter = { name: filter ?? undefined, diff --git a/apps/meteor/tests/end-to-end/api/teams.ts b/apps/meteor/tests/end-to-end/api/teams.ts index b630a97b1727c..5aa455ac985b3 100644 --- a/apps/meteor/tests/end-to-end/api/teams.ts +++ b/apps/meteor/tests/end-to-end/api/teams.ts @@ -58,6 +58,14 @@ describe('[Teams]', () => { ]); }); + before(async () => { + return updatePermission('create-team', ['admin', 'user']); + }); + + after(async () => { + return updatePermission('create-team', ['admin', 'user']); + }); + it('should create a public team', (done) => { void request .post(api('teams.create')) @@ -260,7 +268,6 @@ describe('[Teams]', () => { it('should not create a team with no associated room', async () => { const teamName = 'invalid*team*name'; - await request .post(api('teams.create')) .set(credentials) @@ -302,555 +309,756 @@ describe('[Teams]', () => { expect(response.body).to.have.property('error', 'team-does-not-exist'); }); }); - }); - - describe('/teams.convertToChannel', () => { - let testTeam: ITeam; - let channelToEraseId: IRoom['_id']; - let channelToKeepId: IRoom['_id']; - const teamName = `test-team-convert-to-channel-${Date.now()}`; - const channelToEraseName = `${teamName}-channelToErase`; - const channelToKeepName = `${teamName}-channelToKeep`; - before('Create test team', (done) => { - void request + it('should not allow creating a team when the user does NOT have the create-team permission', async () => { + await updatePermission('create-team', []); + await request .post(api('teams.create')) .set(credentials) .send({ - name: teamName, - type: 1, - }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); - }); - - before('create channel (to erase after its team is converted to a channel)', (done) => { - void request - .post(api('channels.create')) - .set(credentials) - .send({ - name: channelToEraseName, + name: `test-team-${Date.now()}`, + type: 0, }) .expect('Content-Type', 'application/json') - .expect(200) + .expect(403) .expect((res) => { - channelToEraseId = res.body.channel._id; - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('channel._id'); - expect(res.body).to.have.nested.property('channel.name', channelToEraseName); - expect(res.body).to.have.nested.property('channel.t', 'c'); - expect(res.body).to.have.nested.property('channel.msgs', 0); - }) - .then(() => done()); + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); }); + }); +}); - before('add first channel to team', (done) => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [channelToEraseId], - teamId: testTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .then(() => done()) - .catch(done); - }); +describe('/teams.convertToChannel', () => { + let testTeam: ITeam; + let channelToEraseId: IRoom['_id']; + let channelToKeepId: IRoom['_id']; + const teamName = `test-team-convert-to-channel-${Date.now()}`; + const channelToEraseName = `${teamName}-channelToErase`; + const channelToKeepName = `${teamName}-channelToKeep`; + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 1, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); - before('create channel (to keep after its team is converted to a channel)', (done) => { - void request - .post(api('channels.create')) - .set(credentials) - .send({ - name: channelToKeepName, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - channelToKeepId = res.body.channel._id; - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('channel._id'); - expect(res.body).to.have.nested.property('channel.name', channelToKeepName); - expect(res.body).to.have.nested.property('channel.t', 'c'); - expect(res.body).to.have.nested.property('channel.msgs', 0); - }) - .then(() => done()); - }); + before('create channel (to erase after its team is converted to a channel)', (done) => { + void request + .post(api('channels.create')) + .set(credentials) + .send({ + name: channelToEraseName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + channelToEraseId = res.body.channel._id; + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('channel._id'); + expect(res.body).to.have.nested.property('channel.name', channelToEraseName); + expect(res.body).to.have.nested.property('channel.t', 'c'); + expect(res.body).to.have.nested.property('channel.msgs', 0); + }) + .then(() => done()); + }); - before('add second channel to team', (done) => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [channelToKeepId], - teamId: testTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .then(() => done()); - }); + before('add first channel to team', (done) => { + void request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [channelToEraseId], + teamId: testTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); + }) + .then(() => done()) + .catch(done); + }); - after(() => - Promise.all([ - deleteTeam(credentials, teamName), - deleteRoom({ type: 'p', roomId: testTeam.roomId }), - deleteRoom({ type: 'c', roomId: channelToKeepId }), - ]), - ); + before('create channel (to keep after its team is converted to a channel)', (done) => { + void request + .post(api('channels.create')) + .set(credentials) + .send({ + name: channelToKeepName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + channelToKeepId = res.body.channel._id; + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('channel._id'); + expect(res.body).to.have.nested.property('channel.name', channelToKeepName); + expect(res.body).to.have.nested.property('channel.t', 'c'); + expect(res.body).to.have.nested.property('channel.msgs', 0); + }) + .then(() => done()); + }); - it('should convert the team to a channel, delete the specified room and move the other back to the workspace', (done) => { - void request - .post(api('teams.convertToChannel')) - .set(credentials) - .send({ - teamName, - roomsToRemove: [channelToEraseId], - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then(() => { - void request - .get(api('channels.info')) - .set(credentials) - .query({ - roomId: channelToEraseId, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error'); - expect(response.body.error).to.include('[error-room-not-found]'); - }); - }) - .then(() => { - void request - .get(api('channels.info')) - .set(credentials) - .query({ - roomId: channelToKeepId, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('channel'); - expect(response.body.channel).to.have.property('_id', channelToKeepId); - expect(response.body.channel).to.not.have.property('teamId'); - }); - }) - .then(() => { + before('add second channel to team', (done) => { + void request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [channelToKeepId], + teamId: testTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); + }) + .then(() => done()); + }); + + after(() => + Promise.all([ + deleteTeam(credentials, teamName), + deleteRoom({ type: 'p', roomId: testTeam.roomId }), + deleteRoom({ type: 'c', roomId: channelToKeepId }), + ]), + ); + + it('should convert the team to a channel, delete the specified room and move the other back to the workspace', (done) => { + void request + .post(api('teams.convertToChannel')) + .set(credentials) + .send({ + teamName, + roomsToRemove: [channelToEraseId], + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then(() => { + void request + .get(api('channels.info')) + .set(credentials) + .query({ + roomId: channelToEraseId, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error'); + expect(response.body.error).to.include('[error-room-not-found]'); + }); + }) + .then(() => { + void request + .get(api('channels.info')) + .set(credentials) + .query({ + roomId: channelToKeepId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('channel'); + expect(response.body.channel).to.have.property('_id', channelToKeepId); + expect(response.body.channel).to.not.have.property('teamId'); + }); + }) + .then(() => { + void request + .get(api('channels.info')) + .set(credentials) + .query({ + roomId: testTeam.roomId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('channel'); + expect(response.body.channel).to.have.property('_id', testTeam.roomId); + expect(response.body.channel).to.not.have.property('teamId'); + expect(response.body.channel).to.not.have.property('teamMain'); + }); + }) + .then(() => done()) + .catch(done); + }); +}); + +describe('/teams.addMembers', () => { + let testTeam: ITeam; + const teamName = `test-team-add-members-${Date.now()}`; + let testUser: TestUser; + let testUser2: TestUser; + + before(async () => { + testUser = await createUser(); + testUser2 = await createUser(); + }); + + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); + + after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + + it('should add members to a public team', (done) => { + void request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam.name, + members: [ + { + userId: testUser._id, + roles: ['member'], + }, + { + userId: testUser2._id, + roles: ['member'], + }, + ], + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then( + () => void request - .get(api('channels.info')) + .get(api('teams.members')) .set(credentials) .query({ - roomId: testTeam.roomId, + teamName: testTeam.name, }) .expect('Content-Type', 'application/json') .expect(200) .expect((response) => { expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('channel'); - expect(response.body.channel).to.have.property('_id', testTeam.roomId); - expect(response.body.channel).to.not.have.property('teamId'); - expect(response.body.channel).to.not.have.property('teamMain'); - }); - }) - .then(() => done()) - .catch(done); - }); + expect(response.body).to.have.property('members'); + expect(response.body.members).to.have.length(3); + expect(response.body.members[1]).to.have.property('user'); + expect(response.body.members[1]).to.have.property('roles'); + expect(response.body.members[1]).to.have.property('createdBy'); + expect(response.body.members[1]).to.have.property('createdAt'); + + const members = (response.body.members as ITeamMemberInfo[]).map(({ user, roles }) => ({ + _id: user._id, + username: user.username, + name: user.name, + roles, + })); + + expect(members).to.deep.own.include({ + _id: testUser._id, + username: testUser.username, + name: testUser.name, + roles: ['member'], + }); + expect(members).to.deep.own.include({ + _id: testUser2._id, + username: testUser2.username, + name: testUser2.name, + roles: ['member'], + }); + }), + ) + .then(() => done()) + .catch(done); }); +}); - describe('/teams.addMembers', () => { - let testTeam: ITeam; - const teamName = `test-team-add-members-${Date.now()}`; - let testUser: TestUser; - let testUser2: TestUser; +describe('/teams.members', () => { + let testTeam: ITeam; + const teamName = `test-team-members-${Date.now()}`; + let testUser: TestUser; + let testUser2: TestUser; - before(async () => { - testUser = await createUser(); - testUser2 = await createUser(); - }); + before(async () => { + testUser = await createUser(); + testUser2 = await createUser(); + }); - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName, - type: 0, - }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); - }); + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); - after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + before('Add members to team', (done) => { + void request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam.name, + members: [ + { + userId: testUser._id, + roles: ['member'], + }, + { + userId: testUser2._id, + roles: ['member'], + }, + ], + }) + .end(done); + }); - it('should add members to a public team', (done) => { - void request - .post(api('teams.addMembers')) - .set(credentials) - .send({ - teamName: testTeam.name, - members: [ - { - userId: testUser._id, - roles: ['member'], - }, - { - userId: testUser2._id, - roles: ['member'], - }, - ], - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then( - () => - void request - .get(api('teams.members')) - .set(credentials) - .query({ - teamName: testTeam.name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('members'); - expect(response.body.members).to.have.length(3); - expect(response.body.members[1]).to.have.property('user'); - expect(response.body.members[1]).to.have.property('roles'); - expect(response.body.members[1]).to.have.property('createdBy'); - expect(response.body.members[1]).to.have.property('createdAt'); - - const members = (response.body.members as ITeamMemberInfo[]).map(({ user, roles }) => ({ - _id: user._id, - username: user.username, - name: user.name, - roles, - })); - - expect(members).to.deep.own.include({ - _id: testUser._id, - username: testUser.username, - name: testUser.name, - roles: ['member'], - }); - expect(members).to.deep.own.include({ - _id: testUser2._id, - username: testUser2.username, - name: testUser2.name, - roles: ['member'], - }); - }), - ) - .then(() => done()) - .catch(done); - }); + after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + + it('should list all the members from a public team', (done) => { + void request + .get(api('teams.members')) + .set(credentials) + .query({ + teamName: testTeam.name, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count', 3); + expect(res.body).to.have.property('offset', 0); + expect(res.body).to.have.property('total', 3); + expect(res.body).to.have.property('members'); + expect(res.body.members).to.have.length(3); + expect(res.body.members[0]).to.have.property('user'); + expect(res.body.members[0]).to.have.property('roles'); + expect(res.body.members[0]).to.have.property('createdBy'); + expect(res.body.members[0]).to.have.property('createdAt'); + expect(res.body.members[0].user).to.have.property('_id'); + expect(res.body.members[0].user).to.have.property('username'); + expect(res.body.members[0].user).to.have.property('name'); + expect(res.body.members[0].user).to.have.property('status'); + expect(res.body.members[0].createdBy).to.have.property('_id'); + expect(res.body.members[0].createdBy).to.have.property('username'); + }) + .end(done); }); +}); - describe('/teams.members', () => { - let testTeam: ITeam; - const teamName = `test-team-members-${Date.now()}`; - let testUser: TestUser; - let testUser2: TestUser; +describe('/teams.list', () => { + const teamName = `test-team-list-${Date.now()}`; + + let testUser1: TestUser; + let testUser1Credentials: Credentials; + let testTeamAdmin: TestUser; + let testTeam1: IRoom; + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end(done); + }); - before(async () => { - testUser = await createUser(); - testUser2 = await createUser(); - }); + before('Create test users', async () => { + testUser1 = await createUser(); + }); - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName, - type: 0, - }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); + before('login test users', async () => { + testUser1Credentials = await login(testUser1.username, password); + }); + + before('Create test team', async () => { + await request.post(api('teams.create')).set(credentials).send({ + name: teamName, + type: 0, }); - before('Add members to team', (done) => { - void request - .post(api('teams.addMembers')) - .set(credentials) - .send({ - teamName: testTeam.name, - members: [ - { - userId: testUser._id, - roles: ['member'], - }, - { - userId: testUser2._id, - roles: ['member'], - }, - ], - }) - .end(done); - }); - - after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + const team1Name = `test-team-1-${Date.now()}`; + const teamAdminName = `test-team-admin-${Date.now()}`; - it('should list all the members from a public team', (done) => { - void request - .get(api('teams.members')) - .set(credentials) - .query({ - teamName: testTeam.name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count', 3); - expect(res.body).to.have.property('offset', 0); - expect(res.body).to.have.property('total', 3); - expect(res.body).to.have.property('members'); - expect(res.body.members).to.have.length(3); - expect(res.body.members[0]).to.have.property('user'); - expect(res.body.members[0]).to.have.property('roles'); - expect(res.body.members[0]).to.have.property('createdBy'); - expect(res.body.members[0]).to.have.property('createdAt'); - expect(res.body.members[0].user).to.have.property('_id'); - expect(res.body.members[0].user).to.have.property('username'); - expect(res.body.members[0].user).to.have.property('name'); - expect(res.body.members[0].user).to.have.property('status'); - expect(res.body.members[0].createdBy).to.have.property('_id'); - expect(res.body.members[0].createdBy).to.have.property('username'); - }) - .end(done); - }); + testTeam1 = ( + await request.post(api('teams.create')).set(testUser1Credentials).send({ + name: team1Name, + type: 0, + }) + ).body.team; + testTeamAdmin = ( + await request.post(api('teams.create')).set(credentials).send({ + name: teamAdminName, + type: 0, + }) + ).body.team; }); - describe('/teams.list', () => { - const teamName = `test-team-list-${Date.now()}`; + after(() => + Promise.all([ + deleteTeam(credentials, teamName), + deleteTeam(testUser1Credentials, testTeam1.name!), + deleteTeam(credentials, testTeamAdmin.name!), + ]), + ); + + after('delete test users', () => deleteUser(testUser1)); + + it('should list all teams', (done) => { + void request + .get(api('teams.list')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('offset', 0); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('teams'); + expect(res.body.teams.length).to.be.gte(1); + expect(res.body.teams[0]).to.have.property('_id'); + expect(res.body.teams[0]).to.have.property('_updatedAt'); + expect(res.body.teams[0]).to.have.property('name'); + expect(res.body.teams[0]).to.have.property('type'); + expect(res.body.teams[0]).to.have.property('roomId'); + expect(res.body.teams[0]).to.have.property('createdBy'); + expect(res.body.teams[0].createdBy).to.have.property('_id'); + expect(res.body.teams[0].createdBy).to.have.property('username'); + expect(res.body.teams[0]).to.have.property('createdAt'); + expect(res.body.teams[0]).to.have.property('rooms'); + expect(res.body.teams[0]).to.have.property('numberOfUsers'); + }) + .end(done); + }); - let testUser1: TestUser; - let testUser1Credentials: Credentials; - let testTeamAdmin: TestUser; - let testTeam1: IRoom; - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName, - type: 0, - }) - .end(done); - }); + it("should prevent users from accessing unrelated teams via 'query' parameter", () => { + return request + .get(api('teams.list')) + .set(testUser1Credentials) + .query({ + query: JSON.stringify({ _id: { $regex: '.*' } }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body.teams.length).to.be.gte(1); + expect(res.body.teams) + .to.be.an('array') + .and.to.satisfy( + (teams: ITeam[]) => teams.every((team) => team.createdBy._id === testUser1._id), + `Expected only user's own teams to be returned, but found unowned teams.\n${JSON.stringify( + res.body.teams.filter((team: ITeam) => team.createdBy._id !== testUser1._id), + null, + 2, + )}`, + ); + }); + }); - before('Create test users', async () => { - testUser1 = await createUser(); - }); + it("should prevent admins from accessing unrelated teams via 'query' parameter", () => { + return request + .get(api('teams.list')) + .set(credentials) + .query({ + query: JSON.stringify({ _id: { $regex: '.*' } }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body.teams.length).to.be.gte(1); + expect(res.body.teams) + .to.be.an('array') + .and.to.satisfy( + (teams: ITeam[]) => teams.every((team) => team.createdBy._id === credentials['X-User-Id']), + `Expected only admin's own teams to be returned, but found unowned teams.\n${JSON.stringify( + res.body.teams.filter((team: ITeam) => team.createdBy._id !== credentials['X-User-Id']), + null, + 2, + )}`, + ); + }); + }); +}); - before('login test users', async () => { - testUser1Credentials = await login(testUser1.username, password); +describe('/teams.listAll', () => { + let teamName: string; + before(async () => { + await updatePermission('view-all-teams', ['admin']); + teamName = `test-team-${Date.now()}`; + await request.post(api('teams.create')).set(credentials).send({ + name: teamName, + type: 0, }); + }); - before('Create test team', async () => { - await request.post(api('teams.create')).set(credentials).send({ - name: teamName, - type: 0, + after(() => Promise.all([deleteTeam(credentials, teamName), updatePermission('view-all-teams', ['admin'])])); + + it('should list all teams', async () => { + await request + .get(api('teams.listAll')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('offset', 0); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('teams'); + expect(res.body.teams).to.be.an('array').that.is.not.empty; + expect(res.body.teams[0]).to.have.property('_id'); + expect(res.body.teams[0]).to.have.property('_updatedAt'); + expect(res.body.teams[0]).to.have.property('name'); + expect(res.body.teams[0]).to.have.property('type'); + expect(res.body.teams[0]).to.have.property('roomId'); + expect(res.body.teams[0]).to.have.property('createdBy'); + expect(res.body.teams[0].createdBy).to.have.property('_id'); + expect(res.body.teams[0].createdBy).to.have.property('username'); + expect(res.body.teams[0]).to.have.property('createdAt'); + expect(res.body.teams[0]).to.have.property('rooms'); + expect(res.body.teams[0]).to.have.property('numberOfUsers'); }); + }); - const team1Name = `test-team-1-${Date.now()}`; - const teamAdminName = `test-team-admin-${Date.now()}`; + it('should return an error when the user does NOT have the view-all-teams permission', async () => { + await updatePermission('view-all-teams', []); + await request + .get(api('teams.listAll')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); +}); - testTeam1 = ( - await request.post(api('teams.create')).set(testUser1Credentials).send({ - name: team1Name, - type: 0, - }) - ).body.team; - testTeamAdmin = ( - await request.post(api('teams.create')).set(credentials).send({ - name: teamAdminName, - type: 0, - }) - ).body.team; - }); +describe('/teams.updateMember', () => { + let testTeam: ITeam; + const teamName = `test-team-update-member-${Date.now()}`; + let testUser: TestUser; + let testUser2: TestUser; - after(() => - Promise.all([ - deleteTeam(credentials, teamName), - deleteTeam(testUser1Credentials, testTeam1.name!), - deleteTeam(credentials, testTeamAdmin.name!), - ]), - ); + before(async () => { + testUser = await createUser(); + testUser2 = await createUser(); + }); - after('delete test users', () => deleteUser(testUser1)); + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); + before('Add members to team', (done) => { + void request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam.name, + members: [ + { + userId: testUser._id, + roles: ['member'], + }, + { + userId: testUser2._id, + roles: ['member'], + }, + ], + }) + .end(done); + }); - it('should list all teams', (done) => { - void request - .get(api('teams.list')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('offset', 0); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('teams'); - expect(res.body.teams.length).to.be.gte(1); - expect(res.body.teams[0]).to.have.property('_id'); - expect(res.body.teams[0]).to.have.property('_updatedAt'); - expect(res.body.teams[0]).to.have.property('name'); - expect(res.body.teams[0]).to.have.property('type'); - expect(res.body.teams[0]).to.have.property('roomId'); - expect(res.body.teams[0]).to.have.property('createdBy'); - expect(res.body.teams[0].createdBy).to.have.property('_id'); - expect(res.body.teams[0].createdBy).to.have.property('username'); - expect(res.body.teams[0]).to.have.property('createdAt'); - expect(res.body.teams[0]).to.have.property('rooms'); - expect(res.body.teams[0]).to.have.property('numberOfUsers'); - }) - .end(done); - }); + after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + + it("should update member's data in a public team", (done) => { + void request + .post(api('teams.updateMember')) + .set(credentials) + .send({ + teamName: testTeam.name, + member: { + userId: testUser._id, + roles: ['member', 'owner'], + }, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then( + () => + void request + .get(api('teams.members')) + .set(credentials) + .query({ + teamName: testTeam.name, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('members'); + expect(response.body.members).to.have.length(3); + + const members = (response.body.members as ITeamMemberInfo[]).map(({ user, roles }) => ({ + _id: user._id, + username: user.username, + name: user.name, + roles, + })); + + expect(members).to.deep.own.include({ + _id: testUser._id, + username: testUser.username, + name: testUser.name, + roles: ['member', 'owner'], + }); + }), + ) + .then(() => done()) + .catch(done); + }); +}); - it("should prevent users from accessing unrelated teams via 'query' parameter", () => { - return request - .get(api('teams.list')) - .set(testUser1Credentials) - .query({ - query: JSON.stringify({ _id: { $regex: '.*' } }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body.teams.length).to.be.gte(1); - expect(res.body.teams) - .to.be.an('array') - .and.to.satisfy( - (teams: ITeam[]) => teams.every((team) => team.createdBy._id === testUser1._id), - `Expected only user's own teams to be returned, but found unowned teams.\n${JSON.stringify( - res.body.teams.filter((team: ITeam) => team.createdBy._id !== testUser1._id), - null, - 2, - )}`, - ); - }); - }); +describe('/teams.removeMember', () => { + let testTeam: ITeam; + const teamName = `test-team-remove-member-${Date.now()}`; + let testUser: TestUser; + let testUser2: TestUser; - it("should prevent admins from accessing unrelated teams via 'query' parameter", () => { - return request - .get(api('teams.list')) - .set(credentials) - .query({ - query: JSON.stringify({ _id: { $regex: '.*' } }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body.teams.length).to.be.gte(1); - expect(res.body.teams) - .to.be.an('array') - .and.to.satisfy( - (teams: ITeam[]) => teams.every((team) => team.createdBy._id === credentials['X-User-Id']), - `Expected only admin's own teams to be returned, but found unowned teams.\n${JSON.stringify( - res.body.teams.filter((team: ITeam) => team.createdBy._id !== credentials['X-User-Id']), - null, - 2, - )}`, - ); - }); - }); + before(async () => { + testUser = await createUser(); + testUser2 = await createUser(); }); - describe('/teams.updateMember', () => { - let testTeam: ITeam; - const teamName = `test-team-update-member-${Date.now()}`; - let testUser: TestUser; - let testUser2: TestUser; - - before(async () => { - testUser = await createUser(); - testUser2 = await createUser(); - }); + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName, - type: 0, - }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); - }); - before('Add members to team', (done) => { - void request - .post(api('teams.addMembers')) - .set(credentials) - .send({ - teamName: testTeam.name, - members: [ - { - userId: testUser._id, - roles: ['member'], - }, - { - userId: testUser2._id, - roles: ['member'], - }, - ], - }) - .end(done); - }); + after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + + it('should not be able to remove the last owner', (done) => { + void request + .post(api('teams.removeMember')) + .set(credentials) + .send({ + teamName: testTeam.name, + userId: credentials['X-User-Id'], + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('last-owner-can-not-be-removed'); + }) + .then(() => done()) + .catch(done); + }); - after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + it('should not be able to remove if rooms is empty', (done) => { + void request + .post(api('teams.removeMember')) + .set(credentials) + .send({ + teamName: testTeam.name, + userId: credentials['X-User-Id'], + rooms: [], + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.errorType).to.be.equal('invalid-params'); + }) + .then(() => done()) + .catch(done); + }); - it("should update member's data in a public team", (done) => { - void request - .post(api('teams.updateMember')) - .set(credentials) - .send({ - teamName: testTeam.name, - member: { + it('should remove one member from a public team', (done) => { + void request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam.name, + members: [ + { userId: testUser._id, + roles: ['member'], + }, + { + userId: testUser2._id, roles: ['member', 'owner'], }, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then( - () => - void request + ], + }) + .then(() => + request + .post(api('teams.removeMember')) + .set(credentials) + .send({ + teamName: testTeam.name, + userId: testUser2._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then(() => + request .get(api('teams.members')) .set(credentials) .query({ @@ -861,1357 +1069,789 @@ describe('[Teams]', () => { .expect((response) => { expect(response.body).to.have.property('success', true); expect(response.body).to.have.property('members'); - expect(response.body.members).to.have.length(3); - - const members = (response.body.members as ITeamMemberInfo[]).map(({ user, roles }) => ({ - _id: user._id, - username: user.username, - name: user.name, - roles, - })); - - expect(members).to.deep.own.include({ - _id: testUser._id, - username: testUser.username, - name: testUser.name, - roles: ['member', 'owner'], - }); + expect(response.body.members).to.have.length(2); }), - ) - .then(() => done()) - .catch(done); - }); + ) + .then(() => done()), + ) + .catch(done); }); +}); - describe('/teams.removeMember', () => { - let testTeam: ITeam; - const teamName = `test-team-remove-member-${Date.now()}`; - let testUser: TestUser; - let testUser2: TestUser; +describe('/teams.leave', () => { + let testTeam: ITeam; + const teamName = `test-team-leave-${Date.now()}`; + let testUser: TestUser; + let testUser2: TestUser; - before(async () => { - testUser = await createUser(); - testUser2 = await createUser(); - }); + before(async () => { + testUser = await createUser(); + testUser2 = await createUser(); + }); + + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); + + after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + + it('should not be able to remove the last owner', (done) => { + request + .post(api('teams.leave')) + .set(credentials) + .send({ + teamName: testTeam.name, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('last-owner-can-not-be-removed'); + }) + .then(() => done()) + .catch(done); + }); + + it('should remove the calling user from the team', (done) => { + request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam.name, + members: [ + { + userId: testUser._id, + roles: ['member'], + }, + { + userId: testUser2._id, + roles: ['member', 'owner'], + }, + ], + }) + .then(() => + request + .post(api('teams.leave')) + .set(credentials) + .send({ + teamName: testTeam.name, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then(() => done()), + ) + .catch(done); + }); + + it('should not be able to leave if rooms is empty', (done) => { + request + .post(api('teams.leave')) + .set(credentials) + .send({ + teamName: testTeam.name, + userId: credentials['X-User-Id'], + rooms: [], + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.errorType).to.be.equal('invalid-params'); + }) + .then(() => done()) + .catch(done); + }); +}); + +describe('/teams.info', () => { + const teamName = `test-team-info-${Date.now()}`; + let testTeam: ITeam; + let testTeam2: ITeam; + let testUser: TestUser; + let testUserCredentials: Credentials; + + before(async () => { + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + testTeam = await createTeam(credentials, teamName, TEAM_TYPE.PUBLIC); + testTeam2 = await createTeam(credentials, `${teamName}-2`, TEAM_TYPE.PRIVATE); + }); + + after(() => Promise.all([deleteTeam(credentials, testTeam.name), deleteTeam(credentials, testTeam2.name), deleteUser(testUser)])); + + it('should successfully get a team info by name', (done) => { + request + .get(api('teams.info')) + .set(credentials) + .query({ + teamName: testTeam.name, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('teamInfo'); + expect(response.body.teamInfo).to.have.property('_id', testTeam._id); + expect(response.body.teamInfo).to.have.property('name', testTeam.name); + }) + .then(() => done()) + .catch(done); + }); + it('should successfully get a team info by id', (done) => { + request + .get(api('teams.info')) + .set(credentials) + .query({ + teamId: testTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('teamInfo'); + expect(response.body.teamInfo).to.have.property('_id', testTeam._id); + expect(response.body.teamInfo).to.have.property('name', testTeam.name); + }) + .then(() => done()) + .catch(done); + }); + it('should fail if a team is not found', (done) => { + request + .get(api('teams.info')) + .set(credentials) + .query({ + teamName: '', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error', 'Team not found'); + }) + .then(() => done()) + .catch(done); + }); + it('should fail if a user doesnt belong to a team', (done) => { + request + .get(api('teams.info')) + .set(testUserCredentials) + .query({ + teamName: testTeam2.name, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error', 'unauthorized'); + }) + .then(() => done()) + .catch(done); + }); +}); - before('Create test team', (done) => { +describe('/teams.delete', () => { + describe('deleting an empty team', () => { + let roomId: IRoom['_id']; + const tempTeamName = `temporaryTeam-${Date.now()}`; + + before('create team', (done) => { void request .post(api('teams.create')) .set(credentials) .send({ - name: teamName, + name: tempTeamName, type: 0, }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); + .expect('Content-Type', 'application/json') + .expect(200) + .expect((resp) => { + expect(resp.body).to.have.property('success', true); + expect(resp.body).to.have.property('team'); + expect(resp.body.team).to.have.property('name', tempTeamName); + expect(resp.body.team).to.have.property('_id'); + expect(resp.body.team).to.have.property('roomId'); + + roomId = resp.body.team.roomId; + }) + .then(() => done()); }); - after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); + after(() => deleteTeam(credentials, tempTeamName)); - it('should not be able to remove the last owner', (done) => { - void request - .post(api('teams.removeMember')) + it('should delete the team and the main room', (done) => { + request + .post(api('teams.delete')) .set(credentials) .send({ - teamName: testTeam.name, - userId: credentials['X-User-Id'], + teamName: tempTeamName, }) .expect('Content-Type', 'application/json') - .expect(400) + .expect(200) .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('last-owner-can-not-be-removed'); + expect(res.body).to.have.property('success', true); + }) + .then(() => { + void request + .get(api('teams.info')) + .set(credentials) + .query({ + teamName: tempTeamName, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error'); + expect(response.body.error).to.be.equal('Team not found'); + }) + .then(() => { + void request + .get(api('channels.info')) + .set(credentials) + .query({ + roomId, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error'); + expect(response.body.error).to.include('[error-room-not-found]'); + }) + .then(() => done()); + }); }) - .then(() => done()) .catch(done); }); + }); + + describe('delete team with two rooms', () => { + const tempTeamName = `temporaryTeam-${Date.now()}`; + const channel1Name = `${tempTeamName}-channel1`; + const channel2Name = `${tempTeamName}-channel2`; + let teamId: ITeam['_id']; + let channel1Id: IRoom['_id']; + let channel2Id: IRoom['_id']; - it('should not be able to remove if rooms is empty', (done) => { + before('create team', (done) => { void request - .post(api('teams.removeMember')) + .post(api('teams.create')) .set(credentials) .send({ - teamName: testTeam.name, - userId: credentials['X-User-Id'], - rooms: [], + name: tempTeamName, + type: 0, }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.errorType).to.be.equal('invalid-params'); + .then((response) => { + teamId = response.body.team._id; }) - .then(() => done()) - .catch(done); + .then(() => done()); }); - it('should remove one member from a public team', (done) => { + before('create channel 1', (done) => { void request - .post(api('teams.addMembers')) + .post(api('channels.create')) .set(credentials) .send({ - teamName: testTeam.name, - members: [ - { - userId: testUser._id, - roles: ['member'], - }, - { - userId: testUser2._id, - roles: ['member', 'owner'], - }, - ], + name: channel1Name, }) - .then(() => - request - .post(api('teams.removeMember')) - .set(credentials) - .send({ - teamName: testTeam.name, - userId: testUser2._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then(() => - request - .get(api('teams.members')) - .set(credentials) - .query({ - teamName: testTeam.name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('members'); - expect(response.body.members).to.have.length(2); - }), - ) - .then(() => done()), - ) - .catch(done); + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + channel1Id = res.body.channel._id; + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('channel._id'); + expect(res.body).to.have.nested.property('channel.name', channel1Name); + expect(res.body).to.have.nested.property('channel.t', 'c'); + expect(res.body).to.have.nested.property('channel.msgs', 0); + }) + .then(() => done()); }); - }); - describe('/teams.leave', () => { - let testTeam: ITeam; - const teamName = `test-team-leave-${Date.now()}`; - let testUser: TestUser; - let testUser2: TestUser; - - before(async () => { - testUser = await createUser(); - testUser2 = await createUser(); + before('add channel 1 to team', (done) => { + request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [channel1Id], + teamId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', teamId); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); + }) + .then(() => done()) + .catch(done); }); - before('Create test team', (done) => { + before('create channel 2', (done) => { void request - .post(api('teams.create')) + .post(api('channels.create')) .set(credentials) .send({ - name: teamName, - type: 0, + name: channel2Name, }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + channel2Id = res.body.channel._id; + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('channel._id'); + expect(res.body).to.have.nested.property('channel.name', channel2Name); + expect(res.body).to.have.nested.property('channel.t', 'c'); + expect(res.body).to.have.nested.property('channel.msgs', 0); + }) + .then(() => done()); }); - after(() => Promise.all([deleteUser(testUser), deleteUser(testUser2), deleteTeam(credentials, teamName)])); - - it('should not be able to remove the last owner', (done) => { - request - .post(api('teams.leave')) + before('add channel 2 to team', (done) => { + void request + .post(api('teams.addRooms')) .set(credentials) .send({ - teamName: testTeam.name, + rooms: [channel2Id], + teamId, }) .expect('Content-Type', 'application/json') - .expect(400) + .expect(200) .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('last-owner-can-not-be-removed'); + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', teamId); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); }) - .then(() => done()) - .catch(done); + .then(() => done()); }); - it('should remove the calling user from the team', (done) => { + after(() => deleteRoom({ type: 'c', roomId: channel1Id })); + + it('should delete the specified room and move the other back to the workspace', (done) => { request - .post(api('teams.addMembers')) + .post(api('teams.delete')) .set(credentials) .send({ - teamName: testTeam.name, - members: [ - { - userId: testUser._id, - roles: ['member'], - }, - { - userId: testUser2._id, - roles: ['member', 'owner'], - }, - ], + teamName: tempTeamName, + roomsToRemove: [channel2Id], + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); }) - .then(() => - request - .post(api('teams.leave')) + .then(() => { + void request + .get(api('channels.info')) .set(credentials) - .send({ - teamName: testTeam.name, + .query({ + roomId: channel2Id, }) .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); + .expect(400) + .expect((response) => { + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error'); + expect(response.body.error).to.include('[error-room-not-found]'); }) - .then(() => done()), - ) + .then(() => { + void request + .get(api('channels.info')) + .set(credentials) + .query({ + roomId: channel1Id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((response) => { + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('channel'); + expect(response.body.channel).to.have.property('_id', channel1Id); + expect(response.body.channel).to.not.have.property('teamId'); + }) + .then(() => done()); + }); + }) .catch(done); }); + }); +}); - it('should not be able to leave if rooms is empty', (done) => { - request - .post(api('teams.leave')) +describe('/teams.addRooms', () => { + let privateRoom: IRoom & { t: 'p' }; + let privateRoom2: IRoom & { t: 'p' }; + let privateRoom3: IRoom & { t: 'p' }; + let publicRoom: IRoom & { t: 'c' }; + let publicRoom2: IRoom & { t: 'c' }; + let publicTeam: ITeam; + let privateTeam: ITeam; + let testUser: TestUser; + let testUserCredentials: Credentials; + + before(async () => { + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + privateRoom = (await createRoom({ type: 'p', name: `community-channel-private-1-${Date.now()}` })).body.group; + privateRoom2 = (await createRoom({ type: 'p', name: `community-channel-private-2-${Date.now()}` })).body.group; + privateRoom3 = (await createRoom({ type: 'p', name: `community-channel-private-3-${Date.now()}` })).body.group; + publicRoom = (await createRoom({ type: 'c', name: `community-channel-public-1-${Date.now()}` })).body.channel; + publicRoom2 = (await createRoom({ type: 'c', name: `community-channel-public-2-${Date.now()}` })).body.channel; + publicTeam = await createTeam(credentials, `team-name-c-${Date.now()}`, TEAM_TYPE.PUBLIC); + privateTeam = await createTeam(credentials, `team-name-p-${Date.now()}`, TEAM_TYPE.PRIVATE); + }); + + after(async () => { + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + await Promise.all([ + updatePermission('add-team-channel', ['admin', 'owner', 'moderator']), + ...[privateRoom, privateRoom2, privateRoom3, publicRoom, publicRoom2].map((room) => deleteRoom({ type: room.t, roomId: room._id })), + deleteUser(testUser), + ]); + }); + + it('should throw an error if no permission', (done) => { + void updatePermission('add-team-channel', []).then(() => { + void request + .post(api('teams.addRooms')) .set(credentials) .send({ - teamName: testTeam.name, - userId: credentials['X-User-Id'], - rooms: [], + rooms: [publicRoom._id], + teamId: publicTeam._id, }) .expect('Content-Type', 'application/json') - .expect(400) + .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error'); - expect(res.body.errorType).to.be.equal('invalid-params'); + expect(res.body.error).to.be.equal('error-no-permission-team-channel'); }) - .then(() => done()) - .catch(done); + .end(done); }); }); - describe('/teams.info', () => { - const teamName = `test-team-info-${Date.now()}`; - let testTeam: ITeam; - let testTeam2: ITeam; - let testUser: TestUser; - let testUserCredentials: Credentials; - - before(async () => { - testUser = await createUser(); - testUserCredentials = await login(testUser.username, password); - testTeam = await createTeam(credentials, teamName, TEAM_TYPE.PUBLIC); - testTeam2 = await createTeam(credentials, `${teamName}-2`, TEAM_TYPE.PRIVATE); - }); - - after(() => Promise.all([deleteTeam(credentials, testTeam.name), deleteTeam(credentials, testTeam2.name), deleteUser(testUser)])); - - it('should successfully get a team info by name', (done) => { - request - .get(api('teams.info')) + it('should add public and private rooms to team', (done) => { + void updatePermission('add-team-channel', ['admin']).then(() => { + void request + .post(api('teams.addRooms')) .set(credentials) - .query({ - teamName: testTeam.name, + .send({ + rooms: [publicRoom._id, privateRoom._id], + teamId: publicTeam._id, }) .expect('Content-Type', 'application/json') .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('teamInfo'); - expect(response.body.teamInfo).to.have.property('_id', testTeam._id); - expect(response.body.teamInfo).to.have.property('name', testTeam.name); + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.have.length(2); + expect(res.body.rooms[0]).to.have.property('_id'); + expect(res.body.rooms[0]).to.have.property('teamId', publicTeam._id); + expect(res.body.rooms[1]).to.have.property('_id'); + expect(res.body.rooms[1]).to.have.property('teamId', publicTeam._id); + + const rids = (res.body.rooms as IRoom[]).map(({ _id }) => _id); + + expect(rids).to.include(publicRoom._id); + expect(rids).to.include(privateRoom._id); }) - .then(() => done()) - .catch(done); + .end(done); }); - it('should successfully get a team info by id', (done) => { - request - .get(api('teams.info')) + }); + + it('should add public room to private team', (done) => { + void updatePermission('add-team-channel', ['admin']).then(() => { + void request + .post(api('teams.addRooms')) .set(credentials) - .query({ - teamId: testTeam._id, + .send({ + rooms: [publicRoom2._id], + teamId: privateTeam._id, }) .expect('Content-Type', 'application/json') .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('teamInfo'); - expect(response.body.teamInfo).to.have.property('_id', testTeam._id); - expect(response.body.teamInfo).to.have.property('name', testTeam.name); + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', privateTeam._id); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); }) - .then(() => done()) - .catch(done); + .end(done); }); - it('should fail if a team is not found', (done) => { - request - .get(api('teams.info')) + }); + + it('should add private room to team', (done) => { + void updatePermission('add-team-channel', ['admin']).then(() => { + void request + .post(api('teams.addRooms')) .set(credentials) - .query({ - teamName: '', - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error', 'Team not found'); - }) - .then(() => done()) - .catch(done); - }); - it('should fail if a user doesnt belong to a team', (done) => { - request - .get(api('teams.info')) - .set(testUserCredentials) - .query({ - teamName: testTeam2.name, + .send({ + rooms: [privateRoom2._id], + teamId: privateTeam._id, }) .expect('Content-Type', 'application/json') - .expect(403) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error', 'unauthorized'); + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms[0]).to.have.property('teamId', privateTeam._id); + expect(res.body.rooms[0]).to.not.have.property('teamDefault'); }) - .then(() => done()) - .catch(done); + .end(done); }); }); - describe('/teams.delete', () => { - describe('deleting an empty team', () => { - let roomId: IRoom['_id']; - const tempTeamName = `temporaryTeam-${Date.now()}`; - - before('create team', (done) => { + it('should fail if the user cannot access the channel', (done) => { + void updatePermission('add-team-channel', ['admin', 'user']) + .then(() => { void request - .post(api('teams.create')) - .set(credentials) + .post(api('teams.addRooms')) + .set(testUserCredentials) .send({ - name: tempTeamName, - type: 0, + rooms: [privateRoom3._id], + teamId: privateTeam._id, }) .expect('Content-Type', 'application/json') - .expect(200) - .expect((resp) => { - expect(resp.body).to.have.property('success', true); - expect(resp.body).to.have.property('team'); - expect(resp.body.team).to.have.property('name', tempTeamName); - expect(resp.body.team).to.have.property('_id'); - expect(resp.body.team).to.have.property('roomId'); - - roomId = resp.body.team.roomId; + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('invalid-room'); }) - .then(() => done()); - }); - - after(() => deleteTeam(credentials, tempTeamName)); + .end(done); + }) + .catch(done); + }); - it('should delete the team and the main room', (done) => { - request - .post(api('teams.delete')) - .set(credentials) + it('should fail if the user is not the owner of the channel', (done) => { + void request + .post(methodCall('addUsersToRoom')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'addUsersToRoom', + params: [{ rid: privateRoom3._id, users: [testUser.username] }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then(() => { + void request + .post(api('teams.addRooms')) + .set(testUserCredentials) .send({ - teamName: tempTeamName, + rooms: [privateRoom3._id], + teamId: privateTeam._id, }) .expect('Content-Type', 'application/json') - .expect(200) + .expect(400) .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then(() => { - void request - .get(api('teams.info')) - .set(credentials) - .query({ - teamName: tempTeamName, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error'); - expect(response.body.error).to.be.equal('Team not found'); - }) - .then(() => { - void request - .get(api('channels.info')) - .set(credentials) - .query({ - roomId, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error'); - expect(response.body.error).to.include('[error-room-not-found]'); - }) - .then(() => done()); - }); - }) - .catch(done); - }); - }); - - describe('delete team with two rooms', () => { - const tempTeamName = `temporaryTeam-${Date.now()}`; - const channel1Name = `${tempTeamName}-channel1`; - const channel2Name = `${tempTeamName}-channel2`; - let teamId: ITeam['_id']; - let channel1Id: IRoom['_id']; - let channel2Id: IRoom['_id']; - - before('create team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: tempTeamName, - type: 0, - }) - .then((response) => { - teamId = response.body.team._id; - }) - .then(() => done()); - }); - - before('create channel 1', (done) => { - void request - .post(api('channels.create')) - .set(credentials) - .send({ - name: channel1Name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - channel1Id = res.body.channel._id; - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('channel._id'); - expect(res.body).to.have.nested.property('channel.name', channel1Name); - expect(res.body).to.have.nested.property('channel.t', 'c'); - expect(res.body).to.have.nested.property('channel.msgs', 0); - }) - .then(() => done()); - }); - - before('add channel 1 to team', (done) => { - request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [channel1Id], - teamId, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', teamId); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .then(() => done()) - .catch(done); - }); - - before('create channel 2', (done) => { - void request - .post(api('channels.create')) - .set(credentials) - .send({ - name: channel2Name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - channel2Id = res.body.channel._id; - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('channel._id'); - expect(res.body).to.have.nested.property('channel.name', channel2Name); - expect(res.body).to.have.nested.property('channel.t', 'c'); - expect(res.body).to.have.nested.property('channel.msgs', 0); - }) - .then(() => done()); - }); - - before('add channel 2 to team', (done) => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [channel2Id], - teamId, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', teamId); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .then(() => done()); - }); - - after(() => deleteRoom({ type: 'c', roomId: channel1Id })); - - it('should delete the specified room and move the other back to the workspace', (done) => { - request - .post(api('teams.delete')) - .set(credentials) - .send({ - teamName: tempTeamName, - roomsToRemove: [channel2Id], - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .then(() => { - void request - .get(api('channels.info')) - .set(credentials) - .query({ - roomId: channel2Id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((response) => { - expect(response.body).to.have.property('success', false); - expect(response.body).to.have.property('error'); - expect(response.body.error).to.include('[error-room-not-found]'); - }) - .then(() => { - void request - .get(api('channels.info')) - .set(credentials) - .query({ - roomId: channel1Id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((response) => { - expect(response.body).to.have.property('success', true); - expect(response.body).to.have.property('channel'); - expect(response.body.channel).to.have.property('_id', channel1Id); - expect(response.body.channel).to.not.have.property('teamId'); - }) - .then(() => done()); - }); - }) - .catch(done); - }); - }); - }); - - describe('/teams.addRooms', () => { - let privateRoom: IRoom & { t: 'p' }; - let privateRoom2: IRoom & { t: 'p' }; - let privateRoom3: IRoom & { t: 'p' }; - let publicRoom: IRoom & { t: 'c' }; - let publicRoom2: IRoom & { t: 'c' }; - let publicTeam: ITeam; - let privateTeam: ITeam; - let testUser: TestUser; - let testUserCredentials: Credentials; - - before(async () => { - testUser = await createUser(); - testUserCredentials = await login(testUser.username, password); - privateRoom = (await createRoom({ type: 'p', name: `community-channel-private-1-${Date.now()}` })).body.group; - privateRoom2 = (await createRoom({ type: 'p', name: `community-channel-private-2-${Date.now()}` })).body.group; - privateRoom3 = (await createRoom({ type: 'p', name: `community-channel-private-3-${Date.now()}` })).body.group; - publicRoom = (await createRoom({ type: 'c', name: `community-channel-public-1-${Date.now()}` })).body.channel; - publicRoom2 = (await createRoom({ type: 'c', name: `community-channel-public-2-${Date.now()}` })).body.channel; - publicTeam = await createTeam(credentials, `team-name-c-${Date.now()}`, TEAM_TYPE.PUBLIC); - privateTeam = await createTeam(credentials, `team-name-p-${Date.now()}`, TEAM_TYPE.PRIVATE); - }); - - after(async () => { - await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); - await Promise.all([ - updatePermission('add-team-channel', ['admin', 'owner', 'moderator']), - ...[privateRoom, privateRoom2, privateRoom3, publicRoom, publicRoom2].map((room) => deleteRoom({ type: room.t, roomId: room._id })), - deleteUser(testUser), - ]); - }); - - it('should throw an error if no permission', (done) => { - void updatePermission('add-team-channel', []).then(() => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom._id], - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('error-no-permission-team-channel'); + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('error-no-owner-channel'); }) .end(done); - }); - }); - - it('should add public and private rooms to team', (done) => { - void updatePermission('add-team-channel', ['admin']).then(() => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom._id, privateRoom._id], - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.have.length(2); - expect(res.body.rooms[0]).to.have.property('_id'); - expect(res.body.rooms[0]).to.have.property('teamId', publicTeam._id); - expect(res.body.rooms[1]).to.have.property('_id'); - expect(res.body.rooms[1]).to.have.property('teamId', publicTeam._id); - - const rids = (res.body.rooms as IRoom[]).map(({ _id }) => _id); + }) + .catch(done); + }); +}); - expect(rids).to.include(publicRoom._id); - expect(rids).to.include(privateRoom._id); - }) - .end(done); +describe('/teams.listRooms', () => { + let testUser: TestUser; + let testUserCredentials: Credentials; + let privateTeam: ITeam; + let publicTeam: ITeam; + let privateRoom: IRoom; + let publicRoom: IRoom; + let publicRoom2: IRoom; + + before(async () => { + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + privateTeam = await createTeam(credentials, `teamName-private-${Date.now()}`, TEAM_TYPE.PRIVATE); + publicTeam = await createTeam(testUserCredentials, `teamName-public-${Date.now()}`, TEAM_TYPE.PUBLIC); + + privateRoom = (await createRoom({ type: 'p', name: `test-p-${Date.now()}` })).body.group; + publicRoom = (await createRoom({ type: 'c', name: `test-c-${Date.now()}` })).body.channel; + publicRoom2 = (await createRoom({ type: 'c', name: `test-c2-${Date.now()}` })).body.channel; + + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [privateRoom._id], + teamId: publicTeam._id, }); - }); - - it('should add public room to private team', (done) => { - void updatePermission('add-team-channel', ['admin']).then(() => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom2._id], - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', privateTeam._id); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .end(done); + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [publicRoom._id], + teamId: publicTeam._id, }); - }); - it('should add private room to team', (done) => { - void updatePermission('add-team-channel', ['admin']).then(() => { - void request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [privateRoom2._id], - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms[0]).to.have.property('teamId', privateTeam._id); - expect(res.body.rooms[0]).to.not.have.property('teamDefault'); - }) - .end(done); + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [publicRoom2._id], + teamId: privateTeam._id, }); - }); + }); - it('should fail if the user cannot access the channel', (done) => { - void updatePermission('add-team-channel', ['admin', 'user']) - .then(() => { - void request - .post(api('teams.addRooms')) - .set(testUserCredentials) - .send({ - rooms: [privateRoom3._id], - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('invalid-room'); - }) - .end(done); + after(() => + Promise.all([ + updatePermission('view-all-teams', ['admin']), + updatePermission('view-all-team-channels', ['admin', 'owner']), + deleteUser(testUser), + deleteTeam(credentials, privateTeam.name), + deleteTeam(credentials, publicTeam.name), + deleteRoom({ type: 'p', roomId: privateRoom._id }), + deleteRoom({ type: 'c', roomId: publicRoom._id }), + deleteRoom({ type: 'c', roomId: publicRoom2._id }), + ]), + ); + + it('should throw an error if team is private and no permission', (done) => { + void updatePermission('view-all-teams', []).then(() => { + void request + .get(api('teams.listRooms')) + .set(testUserCredentials) + .query({ + teamId: privateTeam._id, }) - .catch(done); + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('user-not-on-private-team'); + }) + .end(done); }); + }); - it('should fail if the user is not the owner of the channel', (done) => { + it('should return only public rooms for public team', (done) => { + void updatePermission('view-all-team-channels', []).then(() => { void request - .post(methodCall('addUsersToRoom')) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'addUsersToRoom', - params: [{ rid: privateRoom3._id, users: [testUser.username] }], - id: 'id', - msg: 'method', - }), + .get(api('teams.listRooms')) + .set(testUserCredentials) + .query({ + teamId: publicTeam._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + // main room should not be returned here + expect(res.body.rooms.length).to.equal(1); }) - .then(() => { - void request - .post(api('teams.addRooms')) - .set(testUserCredentials) - .send({ - rooms: [privateRoom3._id], - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('error-no-owner-channel'); - }) - .end(done); - }) - .catch(done); + .end(done); }); }); - describe('/teams.listRooms', () => { - let testUser: TestUser; - let testUserCredentials: Credentials; - let privateTeam: ITeam; - let publicTeam: ITeam; - let privateRoom: IRoom; - let publicRoom: IRoom; - let publicRoom2: IRoom; - - before(async () => { - testUser = await createUser(); - testUserCredentials = await login(testUser.username, password); - privateTeam = await createTeam(credentials, `teamName-private-${Date.now()}`, TEAM_TYPE.PRIVATE); - publicTeam = await createTeam(testUserCredentials, `teamName-public-${Date.now()}`, TEAM_TYPE.PUBLIC); - - privateRoom = (await createRoom({ type: 'p', name: `test-p-${Date.now()}` })).body.group; - publicRoom = (await createRoom({ type: 'c', name: `test-c-${Date.now()}` })).body.channel; - publicRoom2 = (await createRoom({ type: 'c', name: `test-c2-${Date.now()}` })).body.channel; - - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [privateRoom._id], - teamId: publicTeam._id, - }); - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom._id], + it('should return all rooms for public team', (done) => { + void updatePermission('view-all-team-channels', ['user']).then(() => { + void request + .get(api('teams.listRooms')) + .set(testUserCredentials) + .query({ teamId: publicTeam._id, - }); - - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom2._id], - teamId: privateTeam._id, - }); + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(2); + }) + .end(done); }); + }); + it('should return all rooms for public team even requested with count and offset params', (done) => { + void updatePermission('view-all-team-channels', ['user']).then(() => { + void request + .get(api('teams.listRooms')) + .set(testUserCredentials) + .query({ + teamId: publicTeam._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(2); + }) + .end(done); + }); + }); - after(() => - Promise.all([ - updatePermission('view-all-teams', ['admin']), - updatePermission('view-all-team-channels', ['admin', 'owner']), - deleteUser(testUser), - deleteTeam(credentials, privateTeam.name), - deleteTeam(credentials, publicTeam.name), - deleteRoom({ type: 'p', roomId: privateRoom._id }), - deleteRoom({ type: 'c', roomId: publicRoom._id }), - deleteRoom({ type: 'c', roomId: publicRoom2._id }), - ]), - ); - - it('should throw an error if team is private and no permission', (done) => { - void updatePermission('view-all-teams', []).then(() => { - void request - .get(api('teams.listRooms')) - .set(testUserCredentials) - .query({ - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('user-not-on-private-team'); - }) - .end(done); - }); - }); - - it('should return only public rooms for public team', (done) => { - void updatePermission('view-all-team-channels', []).then(() => { - void request - .get(api('teams.listRooms')) - .set(testUserCredentials) - .query({ - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.be.an('array'); - // main room should not be returned here - expect(res.body.rooms.length).to.equal(1); - }) - .end(done); - }); - }); - - it('should return all rooms for public team', (done) => { - void updatePermission('view-all-team-channels', ['user']).then(() => { - void request - .get(api('teams.listRooms')) - .set(testUserCredentials) - .query({ - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.be.an('array'); - expect(res.body.rooms.length).to.equal(2); - }) - .end(done); - }); - }); - it('should return all rooms for public team even requested with count and offset params', (done) => { - void updatePermission('view-all-team-channels', ['user']).then(() => { - void request - .get(api('teams.listRooms')) - .set(testUserCredentials) - .query({ - teamId: publicTeam._id, - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.be.an('array'); - expect(res.body.rooms.length).to.equal(2); - }) - .end(done); - }); - }); - - it('should return public rooms for private team', (done) => { - void updatePermission('view-all-team-channels', []).then(() => { - void updatePermission('view-all-teams', ['admin']).then(() => { - void request - .get(api('teams.listRooms')) - .set(credentials) - .query({ - teamId: privateTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.be.an('array'); - expect(res.body.rooms.length).to.equal(1); - }) - .end(done); - }); - }); - }); - it('should return public rooms for private team even requested with count and offset params', (done) => { - void updatePermission('view-all-team-channels', []).then(() => { - void updatePermission('view-all-teams', ['admin']).then(() => { - void request - .get(api('teams.listRooms')) - .set(credentials) - .query({ - teamId: privateTeam._id, - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rooms'); - expect(res.body.rooms).to.be.an('array'); - expect(res.body.rooms.length).to.equal(1); - }) - .end(done); - }); - }); - }); - }); - - describe('/teams.updateRoom', () => { - let publicRoom: IRoom; - let publicTeam: ITeam; - const name = `teamName-update-room-${Date.now()}`; - - before(async () => { - publicRoom = (await createRoom({ type: 'c', name: `public-update-room-${Date.now()}` })).body.channel; - publicTeam = await createTeam(credentials, name, TEAM_TYPE.PUBLIC); - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom._id], - teamId: publicTeam._id, - }); - }); - - after(async () => { - await deleteTeam(credentials, name); - await Promise.all([ - updatePermission('edit-team-channel', ['admin', 'owner', 'moderator']), - deleteRoom({ type: 'c', roomId: publicRoom._id }), - ]); - }); - - it('should throw an error if no permission', (done) => { - void updatePermission('edit-team-channel', []).then(() => { - void request - .post(api('teams.updateRoom')) - .set(credentials) - .send({ - roomId: publicRoom._id, - isDefault: true, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('unauthorized'); - }) - .end(done); - }); - }); - - it('should set room to team default', (done) => { - void updatePermission('edit-team-channel', ['admin']).then(() => { - void request - .post(api('teams.updateRoom')) - .set(credentials) - .send({ - roomId: publicRoom._id, - isDefault: true, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('room'); - expect(res.body.room).to.have.property('teamId', publicTeam._id); - expect(res.body.room).to.have.property('teamDefault', true); - }) - .end(done); - }); - }); - - describe('team auto-join', () => { - let testTeam: ITeam; - let createdRoom: IRoom; - let testUser1: TestUser; - let testUser2: TestUser; - - before(async () => { - const [testUser1Result, testUser2Result] = await Promise.all([createUser(), createUser()]); - - testUser1 = testUser1Result; - testUser2 = testUser2Result; - }); - - beforeEach(async () => { - const createTeamPromise = createTeam(credentials, `test-team-name${Date.now()}`, 0); - const createRoomPromise = createRoom({ name: `test-room-name${Date.now()}`, type: 'c' }); - const [testTeamCreationResult, testRoomCreationResult] = await Promise.all([createTeamPromise, createRoomPromise]); - - testTeam = testTeamCreationResult; - createdRoom = testRoomCreationResult.body.channel; - - await request - .post(api('teams.addRooms')) - .set(credentials) - .expect(200) - .send({ - rooms: [createdRoom._id], - teamName: testTeam.name, - }); - }); - - afterEach(() => Promise.all([deleteTeam(credentials, testTeam.name), deleteRoom({ roomId: createdRoom._id, type: 'c' })])); - - after(() => Promise.all([updateSetting('API_User_Limit', 500), deleteUser(testUser1), deleteUser(testUser2)])); - - it('should add members when the members count is less than or equal to the API_User_Limit setting', async () => { - await updateSetting('API_User_Limit', 2); - - await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]); - await request - .post(api('teams.updateRoom')) - .set(credentials) - .send({ - roomId: createdRoom._id, - isDefault: true, - }) - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(3); - }); - }); - - it('should not add all members when we update a team channel to be auto-join and the members count is greater than the API_User_Limit setting', async () => { - await updateSetting('API_User_Limit', 1); - - await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]); - await request - .post(api('teams.updateRoom')) - .set(credentials) - .send({ - roomId: createdRoom._id, - isDefault: true, - }) - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(2); - }); - }); - }); - }); - - describe('/teams.removeRoom', () => { - let publicRoom: IRoom; - let publicTeam: ITeam; - const name = `teamName-remove-room-${Date.now()}`; - - before(async () => { - publicRoom = (await createRoom({ type: 'c', name: `public-remove-room-${Date.now()}` })).body.channel; - publicTeam = await createTeam(credentials, name, TEAM_TYPE.PUBLIC); - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [publicRoom._id], - teamId: publicTeam._id, - }); - }); - - after(async () => { - await deleteTeam(credentials, name); - await Promise.all([ - updatePermission('edit-team-channel', ['admin', 'owner', 'moderator']), - deleteRoom({ type: 'c', roomId: publicRoom._id }), - ]); - }); - - after(() => - Promise.all([ - updatePermission('remove-team-channel', ['admin', 'owner', 'moderator']), - deleteRoom({ type: 'c', roomId: publicRoom._id }), - deleteTeam(credentials, name), - ]), - ); - - it('should throw an error if no permission', (done) => { - void updatePermission('remove-team-channel', []).then(() => { - void request - .post(api('teams.removeRoom')) - .set(credentials) - .send({ - roomId: publicRoom._id, - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('unauthorized'); - }) - .end(done); - }); - }); - - it('should remove room from team', (done) => { - void updatePermission('remove-team-channel', ['admin']).then(() => { - void request - .post(api('teams.removeRoom')) - .set(credentials) - .send({ - roomId: publicRoom._id, - teamId: publicTeam._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('room'); - expect(res.body.room).to.not.have.property('teamId'); - expect(res.body.room).to.not.have.property('teamDefault'); - }) - .end(done); - }); - }); - }); - - describe('/teams.update', () => { - let testTeam: ITeam; - let testTeam2: ITeam; - let testTeam3: ITeam; - const teamName = `test-team-name1${Date.now()}`; - const teamName2 = `test-team-name2${Date.now()}`; - const teamName3 = `test-team-name3${Date.now()}`; - const testTeamName = `test-team-name-changed${Date.now()}-1`; - const testTeamName2 = `test-team-name-changed${Date.now()}-2`; - let unauthorizedUser: TestUser; - - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName, - type: 0, - }) - .end((_err, res) => { - testTeam = res.body.team; - done(); - }); - }); - - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName2, - type: 0, - }) - .end((_err, res) => { - testTeam2 = res.body.team; - done(); - }); - }); - - before('Create test team', (done) => { - void request - .post(api('teams.create')) - .set(credentials) - .send({ - name: teamName3, - type: 0, - }) - .end((_err, res) => { - testTeam3 = res.body.team; - done(); - }); - }); - - before(async () => { - unauthorizedUser = await createUser(); - }); - - after(() => - Promise.all([...[testTeamName, testTeamName2, teamName3].map((name) => deleteTeam(credentials, name)), deleteUser(unauthorizedUser)]), - ); - - it('should update team name', async () => { - const updateResponse = await request - .post(api('teams.update')) - .set(credentials) - .send({ - teamId: testTeam._id, - data: { - name: testTeamName, - }, - }); - - expect(updateResponse.body).to.have.property('success', true); - - const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam._id }); - - expect(infoResponse.body).to.have.property('success', true); - - const { teamInfo } = infoResponse.body; - expect(teamInfo).to.have.property('name', testTeamName); - }); - - it('should update team type', async () => { - const updateResponse = await request - .post(api('teams.update')) - .set(credentials) - .send({ - teamId: testTeam._id, - data: { - type: 1, - }, - }); - - expect(updateResponse.body).to.have.property('success', true); - - const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam._id }); - - expect(infoResponse.body).to.have.property('success', true); - - const { teamInfo } = infoResponse.body; - expect(teamInfo).to.have.property('type', 1); - }); - - it('should update team name and type at once', async () => { - const updateResponse = await request - .post(api('teams.update')) - .set(credentials) - .send({ - teamId: testTeam2._id, - data: { - name: testTeamName2, - type: 1, - }, - }); - - expect(updateResponse.body).to.have.property('success', true); - - const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam2._id }); - - expect(infoResponse.body).to.have.property('success', true); - - const { teamInfo } = infoResponse.body; - expect(teamInfo).to.have.property('type', 1); - }); - - it('should not update team if permissions are not met', async () => { - const unauthorizedUserCredentials = await login(unauthorizedUser.username, password); - - const res = await request - .post(api('teams.update')) - .set(unauthorizedUserCredentials) - .send({ - teamId: testTeam._id, - data: { - name: 'anyname', - }, - }) - .expect('Content-Type', 'application/json') - .expect(403); - - expect(res.body).to.have.property('success', false); - }); - - describe('should update team room to default and invite users with the right notification preferences', () => { - let userWithPrefs: TestUser; - let userCredentials: Credentials; - let createdRoom: IRoom; - - before(async () => { - userWithPrefs = await createUser(); - userCredentials = await login(userWithPrefs.username, password); - - createdRoom = (await createRoom({ type: 'c', name: `${Date.now()}-testTeam3` })).body.channel; - - await request - .post(api('teams.addRooms')) - .set(credentials) - .send({ - rooms: [createdRoom._id], - teamId: testTeam3._id, - }); - }); - - after(() => Promise.all([deleteUser(userWithPrefs), deleteRoom({ type: 'c', roomId: createdRoom._id })])); - - it('should update user prefs', async () => { - await request - .post(methodCall('saveUserPreferences')) - .set(userCredentials) - .send({ - message: JSON.stringify({ - method: 'saveUserPreferences', - params: [{ emailNotificationMode: 'nothing' }], - id: 'id', - msg: 'method', - }), - }) - .expect(200); - }); - - it('should add user with prefs to team', (done) => { + it('should return public rooms for private team', (done) => { + void updatePermission('view-all-team-channels', []).then(() => { + void updatePermission('view-all-teams', ['admin']).then(() => { void request - .post(api('teams.addMembers')) + .get(api('teams.listRooms')) .set(credentials) - .send({ - teamName: testTeam3.name, - members: [ - { - userId: userWithPrefs._id, - roles: ['member'], - }, - ], + .query({ + teamId: privateTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(1); }) .end(done); }); - - it('should update team channel to auto-join', async () => { - const response = await request.post(api('teams.updateRoom')).set(credentials).send({ - roomId: createdRoom._id, - isDefault: true, - }); - expect(response.body).to.have.property('success', true); - }); - - it('should return the user subscription with the right notification preferences', (done) => { + }); + }); + it('should return public rooms for private team even requested with count and offset params', (done) => { + void updatePermission('view-all-team-channels', []).then(() => { + void updatePermission('view-all-teams', ['admin']).then(() => { void request - .get(api('subscriptions.getOne')) - .set(userCredentials) + .get(api('teams.listRooms')) + .set(credentials) .query({ - roomId: createdRoom._id, + teamId: privateTeam._id, + count: 5, + offset: 0, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('subscription').and.to.be.an('object'); - expect(res.body).to.have.nested.property('subscription.emailNotifications').and.to.be.equal('nothing'); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(1); }) .end(done); }); @@ -2539,3 +2179,440 @@ describe('[Teams]', () => { }); }); }); + +describe('/teams.updateRoom', () => { + let publicRoom: IRoom; + let publicTeam: ITeam; + const name = `teamName-update-room-${Date.now()}`; + + before(async () => { + publicRoom = (await createRoom({ type: 'c', name: `public-update-room-${Date.now()}` })).body.channel; + publicTeam = await createTeam(credentials, name, TEAM_TYPE.PUBLIC); + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [publicRoom._id], + teamId: publicTeam._id, + }); + }); + + after(async () => { + await deleteTeam(credentials, name); + await Promise.all([ + updatePermission('edit-team-channel', ['admin', 'owner', 'moderator']), + deleteRoom({ type: 'c', roomId: publicRoom._id }), + ]); + }); + + it('should throw an error if no permission', (done) => { + void updatePermission('edit-team-channel', []).then(() => { + void request + .post(api('teams.updateRoom')) + .set(credentials) + .send({ + roomId: publicRoom._id, + isDefault: true, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('unauthorized'); + }) + .end(done); + }); + }); + + it('should set room to team default', (done) => { + void updatePermission('edit-team-channel', ['admin']).then(() => { + void request + .post(api('teams.updateRoom')) + .set(credentials) + .send({ + roomId: publicRoom._id, + isDefault: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('room'); + expect(res.body.room).to.have.property('teamId', publicTeam._id); + expect(res.body.room).to.have.property('teamDefault', true); + }) + .end(done); + }); + }); + + describe('team auto-join', () => { + let testTeam: ITeam; + let createdRoom: IRoom; + let testUser1: TestUser; + let testUser2: TestUser; + + before(async () => { + const [testUser1Result, testUser2Result] = await Promise.all([createUser(), createUser()]); + + testUser1 = testUser1Result; + testUser2 = testUser2Result; + }); + + beforeEach(async () => { + const createTeamPromise = createTeam(credentials, `test-team-name${Date.now()}`, 0); + const createRoomPromise = createRoom({ name: `test-room-name${Date.now()}`, type: 'c' }); + const [testTeamCreationResult, testRoomCreationResult] = await Promise.all([createTeamPromise, createRoomPromise]); + + testTeam = testTeamCreationResult; + createdRoom = testRoomCreationResult.body.channel; + + await request + .post(api('teams.addRooms')) + .set(credentials) + .expect(200) + .send({ + rooms: [createdRoom._id], + teamName: testTeam.name, + }); + }); + + afterEach(() => Promise.all([deleteTeam(credentials, testTeam.name), deleteRoom({ roomId: createdRoom._id, type: 'c' })])); + + after(() => Promise.all([updateSetting('API_User_Limit', 500), deleteUser(testUser1), deleteUser(testUser2)])); + + it('should add members when the members count is less than or equal to the API_User_Limit setting', async () => { + await updateSetting('API_User_Limit', 2); + + await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]); + await request + .post(api('teams.updateRoom')) + .set(credentials) + .send({ + roomId: createdRoom._id, + isDefault: true, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(3); + }); + }); + + it('should not add all members when we update a team channel to be auto-join and the members count is greater than the API_User_Limit setting', async () => { + await updateSetting('API_User_Limit', 1); + + await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]); + await request + .post(api('teams.updateRoom')) + .set(credentials) + .send({ + roomId: createdRoom._id, + isDefault: true, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(2); + }); + }); + }); +}); + +describe('/teams.removeRoom', () => { + let publicRoom: IRoom; + let publicTeam: ITeam; + const name = `teamName-remove-room-${Date.now()}`; + + before(async () => { + publicRoom = (await createRoom({ type: 'c', name: `public-remove-room-${Date.now()}` })).body.channel; + publicTeam = await createTeam(credentials, name, TEAM_TYPE.PUBLIC); + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [publicRoom._id], + teamId: publicTeam._id, + }); + }); + + after(async () => { + await deleteTeam(credentials, name); + await Promise.all([ + updatePermission('edit-team-channel', ['admin', 'owner', 'moderator']), + deleteRoom({ type: 'c', roomId: publicRoom._id }), + ]); + }); + + after(() => + Promise.all([ + updatePermission('remove-team-channel', ['admin', 'owner', 'moderator']), + deleteRoom({ type: 'c', roomId: publicRoom._id }), + deleteTeam(credentials, name), + ]), + ); + + it('should throw an error if no permission', (done) => { + void updatePermission('remove-team-channel', []).then(() => { + void request + .post(api('teams.removeRoom')) + .set(credentials) + .send({ + roomId: publicRoom._id, + teamId: publicTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('unauthorized'); + }) + .end(done); + }); + }); + + it('should remove room from team', (done) => { + void updatePermission('remove-team-channel', ['admin']).then(() => { + void request + .post(api('teams.removeRoom')) + .set(credentials) + .send({ + roomId: publicRoom._id, + teamId: publicTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('room'); + expect(res.body.room).to.not.have.property('teamId'); + expect(res.body.room).to.not.have.property('teamDefault'); + }) + .end(done); + }); + }); +}); + +describe('/teams.update', () => { + let testTeam: ITeam; + let testTeam2: ITeam; + let testTeam3: ITeam; + const teamName = `test-team-name1${Date.now()}`; + const teamName2 = `test-team-name2${Date.now()}`; + const teamName3 = `test-team-name3${Date.now()}`; + const testTeamName = `test-team-name-changed${Date.now()}-1`; + const testTeamName2 = `test-team-name-changed${Date.now()}-2`; + let unauthorizedUser: TestUser; + + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName, + type: 0, + }) + .end((_err, res) => { + testTeam = res.body.team; + done(); + }); + }); + + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName2, + type: 0, + }) + .end((_err, res) => { + testTeam2 = res.body.team; + done(); + }); + }); + + before('Create test team', (done) => { + void request + .post(api('teams.create')) + .set(credentials) + .send({ + name: teamName3, + type: 0, + }) + .end((_err, res) => { + testTeam3 = res.body.team; + done(); + }); + }); + + before(async () => { + unauthorizedUser = await createUser(); + }); + + after(() => + Promise.all([...[testTeamName, testTeamName2, teamName3].map((name) => deleteTeam(credentials, name)), deleteUser(unauthorizedUser)]), + ); + + it('should update team name', async () => { + const updateResponse = await request + .post(api('teams.update')) + .set(credentials) + .send({ + teamId: testTeam._id, + data: { + name: testTeamName, + }, + }); + + expect(updateResponse.body).to.have.property('success', true); + + const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam._id }); + + expect(infoResponse.body).to.have.property('success', true); + + const { teamInfo } = infoResponse.body; + expect(teamInfo).to.have.property('name', testTeamName); + }); + + it('should update team type', async () => { + const updateResponse = await request + .post(api('teams.update')) + .set(credentials) + .send({ + teamId: testTeam._id, + data: { + type: 1, + }, + }); + + expect(updateResponse.body).to.have.property('success', true); + + const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam._id }); + + expect(infoResponse.body).to.have.property('success', true); + + const { teamInfo } = infoResponse.body; + expect(teamInfo).to.have.property('type', 1); + }); + + it('should update team name and type at once', async () => { + const updateResponse = await request + .post(api('teams.update')) + .set(credentials) + .send({ + teamId: testTeam2._id, + data: { + name: testTeamName2, + type: 1, + }, + }); + + expect(updateResponse.body).to.have.property('success', true); + + const infoResponse = await request.get(api('teams.info')).set(credentials).query({ teamId: testTeam2._id }); + + expect(infoResponse.body).to.have.property('success', true); + + const { teamInfo } = infoResponse.body; + expect(teamInfo).to.have.property('type', 1); + }); + + it('should not update team if permissions are not met', async () => { + const unauthorizedUserCredentials = await login(unauthorizedUser.username, password); + + const res = await request + .post(api('teams.update')) + .set(unauthorizedUserCredentials) + .send({ + teamId: testTeam._id, + data: { + name: 'anyname', + }, + }) + .expect('Content-Type', 'application/json') + .expect(403); + + expect(res.body).to.have.property('success', false); + }); + + describe('should update team room to default and invite users with the right notification preferences', () => { + let userWithPrefs: TestUser; + let userCredentials: Credentials; + let createdRoom: IRoom; + + before(async () => { + userWithPrefs = await createUser(); + userCredentials = await login(userWithPrefs.username, password); + + createdRoom = (await createRoom({ type: 'c', name: `${Date.now()}-testTeam3` })).body.channel; + + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ + rooms: [createdRoom._id], + teamId: testTeam3._id, + }); + }); + + after(() => Promise.all([deleteUser(userWithPrefs), deleteRoom({ type: 'c', roomId: createdRoom._id })])); + + it('should update user prefs', async () => { + await request + .post(methodCall('saveUserPreferences')) + .set(userCredentials) + .send({ + message: JSON.stringify({ + method: 'saveUserPreferences', + params: [{ emailNotificationMode: 'nothing' }], + id: 'id', + msg: 'method', + }), + }) + .expect(200); + }); + + it('should add user with prefs to team', (done) => { + void request + .post(api('teams.addMembers')) + .set(credentials) + .send({ + teamName: testTeam3.name, + members: [ + { + userId: userWithPrefs._id, + roles: ['member'], + }, + ], + }) + .end(done); + }); + + it('should update team channel to auto-join', async () => { + const response = await request.post(api('teams.updateRoom')).set(credentials).send({ + roomId: createdRoom._id, + isDefault: true, + }); + expect(response.body).to.have.property('success', true); + }); + + it('should return the user subscription with the right notification preferences', (done) => { + void request + .get(api('subscriptions.getOne')) + .set(userCredentials) + .query({ + roomId: createdRoom._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('subscription').and.to.be.an('object'); + expect(res.body).to.have.nested.property('subscription.emailNotifications').and.to.be.equal('nothing'); + }) + .end(done); + }); + }); +}); From 773242ffca6d8f815cb93284a77eb95f3c165d29 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 13 May 2024 08:48:41 -0300 Subject: [PATCH 15/85] chore!: Improve permissions check on im endpoints (#32333) --- apps/meteor/app/api/server/v1/im.ts | 12 +- .../tests/end-to-end/api/direct-message.ts | 182 ++++++++++-------- 2 files changed, 108 insertions(+), 86 deletions(-) diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 5f12d7d7b751a..1ee1ac4ca4000 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -410,7 +410,7 @@ API.v1.addRoute( API.v1.addRoute( ['dm.messages.others', 'im.messages.others'], - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-room-administration'] }, { async get() { if (settings.get('API_Enable_Direct_Message_History_EndPoint') !== true) { @@ -419,10 +419,6 @@ API.v1.addRoute( }); } - if (!(await hasPermissionAsync(this.userId, 'view-room-administration'))) { - return API.v1.unauthorized(); - } - const { roomId } = this.queryParams; if (!roomId) { throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" is required'); @@ -498,13 +494,9 @@ API.v1.addRoute( API.v1.addRoute( ['dm.list.everyone', 'im.list.everyone'], - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-room-administration'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-room-administration'))) { - return API.v1.unauthorized(); - } - const { offset, count }: { offset: number; count: number } = await getPaginationItems(this.queryParams); const { sort, fields, query } = await this.parseJsonQuery(); diff --git a/apps/meteor/tests/end-to-end/api/direct-message.ts b/apps/meteor/tests/end-to-end/api/direct-message.ts index afe33c1c5fc32..090eae0d7944b 100644 --- a/apps/meteor/tests/end-to-end/api/direct-message.ts +++ b/apps/meteor/tests/end-to-end/api/direct-message.ts @@ -216,29 +216,51 @@ describe('[Direct Messages]', () => { .end(done); }); - it('/im.list.everyone', (done) => { - void request - .get(api('im.list.everyone')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('ims').and.to.be.an('array'); - const im = (res.body.ims as IRoom[]).find((dm) => dm._id === testDM._id); - expect(im).to.have.property('_id'); - expect(im).to.have.property('t').and.to.be.eq('d'); - expect(im).to.have.property('msgs').and.to.be.a('number'); - expect(im).to.have.property('usernames').and.to.be.an('array'); - expect(im).to.have.property('ro'); - expect(im).to.have.property('sysMes'); - expect(im).to.have.property('_updatedAt'); - expect(im).to.have.property('ts'); - expect(im).to.have.property('lastMessage'); - }) - .end(done); + describe('/im.list.everyone', () => { + before(async () => { + return updatePermission('view-room-administration', ['admin']); + }); + + after(async () => { + return updatePermission('view-room-administration', ['admin']); + }); + + it('should succesfully return a list of direct messages', async () => { + await request + .get(api('im.list.everyone')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count', 1); + expect(res.body).to.have.property('total', 1); + expect(res.body).to.have.property('ims').and.to.be.an('array'); + const im = res.body.ims[0]; + expect(im).to.have.property('_id'); + expect(im).to.have.property('t').and.to.be.eq('d'); + expect(im).to.have.property('msgs').and.to.be.a('number'); + expect(im).to.have.property('usernames').and.to.be.an('array'); + expect(im).to.have.property('ro'); + expect(im).to.have.property('sysMes'); + expect(im).to.have.property('_updatedAt'); + expect(im).to.have.property('ts'); + expect(im).to.have.property('lastMessage'); + }); + }); + + it('should fail if user does NOT have the view-room-administration permission', async () => { + await updatePermission('view-room-administration', []); + await request + .get(api('im.list.everyone')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); }); describe("Setting: 'Use Real Name': true", () => { @@ -365,63 +387,71 @@ describe('[Direct Messages]', () => { }); describe('/im.messages.others', () => { - it('should fail when the endpoint is disabled', (done) => { - void updateSetting('API_Enable_Direct_Message_History_EndPoint', false).then(() => { - void request - .get(api('im.messages.others')) - .set(credentials) - .query({ - roomId: directMessage._id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-endpoint-disabled'); - }) - .end(done); - }); + it('should fail when the endpoint is disabled and the user has permissions', async () => { + await updateSetting('API_Enable_Direct_Message_History_EndPoint', false); + await request + .get(api('im.messages.others')) + .set(credentials) + .query({ + roomId: directMessage._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-endpoint-disabled'); + }); }); - it('should fail when the endpoint is enabled but the user doesnt have permission', (done) => { - void updateSetting('API_Enable_Direct_Message_History_EndPoint', true).then(() => { - void updatePermission('view-room-administration', []).then(() => { - void request - .get(api('im.messages.others')) - .set(credentials) - .query({ - roomId: directMessage._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + it('should fail when the endpoint is disabled and the user doesnt have permission', async () => { + await updateSetting('API_Enable_Direct_Message_History_EndPoint', false); + await updatePermission('view-room-administration', ['admin']); + await request + .get(api('im.messages.others')) + .set(credentials) + .query({ + roomId: directMessage._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); - }); }); - it('should succeed when the endpoint is enabled and user has permission', (done) => { - void updateSetting('API_Enable_Direct_Message_History_EndPoint', true).then(() => { - void updatePermission('view-room-administration', ['admin']).then(() => { - void request - .get(api('im.messages.others')) - .set(credentials) - .query({ - roomId: directMessage._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('messages').and.to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - }) - .end(done); + it('should fail when the endpoint is enabled but the user doesnt have permission', async () => { + await updateSetting('API_Enable_Direct_Message_History_EndPoint', true); + await updatePermission('view-room-administration', []); + await request + .get(api('im.messages.others')) + .set(credentials) + .query({ + roomId: directMessage._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + it('should succeed when the endpoint is enabled and user has permission', async () => { + await updateSetting('API_Enable_Direct_Message_History_EndPoint', true); + await updatePermission('view-room-administration', ['admin']); + await request + .get(api('im.messages.others')) + .set(credentials) + .query({ + roomId: directMessage._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); }); - }); }); }); From 542659f84ba273ce6ce16766d32b0c5b34855e1b Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 13 May 2024 08:49:58 -0300 Subject: [PATCH 16/85] chore!: Improve permissions check on permissions endpoints (#32343) --- apps/meteor/app/api/server/v1/permissions.ts | 7 +--- .../tests/end-to-end/api/permissions.ts | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/apps/meteor/app/api/server/v1/permissions.ts b/apps/meteor/app/api/server/v1/permissions.ts index 3613cc1713544..65dfafe5ccce2 100644 --- a/apps/meteor/app/api/server/v1/permissions.ts +++ b/apps/meteor/app/api/server/v1/permissions.ts @@ -3,7 +3,6 @@ import { Permissions, Roles } from '@rocket.chat/models'; import { isBodyParamsValidPermissionUpdate } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { notifyOnPermissionChangedById } from '../../../lib/server/lib/notifyListener'; import { API } from '../api'; @@ -41,13 +40,9 @@ API.v1.addRoute( API.v1.addRoute( 'permissions.update', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['access-permissions'] }, { async post() { - if (!(await hasPermissionAsync(this.userId, 'access-permissions'))) { - return API.v1.failure('Editing permissions is not allowed', 'error-edit-permissions-not-allowed'); - } - const { bodyParams } = this; if (!isBodyParamsValidPermissionUpdate(bodyParams)) { diff --git a/apps/meteor/tests/end-to-end/api/permissions.ts b/apps/meteor/tests/end-to-end/api/permissions.ts index 55bd724dad45b..ef2dee4378f90 100644 --- a/apps/meteor/tests/end-to-end/api/permissions.ts +++ b/apps/meteor/tests/end-to-end/api/permissions.ts @@ -1,8 +1,13 @@ +import type { Credentials } from '@rocket.chat/api-client'; +import type { IUser } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { before, describe, it, after } from 'mocha'; import { getCredentials, api, request, credentials } from '../../data/api-data'; import { updatePermission } from '../../data/permissions.helper'; +import { password } from '../../data/user'; +import { createUser, deleteUser, login } from '../../data/users.helper'; +import type { TestUser } from '../../data/users.helper.js'; describe('[Permissions]', () => { before((done) => getCredentials(done)); @@ -54,6 +59,19 @@ describe('[Permissions]', () => { }); describe('[/permissions.update]', () => { + let testUser: TestUser; + let testUserCredentials: Credentials; + before(async () => { + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + await updatePermission('access-permissions', ['admin']); + }); + + after(async () => { + await updatePermission('access-permissions', ['admin']); + await deleteUser(testUser); + }); + it('should change the permissions on the server', (done) => { const permissions = [ { @@ -127,5 +145,23 @@ describe('[Permissions]', () => { }) .end(done); }); + it('should fail updating permission if user does NOT have the access-permissions permission', async () => { + const permissions = [ + { + _id: 'add-oauth-service', + roles: ['admin', 'user'], + }, + ]; + await request + .post(api('permissions.update')) + .set(testUserCredentials) + .send({ permissions }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); }); }); From a8ee385320875709f006c833a98cb825eea409b6 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 13 May 2024 08:51:56 -0300 Subject: [PATCH 17/85] chore!: Improve permissions check on settings endpoints (#32350) --- apps/meteor/app/api/server/v1/settings.ts | 15 +++-- apps/meteor/tests/end-to-end/api/settings.ts | 60 ++++++++++++++++++-- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/meteor/app/api/server/v1/settings.ts b/apps/meteor/app/api/server/v1/settings.ts index 574f4ee64194f..a77c0a248627d 100644 --- a/apps/meteor/app/api/server/v1/settings.ts +++ b/apps/meteor/app/api/server/v1/settings.ts @@ -150,12 +150,15 @@ API.v1.addRoute( API.v1.addRoute( 'settings/:_id', - { authRequired: true }, + { + authRequired: true, + permissionsRequired: { + GET: { permissions: ['view-privileged-setting'], operation: 'hasAll' }, + POST: { permissions: ['edit-privileged-setting'], operation: 'hasAll' }, + }, + }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-privileged-setting'))) { - return API.v1.unauthorized(); - } const setting = await Settings.findOneNotHiddenById(this.urlParams._id); if (!setting) { return API.v1.failure(); @@ -165,10 +168,6 @@ API.v1.addRoute( post: { twoFactorRequired: true, async action(): Promise> { - if (!(await hasPermissionAsync(this.userId, 'edit-privileged-setting'))) { - return API.v1.unauthorized(); - } - if (typeof this.urlParams._id !== 'string') { throw new Meteor.Error('error-id-param-not-provided', 'The parameter "id" is required'); } diff --git a/apps/meteor/tests/end-to-end/api/settings.ts b/apps/meteor/tests/end-to-end/api/settings.ts index 3a03ecbe92263..c596954ad0655 100644 --- a/apps/meteor/tests/end-to-end/api/settings.ts +++ b/apps/meteor/tests/end-to-end/api/settings.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { before, describe, it, after } from 'mocha'; import { getCredentials, api, request, credentials } from '../../data/api-data'; -import { updateSetting } from '../../data/permissions.helper'; +import { updatePermission, updateSetting } from '../../data/permissions.helper'; describe('[Settings]', () => { before((done) => getCredentials(done)); @@ -56,8 +56,18 @@ describe('[Settings]', () => { }); describe('[/settings/:_id]', () => { - it('should return one setting', (done) => { - void request + before(async () => { + await updatePermission('view-privileged-setting', ['admin']); + await updatePermission('edit-privileged-setting', ['admin']); + }); + + after(async () => { + await updatePermission('view-privileged-setting', ['admin']); + await updatePermission('edit-privileged-setting', ['admin']); + }); + + it('should succesfully return one setting (GET)', async () => { + return request .get(api('settings/Site_Url')) .set(credentials) .expect('Content-Type', 'application/json') @@ -66,8 +76,50 @@ describe('[Settings]', () => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('_id', 'Site_Url'); expect(res.body).to.have.property('value'); + }); + }); + + it('should fail returning a setting if user does NOT have the view-privileged-setting permission (GET)', async () => { + await updatePermission('view-privileged-setting', []); + return request + .get(api('settings/Site_Url')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + + it('should succesfully set the value of a setting (POST)', async () => { + return request + .post(api('settings/LDAP_Enable')) + .set(credentials) + .send({ + value: false, }) - .end(done); + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should fail updating the value of a setting if user does NOT have the edit-privileged-setting permission (POST)', async () => { + await updatePermission('edit-privileged-setting', []); + return request + .post(api('settings/LDAP_Enable')) + .set(credentials) + .send({ + value: false, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); }); }); From b40477211b67436022f9b5c095e9bc35f92716cc Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 13 May 2024 09:00:35 -0300 Subject: [PATCH 18/85] chore!: Improve permissions check on licenses endpoints (#32354) --- apps/meteor/ee/server/api/licenses.ts | 6 +----- apps/meteor/tests/end-to-end/api/licenses.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/meteor/ee/server/api/licenses.ts b/apps/meteor/ee/server/api/licenses.ts index 28b0b2e080f33..22ddbda9e31e5 100644 --- a/apps/meteor/ee/server/api/licenses.ts +++ b/apps/meteor/ee/server/api/licenses.ts @@ -41,17 +41,13 @@ API.v1.addRoute( API.v1.addRoute( 'licenses.add', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['edit-privileged-setting'] }, { async post() { check(this.bodyParams, { license: String, }); - if (!(await hasPermissionAsync(this.userId, 'edit-privileged-setting'))) { - return API.v1.unauthorized(); - } - const { license } = this.bodyParams; if (!(await License.validateFormat(license))) { return API.v1.failure('Invalid license'); diff --git a/apps/meteor/tests/end-to-end/api/licenses.ts b/apps/meteor/tests/end-to-end/api/licenses.ts index 7792d497fe1b8..10dce4177aec6 100644 --- a/apps/meteor/tests/end-to-end/api/licenses.ts +++ b/apps/meteor/tests/end-to-end/api/licenses.ts @@ -48,7 +48,7 @@ describe('licenses', () => { .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }) .end(done); }); From f74e0de16ca4737ef8b935911046f8561ae2063e Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 14 May 2024 16:20:12 -0300 Subject: [PATCH 19/85] test: fix im.messages.others endpoint tests (#32426) --- apps/meteor/tests/end-to-end/api/direct-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/direct-message.ts b/apps/meteor/tests/end-to-end/api/direct-message.ts index 090eae0d7944b..e4ebaced41f71 100644 --- a/apps/meteor/tests/end-to-end/api/direct-message.ts +++ b/apps/meteor/tests/end-to-end/api/direct-message.ts @@ -404,7 +404,7 @@ describe('[Direct Messages]', () => { }); it('should fail when the endpoint is disabled and the user doesnt have permission', async () => { await updateSetting('API_Enable_Direct_Message_History_EndPoint', false); - await updatePermission('view-room-administration', ['admin']); + await updatePermission('view-room-administration', []); await request .get(api('im.messages.others')) .set(credentials) From 699e5c9e2a48ad4d0ca62f5b0f57df28cbdb1fd6 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 14 May 2024 18:10:09 -0300 Subject: [PATCH 20/85] chore!: Improve permissions check on misc endpoints (#32337) --- apps/meteor/app/api/server/v1/misc.ts | 6 +-- .../tests/end-to-end/api/miscellaneous.ts | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index 8348b8429e4e2..d0c8ebe5f1b11 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -22,7 +22,6 @@ import { v4 as uuidv4 } from 'uuid'; import { i18n } from '../../../../server/lib/i18n'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { getLogs } from '../../../../server/stream/stdout'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { passwordPolicy } from '../../../lib/server'; import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; @@ -477,12 +476,9 @@ API.v1.addRoute( */ API.v1.addRoute( 'stdout.queue', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['view-logs'] }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'view-logs'))) { - return API.v1.unauthorized(); - } return API.v1.success({ queue: getLogs() }); }, }, diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index efa7bdb93d5b9..b68f3635560b0 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -838,5 +838,45 @@ describe('miscellaneous', () => { expect(foundTokenValue).to.be.false; }); }); + + describe('permissions', () => { + before(async () => { + return updatePermission('view-logs', ['admin']); + }); + + after(async () => { + return updatePermission('view-logs', ['admin']); + }); + + it('should return server logs', async () => { + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + + expect(res.body).to.have.property('queue').and.to.be.an('array').that.is.not.empty; + expect(res.body.queue[0]).to.be.an('object'); + expect(res.body.queue[0]).to.have.property('id').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('string').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('ts').and.to.be.a('string'); + }); + }); + + it('should not return server logs if user does NOT have the view-logs permission', async () => { + await updatePermission('view-logs', []); + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); }); }); From f88b827b44f3ba85b3d2b2682268a3d95a8108f7 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 20 May 2024 14:11:44 -0300 Subject: [PATCH 21/85] chore!: Improve permissions check on roles endpoints (#32347) --- apps/meteor/app/api/server/v1/roles.ts | 17 +- apps/meteor/tests/end-to-end/api/roles.ts | 419 +++++++++++++++++++++- 2 files changed, 421 insertions(+), 15 deletions(-) diff --git a/apps/meteor/app/api/server/v1/roles.ts b/apps/meteor/app/api/server/v1/roles.ts index 66c6677a9eedf..fc9bd273996d3 100644 --- a/apps/meteor/app/api/server/v1/roles.ts +++ b/apps/meteor/app/api/server/v1/roles.ts @@ -91,7 +91,7 @@ API.v1.addRoute( API.v1.addRoute( 'roles.getUsersInRole', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['access-permissions'] }, { async get() { const { roomId, role } = this.queryParams; @@ -109,9 +109,6 @@ API.v1.addRoute( if (!role) { throw new Meteor.Error('error-param-not-provided', 'Query param "role" is required'); } - if (!(await hasPermissionAsync(this.userId, 'access-permissions'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed'); - } if (roomId && !(await hasPermissionAsync(this.userId, 'view-other-user-channels'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } @@ -150,7 +147,7 @@ API.v1.addRoute( API.v1.addRoute( 'roles.delete', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['access-permissions'] }, { async post() { const { bodyParams } = this; @@ -158,10 +155,6 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } - if (!(await hasPermissionAsync(this.userId, 'access-permissions'))) { - throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); - } - const role = await Roles.findOneByIdOrName(bodyParams.roleId); if (!role) { @@ -189,7 +182,7 @@ API.v1.addRoute( API.v1.addRoute( 'roles.removeUserFromRole', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['access-permissions'] }, { async post() { const { bodyParams } = this; @@ -199,10 +192,6 @@ API.v1.addRoute( const { roleId, roleName, username, scope } = bodyParams; - if (!(await hasPermissionAsync(this.userId, 'access-permissions'))) { - throw new Meteor.Error('error-not-allowed', 'Accessing permissions is not allowed'); - } - if (!roleId) { if (!roleName) { return API.v1.failure('error-invalid-role-properties'); diff --git a/apps/meteor/tests/end-to-end/api/roles.ts b/apps/meteor/tests/end-to-end/api/roles.ts index 91f7a30ef2f58..393883dfc0bd5 100644 --- a/apps/meteor/tests/end-to-end/api/roles.ts +++ b/apps/meteor/tests/end-to-end/api/roles.ts @@ -1,10 +1,15 @@ +import type { IUser } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { after, before, describe, it } from 'mocha'; import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { updatePermission } from '../../data/permissions.helper'; +import { password, adminUsername } from '../../data/user'; +import { createUser, deleteUser, login } from '../../data/users.helper'; -describe('[Roles]', () => { +describe('[Roles]', function () { + this.retries(0); const isEnterprise = Boolean(process.env.IS_EE); before((done) => getCredentials(done)); @@ -138,4 +143,416 @@ describe('[Roles]', () => { }); }); }); + + describe('[/roles.getUsersInRole]', () => { + let testUser: IUser; + let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + const testRoleName = `role.getUsersInRole.${Date.now()}`; + let testRoleId = ''; + + before(async () => { + await updatePermission('access-permissions', ['admin']); + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + + if (!isEnterprise) { + return; + } + + await request + .post(api('roles.create')) + .set(credentials) + .send({ + name: testRoleName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('role'); + expect(res.body.role).to.have.property('name', testRoleName); + testRoleId = res.body.role._id; + }); + }); + + after(async () => { + await deleteUser(testUser); + if (!isEnterprise) { + return; + } + + await request.post(api('roles.delete')).set(credentials).send({ + roleId: testRoleId, + }); + }); + + it('should successfully get an empty list of users in a role if no user has been assigned to it', async function () { + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .get(api('roles.getUsersInRole')) + .set(credentials) + .query({ + role: testRoleId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('total', 0); + expect(res.body).to.have.property('users').that.is.an('array').that.is.empty; + }); + }); + + it('should successfully get a list of users in a role', async function () { + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.addUserToRole')) + .set(credentials) + .send({ + roleId: testRoleId, + username: adminUsername, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .get(api('roles.getUsersInRole')) + .set(credentials) + .query({ + role: testRoleId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('total', 1); + expect(res.body).to.have.property('users').that.is.an('array').of.length(1); + expect(res.body.users[0]).to.have.property('_id', credentials['X-User-Id']); + }); + }); + + it('should fail getting a list of users in a role in case an invalid role is provided', async function () { + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .get(api('roles.getUsersInRole')) + .set(credentials) + .query({ + role: 'invalid-role', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-roleId'); + }); + }); + + it('should fail when user does NOT have the access-permissions permission', async () => { + await request + .get(api('roles.getUsersInRole')) + .set(testUserCredentials) + .query({ + role: 'admin', + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); + + describe('[/roles.delete]', () => { + let testUser: IUser; + let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + const testRoleName = `role.delete.${Date.now()}`; + let testRoleId = ''; + + before(async () => { + if (!isEnterprise) { + return; + } + + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + await updatePermission('access-permissions', ['admin']); + await request + .post(api('roles.create')) + .set(credentials) + .send({ + name: testRoleName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('role'); + expect(res.body.role).to.have.property('name', testRoleName); + testRoleId = res.body.role._id; + }); + }); + + after(async () => { + if (!isEnterprise) { + return; + } + await deleteUser(testUser); + }); + + it('should fail deleting a role when user does NOT have the access-permissions permission', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.delete')) + .set(testUserCredentials) + .send({ + roleId: testRoleId, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + + it('should fail deleting a role in EE in case an invalid role is provided', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.delete')) + .set(credentials) + .send({ + roleId: 'invalid-role', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-roleId'); + expect(res.body).to.have.property('error', 'This role does not exist [error-invalid-roleId]'); + }); + }); + + it('should successfully delete a role in EE', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.delete')) + .set(credentials) + .send({ + roleId: testRoleId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + }); + }); + + describe('[/roles.removeUserFromRole]', () => { + let testUser: IUser; + let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + const testRoleName = `role.removeUsersFromRole.${Date.now()}`; + let testRoleId = ''; + + before(async () => { + await updatePermission('access-permissions', ['admin']); + testUser = await createUser(); + testUserCredentials = await login(testUser.username, password); + if (!isEnterprise) { + return; + } + + await request + .post(api('roles.create')) + .set(credentials) + .send({ + name: testRoleName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('role'); + expect(res.body.role).to.have.property('name', testRoleName); + testRoleId = res.body.role._id; + }); + await request + .post(api('roles.addUserToRole')) + .set(credentials) + .send({ + roleId: testRoleId, + username: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + after(async () => { + await request.post(api('roles.delete')).set(credentials).send({ + roleId: testRoleId, + }); + await deleteUser(testUser); + }); + + it('should fail removing a user from a role when user does NOT have the access-permissions permission', async () => { + await request + .post(api('roles.removeUserFromRole')) + .set(testUserCredentials) + .send({ + roleId: testRoleId, + username: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + + it('should fail removing an invalid user from a role', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.removeUserFromRole')) + .set(credentials) + .send({ + roleId: testRoleId, + username: 'invalid-username', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-user'); + expect(res.body).to.have.property('error', 'There is no user with this username [error-invalid-user]'); + }); + }); + + it('should fail removing a user from an invalid role', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.removeUserFromRole')) + .set(credentials) + .send({ + roleId: 'invalid-role', + username: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-roleId'); + expect(res.body).to.have.property('error', 'This role does not exist [error-invalid-roleId]'); + }); + }); + + it('should fail removing a user from a role they do not have', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.removeUserFromRole')) + .set(credentials) + .send({ + roleId: 'admin', + username: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-user-not-in-role'); + expect(res.body).to.have.property('error', 'User is not in this role [error-user-not-in-role]'); + }); + }); + + it('should successfully remove a user from a role', async function () { + // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, + // ideally we should have a single CI job that adds a license and runs both CE and EE tests. + if (!isEnterprise) { + this.skip(); + return; + } + + await request + .post(api('roles.removeUserFromRole')) + .set(credentials) + .send({ + roleId: testRoleId, + username: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + await request + .get(api('roles.getUsersInRole')) + .set(credentials) + .query({ + role: testRoleId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('total', 0); + expect(res.body).to.have.property('users'); + expect(res.body.users).to.be.an('array').that.is.empty; + }); + }); + }); }); From 179ec818a6eed97fb189fbf7c2a313fd40810a4a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 25 Jun 2024 01:23:57 +0530 Subject: [PATCH 22/85] refactor!: removed listEmojiCustom method (#32542) --- apps/meteor/app/emoji-custom/server/index.ts | 1 - .../server/methods/listEmojiCustom.ts | 35 ------------------- 2 files changed, 36 deletions(-) delete mode 100644 apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts diff --git a/apps/meteor/app/emoji-custom/server/index.ts b/apps/meteor/app/emoji-custom/server/index.ts index 83866b83b5696..3ec2051f8fffb 100644 --- a/apps/meteor/app/emoji-custom/server/index.ts +++ b/apps/meteor/app/emoji-custom/server/index.ts @@ -1,5 +1,4 @@ import './startup/emoji-custom'; -import './methods/listEmojiCustom'; import './methods/deleteEmojiCustom'; import './methods/insertOrUpdateEmoji'; import './methods/uploadEmojiCustom'; diff --git a/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts b/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts deleted file mode 100644 index a16d536d92d45..0000000000000 --- a/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { IEmojiCustom } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { EmojiCustom } from '@rocket.chat/models'; -import { check, Match } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - listEmojiCustom(options?: { name?: string; aliases?: string[] }): IEmojiCustom[]; - } -} - -Meteor.methods({ - async listEmojiCustom(options = {}) { - methodDeprecationLogger.method('listEmojiCustom', '7.0.0'); - - const user = await Meteor.userAsync(); - - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'listEmojiCustom', - }); - } - - check(options, { - name: Match.Optional(String), - aliases: Match.Optional([String]), - }); - - return EmojiCustom.find(options).toArray(); - }, -}); From 15dfb92cd034faf76e3b1fdff825b02624158094 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 15 Aug 2024 16:46:40 -0300 Subject: [PATCH 23/85] chore!: removes view-history permission (#33042) Co-authored-by: Guilherme Gazzo --- .changeset/lovely-trees-call.md | 5 +++++ .../app/authorization/server/constant/permissions.ts | 1 - apps/meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v305.ts | 11 +++++++++++ packages/i18n/src/locales/af.i18n.json | 2 -- packages/i18n/src/locales/ar.i18n.json | 2 -- packages/i18n/src/locales/az.i18n.json | 2 -- packages/i18n/src/locales/be-BY.i18n.json | 2 -- packages/i18n/src/locales/bg.i18n.json | 2 -- packages/i18n/src/locales/bs.i18n.json | 2 -- packages/i18n/src/locales/ca.i18n.json | 2 -- packages/i18n/src/locales/cs.i18n.json | 2 -- packages/i18n/src/locales/cy.i18n.json | 2 -- packages/i18n/src/locales/da.i18n.json | 2 -- packages/i18n/src/locales/de-AT.i18n.json | 2 -- packages/i18n/src/locales/de-IN.i18n.json | 2 -- packages/i18n/src/locales/de.i18n.json | 2 -- packages/i18n/src/locales/el.i18n.json | 2 -- packages/i18n/src/locales/en.i18n.json | 2 -- packages/i18n/src/locales/eo.i18n.json | 2 -- packages/i18n/src/locales/es.i18n.json | 2 -- packages/i18n/src/locales/fa.i18n.json | 2 -- packages/i18n/src/locales/fi.i18n.json | 2 -- packages/i18n/src/locales/fr.i18n.json | 2 -- packages/i18n/src/locales/hi-IN.i18n.json | 2 -- packages/i18n/src/locales/hr.i18n.json | 2 -- packages/i18n/src/locales/hu.i18n.json | 2 -- packages/i18n/src/locales/id.i18n.json | 2 -- packages/i18n/src/locales/it.i18n.json | 2 -- packages/i18n/src/locales/ja.i18n.json | 2 -- packages/i18n/src/locales/ka-GE.i18n.json | 2 -- packages/i18n/src/locales/km.i18n.json | 2 -- packages/i18n/src/locales/ko.i18n.json | 2 -- packages/i18n/src/locales/ku.i18n.json | 2 -- packages/i18n/src/locales/lo.i18n.json | 2 -- packages/i18n/src/locales/lt.i18n.json | 2 -- packages/i18n/src/locales/lv.i18n.json | 2 -- packages/i18n/src/locales/mn.i18n.json | 2 -- packages/i18n/src/locales/ms-MY.i18n.json | 2 -- packages/i18n/src/locales/nl.i18n.json | 2 -- packages/i18n/src/locales/nn.i18n.json | 2 -- packages/i18n/src/locales/no.i18n.json | 2 -- packages/i18n/src/locales/pl.i18n.json | 2 -- packages/i18n/src/locales/pt-BR.i18n.json | 2 -- packages/i18n/src/locales/pt.i18n.json | 2 -- packages/i18n/src/locales/ro.i18n.json | 2 -- packages/i18n/src/locales/ru.i18n.json | 2 -- packages/i18n/src/locales/se.i18n.json | 2 -- packages/i18n/src/locales/sk-SK.i18n.json | 2 -- packages/i18n/src/locales/sl-SI.i18n.json | 2 -- packages/i18n/src/locales/sq.i18n.json | 2 -- packages/i18n/src/locales/sr.i18n.json | 2 -- packages/i18n/src/locales/sv.i18n.json | 2 -- packages/i18n/src/locales/ta-IN.i18n.json | 2 -- packages/i18n/src/locales/th-TH.i18n.json | 2 -- packages/i18n/src/locales/tr.i18n.json | 2 -- packages/i18n/src/locales/uk.i18n.json | 2 -- packages/i18n/src/locales/vi-VN.i18n.json | 2 -- packages/i18n/src/locales/zh-HK.i18n.json | 2 -- packages/i18n/src/locales/zh-TW.i18n.json | 2 -- packages/i18n/src/locales/zh.i18n.json | 2 -- 61 files changed, 17 insertions(+), 115 deletions(-) create mode 100644 .changeset/lovely-trees-call.md create mode 100644 apps/meteor/server/startup/migrations/v305.ts diff --git a/.changeset/lovely-trees-call.md b/.changeset/lovely-trees-call.md new file mode 100644 index 0000000000000..fcbfb4303ac5c --- /dev/null +++ b/.changeset/lovely-trees-call.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Removes `view-history` permission due to lack of usage diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts index f57943412fb40..c5ebb8f702d62 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -74,7 +74,6 @@ export const permissions = [ { _id: 'view-device-management', roles: ['admin'] }, { _id: 'view-engagement-dashboard', roles: ['admin'] }, { _id: 'view-full-other-user-info', roles: ['admin'] }, - { _id: 'view-history', roles: ['admin', 'user', 'anonymous'] }, { _id: 'view-joined-room', roles: ['guest', 'bot', 'app', 'anonymous'] }, { _id: 'view-join-code', roles: ['admin'] }, { _id: 'view-logs', roles: ['admin'] }, diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 4cda096b151c3..e7a18e836722b 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -37,5 +37,6 @@ import './v300'; import './v301'; import './v303'; import './v304'; +import './v305'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v305.ts b/apps/meteor/server/startup/migrations/v305.ts new file mode 100644 index 0000000000000..b840965458b82 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v305.ts @@ -0,0 +1,11 @@ +import { Permissions } from '@rocket.chat/models'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 305, + name: 'Remove unused view-history permission', + async up() { + await Permissions.deleteOne({ _id: 'view-history' }); + }, +}); diff --git a/packages/i18n/src/locales/af.i18n.json b/packages/i18n/src/locales/af.i18n.json index ecd47b5d96df8..b71bbdd474af4 100644 --- a/packages/i18n/src/locales/af.i18n.json +++ b/packages/i18n/src/locales/af.i18n.json @@ -2627,8 +2627,6 @@ "view-d-room_description": "Toestemming om direkte boodskappe te sien", "view-full-other-user-info": "Bekyk volledige ander gebruikersinligting", "view-full-other-user-info_description": "Toestemming om die volledige profiel van ander gebruikers te sien, insluitend die rekening skep datum, laaste login, ens.", - "view-history": "Bekyk Geskiedenis", - "view-history_description": "Toestemming om die kanaalgeskiedenis te sien", "view-join-code": "View Sluit by Kode aan", "view-join-code_description": "Toestemming om die kanaalkode te sien", "view-joined-room": "Bekyk aangeslote kamer", diff --git a/packages/i18n/src/locales/ar.i18n.json b/packages/i18n/src/locales/ar.i18n.json index d78474c2429ae..d70af13aa54f8 100644 --- a/packages/i18n/src/locales/ar.i18n.json +++ b/packages/i18n/src/locales/ar.i18n.json @@ -4557,8 +4557,6 @@ "View_full_conversation": "عرض المحادثة بالكامل", "view-full-other-user-info": "عرض كل معلومات المستخدمين الآخرين", "view-full-other-user-info_description": "إذن لعرض الملف الشخصي الكامل للمستخدمين الآخرين بما في ذلك تاريخ إنشاء الحساب وآخر تسجيل دخول، وما إلى ذلك.", - "view-history": "عرض المحفوظات", - "view-history_description": "إذن لعرض محفوظات القناة", "view-join-code": "عرض رمز الانضمام", "view-join-code_description": "إذن لعرض رمز الانضمام للقناة", "view-joined-room": "عرض Room التي تم الانضمام إليها", diff --git a/packages/i18n/src/locales/az.i18n.json b/packages/i18n/src/locales/az.i18n.json index 7117c9fe19222..d4ac33cb2279a 100644 --- a/packages/i18n/src/locales/az.i18n.json +++ b/packages/i18n/src/locales/az.i18n.json @@ -2627,8 +2627,6 @@ "view-d-room_description": "Birbaşa mesajları görmək icazəsi", "view-full-other-user-info": "Tam Digər İstifadəçi Bilgilerini Baxın", "view-full-other-user-info_description": "Hesab yaratma tarixi, son giriş və s. Daxil olmaqla, digər istifadəçilərin tam profilini görmək üçün icazə.", - "view-history": "Tarix bax", - "view-history_description": "Kanalın tarixini görmək üçün icazə", "view-join-code": "Görünüşü Qəbul et", "view-join-code_description": "Kanala qoşulmaq kodunu keçirmək üçün icazə", "view-joined-room": "Qəbul otağını göstərin", diff --git a/packages/i18n/src/locales/be-BY.i18n.json b/packages/i18n/src/locales/be-BY.i18n.json index 9700320e06b5c..9a6956cbf54e7 100644 --- a/packages/i18n/src/locales/be-BY.i18n.json +++ b/packages/i18n/src/locales/be-BY.i18n.json @@ -2645,8 +2645,6 @@ "view-d-room_description": "Дазвол для прагляду прамых паведамленняў", "view-full-other-user-info": "Прагляд поўнага Іншая Інфармацыя пра карыстальніка", "view-full-other-user-info_description": "Дазвол для прагляду ў поўны профіль іншых карыстальнікаў, уключаючы дату стварэння ўліковага запісу, апошні ўваход і г.д.", - "view-history": "прагляд гісторыі", - "view-history_description": "Дазвол для прагляду гісторыі канала", "view-join-code": "Прагледзець Рэгістрацыя код", "view-join-code_description": "Дазвол для прагляду канала далучыцца код", "view-joined-room": "Паглядзець Дата нумар", diff --git a/packages/i18n/src/locales/bg.i18n.json b/packages/i18n/src/locales/bg.i18n.json index 92b08e2f4171c..8eb016f16eb12 100644 --- a/packages/i18n/src/locales/bg.i18n.json +++ b/packages/i18n/src/locales/bg.i18n.json @@ -2623,8 +2623,6 @@ "view-d-room_description": "Разрешение за преглеждане на директни съобщения", "view-full-other-user-info": "Преглед на пълната друга информация за потребителя", "view-full-other-user-info_description": "Разрешение за преглед на пълния потребителски профил на други потребители, включително датата на създаване на сметката, последното влизане и др.", - "view-history": "Преглед на историята", - "view-history_description": "Разрешение за преглед на историята на канала", "view-join-code": "Вижте кода за присъединяване", "view-join-code_description": "Разрешение за преглеждане на кода за присъединяване на канала", "view-joined-room": "Преглед на присъединилата се стая", diff --git a/packages/i18n/src/locales/bs.i18n.json b/packages/i18n/src/locales/bs.i18n.json index ece277df928ac..3be570d0eb9d9 100644 --- a/packages/i18n/src/locales/bs.i18n.json +++ b/packages/i18n/src/locales/bs.i18n.json @@ -2620,8 +2620,6 @@ "view-d-room_description": "Dopuštenje za prikaz izravnih poruka", "view-full-other-user-info": "Pogledajte ostale korisničke informacije", "view-full-other-user-info_description": "Dopuštenje za pregled cijelog profila drugih korisnika, uključujući datum stvaranja računa, posljednju prijavu itd.", - "view-history": "Prikaži povijest", - "view-history_description": "Dopuštenje za prikaz povijesti kanala", "view-join-code": "Pogledajte pridruživanje kodu", "view-join-code_description": "Dopuštenje za pregled kanala za pridruživanje kanalu", "view-joined-room": "Pogledajte povezanu sobu", diff --git a/packages/i18n/src/locales/ca.i18n.json b/packages/i18n/src/locales/ca.i18n.json index a9f616759b98a..6544ddff278ba 100644 --- a/packages/i18n/src/locales/ca.i18n.json +++ b/packages/i18n/src/locales/ca.i18n.json @@ -4465,8 +4465,6 @@ "View_full_conversation": "Veure conversa completa", "view-full-other-user-info": "Veure tota la info d'usuaris", "view-full-other-user-info_description": "Permís per veure el perfil complet d'altres usuaris, incloent la data de creació del compte, el darrer accés, etcètera.", - "view-history": "Veure historial", - "view-history_description": "Permís per veure l'historial del canal", "view-join-code": "Veure el codi per unir-se", "view-join-code_description": "Permís per veure el codi per unir-se al canal", "view-joined-room": "Veure Room unida", diff --git a/packages/i18n/src/locales/cs.i18n.json b/packages/i18n/src/locales/cs.i18n.json index 60ffac0aebef0..d1fdb8411264f 100644 --- a/packages/i18n/src/locales/cs.i18n.json +++ b/packages/i18n/src/locales/cs.i18n.json @@ -3786,8 +3786,6 @@ "view-d-room_description": "Právo zobrazit soukromé zprávy", "view-full-other-user-info": "Zobrazit veškeré informace uživatele", "view-full-other-user-info_description": "Právo zobrazit veškeré informace uživatele včetně data vytvoření, posledního přihlášení, atd.", - "view-history": "Zobrazit historii", - "view-history_description": "Právo zobrazit historii místnosti", "view-join-code": "Zobrazit kód místnosti", "view-join-code_description": "Právo zobrazit kód místnosti", "view-joined-room": "Zobrazit připojené místnosti", diff --git a/packages/i18n/src/locales/cy.i18n.json b/packages/i18n/src/locales/cy.i18n.json index c8e3efc5f7a61..f39c7f2b9935a 100644 --- a/packages/i18n/src/locales/cy.i18n.json +++ b/packages/i18n/src/locales/cy.i18n.json @@ -2621,8 +2621,6 @@ "view-d-room_description": "Caniatâd i weld negeseuon uniongyrchol", "view-full-other-user-info": "Edrychwch ar Wybodaeth Defnyddiwr Llawn Arall", "view-full-other-user-info_description": "Caniatâd i weld proffil llawn defnyddwyr eraill gan gynnwys dyddiad creu cyfrif, mewngofnodi diwethaf, ac ati.", - "view-history": "Gweld Hanes", - "view-history_description": "Caniatâd i weld hanes y sianel", "view-join-code": "Gweld Ymuno â'r Cod", "view-join-code_description": "Caniatâd i weld y sianel ymuno â'r cod", "view-joined-room": "Gweld yr Ystafell Gyfun", diff --git a/packages/i18n/src/locales/da.i18n.json b/packages/i18n/src/locales/da.i18n.json index af2cdc5893acd..f6dc28df9f84b 100644 --- a/packages/i18n/src/locales/da.i18n.json +++ b/packages/i18n/src/locales/da.i18n.json @@ -3895,8 +3895,6 @@ "view-d-room_description": "Tilladelse til at se direkte meddelelser", "view-full-other-user-info": "Se fuld anden brugerinformation", "view-full-other-user-info_description": "Tilladelse til at se fuld profil for andre brugere, herunder oprettelse af konto, sidste login osv.", - "view-history": "Se historik", - "view-history_description": "Tilladelse til at se kanalhistorikken", "view-join-code": "View Tilmeld Kode", "view-join-code_description": "Tilladelse til at se kanalens adgangskode", "view-joined-room": "Se det tilsluttede rum", diff --git a/packages/i18n/src/locales/de-AT.i18n.json b/packages/i18n/src/locales/de-AT.i18n.json index b45c4db185ea9..aa4bea9350666 100644 --- a/packages/i18n/src/locales/de-AT.i18n.json +++ b/packages/i18n/src/locales/de-AT.i18n.json @@ -2629,8 +2629,6 @@ "view-d-room_description": "Berechtigung zum Anzeigen von direkten Nachrichten", "view-full-other-user-info": "Vollständige Benutzerinformationen anzeigen", "view-full-other-user-info_description": "Berechtigung zum Anzeigen des vollständigen Profils anderer Benutzer, einschließlich Kontoerstellungsdatum, letzte Anmeldung usw.", - "view-history": "Siehe Verlauf", - "view-history_description": "Berechtigung zum Anzeigen des Kanalverlaufs", "view-join-code": "Zeigen Sie den Verbindungscode an", "view-join-code_description": "Berechtigung zum Anzeigen des Channel-Join-Codes", "view-joined-room": "Verbundenen Raum anzeigen", diff --git a/packages/i18n/src/locales/de-IN.i18n.json b/packages/i18n/src/locales/de-IN.i18n.json index 94949713940ae..c58e77105882f 100644 --- a/packages/i18n/src/locales/de-IN.i18n.json +++ b/packages/i18n/src/locales/de-IN.i18n.json @@ -2962,8 +2962,6 @@ "view-d-room_description": "Berechtigung, Direktnachrichten zu erhalten", "view-full-other-user-info": "Vollständige Benutzerinformation einsehen", "view-full-other-user-info_description": "Berechtigung, die vollständigen Benutzerinformation anderer Benutzer einzusehen (inkl. Erstelldatum, letztem Login etc.)", - "view-history": "Historie anzeigen", - "view-history_description": "Berechtigung, die Kanal-Historie anzuzeigen", "view-join-code": "Beitritts-Code anzeigen", "view-join-code_description": "Berechtigung, den Beitritts-Code zu einem Kanal anzuzeigen", "view-joined-room": "Beigetretenen Raum anzeigen", diff --git a/packages/i18n/src/locales/de.i18n.json b/packages/i18n/src/locales/de.i18n.json index 7d83471fc3d49..253b4987e40de 100644 --- a/packages/i18n/src/locales/de.i18n.json +++ b/packages/i18n/src/locales/de.i18n.json @@ -5129,8 +5129,6 @@ "View_full_conversation": "Vollständige Konversation anzeigen", "view-full-other-user-info": "Vollständige Benutzerinformation einsehen", "view-full-other-user-info_description": "Berechtigung, die vollständigen Benutzerinformation anderer Benutzer einzusehen (inkl. Erstelldatum, letztem Login etc.)", - "view-history": "Verlauf anzeigen", - "view-history_description": "Berechtigung, den Channel-Verlauf anzuzeigen", "view-join-code": "Beitritts-Code anzeigen", "view-join-code_description": "Berechtigung, den Beitritts-Code zu einem Channel anzuzeigen", "view-joined-room": "Beigetretenen Room anzeigen", diff --git a/packages/i18n/src/locales/el.i18n.json b/packages/i18n/src/locales/el.i18n.json index f0da1f90ba5f2..6c1fe148c1197 100644 --- a/packages/i18n/src/locales/el.i18n.json +++ b/packages/i18n/src/locales/el.i18n.json @@ -2634,8 +2634,6 @@ "view-d-room_description": "Άδεια προβολής άμεσων μηνυμάτων", "view-full-other-user-info": "Δείτε όλες τις άλλες πληροφορίες χρήστη", "view-full-other-user-info_description": "Άδεια προβολής πλήρους προφίλ άλλων χρηστών, συμπεριλαμβανομένης της ημερομηνίας δημιουργίας λογαριασμού, της τελευταίας σύνδεσης, κλπ.", - "view-history": "Προβολή ιστορικού", - "view-history_description": "Άδεια προβολής ιστορικού καναλιών", "view-join-code": "Προβολή σύνδεσης κώδικα", "view-join-code_description": "Άδεια προβολής του κωδικού πρόσθεσης καναλιού", "view-joined-room": "Δείτε την ενταγμένη αίθουσα", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 5ee9a24b8eaaf..f7eb8d638420b 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -5880,8 +5880,6 @@ "View_full_conversation": "View full conversation", "view-full-other-user-info": "View Full Other User Info", "view-full-other-user-info_description": "Permission to view full profile of other users including account creation date, last login, etc.", - "view-history": "View History", - "view-history_description": "Permission to view the channel history", "onboarding.component.form.action.registerNow": "Register now", "view-join-code": "View Join Code", "view-join-code_description": "Permission to view the channel join code", diff --git a/packages/i18n/src/locales/eo.i18n.json b/packages/i18n/src/locales/eo.i18n.json index 3895650a69499..4148469b335bc 100644 --- a/packages/i18n/src/locales/eo.i18n.json +++ b/packages/i18n/src/locales/eo.i18n.json @@ -2627,8 +2627,6 @@ "view-d-room_description": "Permeso por vidi rektajn mesaĝojn", "view-full-other-user-info": "Vidi Plena Aliaj Uzaj Informoj", "view-full-other-user-info_description": "Permeso por vidi plenan profilon de aliaj uzantoj inkluzive de kreo de konto dato, lasta ensaluto, ktp.", - "view-history": "Vidi historion", - "view-history_description": "Permeso por vidi la kanalan historion", "view-join-code": "Vidi Aliĝilon", "view-join-code_description": "Permeso por vidi la kanalon kunigi kodon", "view-joined-room": "Vidi Joined Room", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index a4cfab497f2b3..a486580cf821e 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -4537,8 +4537,6 @@ "View_full_conversation": "Ver conversación completa", "view-full-other-user-info": "Ver toda la información de otro usuario", "view-full-other-user-info_description": "Permiso para ver el perfil completo de otros usuarios, incluida la fecha de creación de la cuenta, el último inicio de sesión, etc.", - "view-history": "Ver historial", - "view-history_description": "Permiso para ver el historial del canal", "view-join-code": "Ver código de participación", "view-join-code_description": "Permiso para ver el código de participación en un canal", "view-joined-room": "Ver Room de participación", diff --git a/packages/i18n/src/locales/fa.i18n.json b/packages/i18n/src/locales/fa.i18n.json index 0e85d3bc17ed2..72bfc5ccc9b50 100644 --- a/packages/i18n/src/locales/fa.i18n.json +++ b/packages/i18n/src/locales/fa.i18n.json @@ -2962,8 +2962,6 @@ "view-d-room_description": "اجازه مشاهده پیام های مستقیم", "view-full-other-user-info": "مشاهده سایر اطلاعات کاربر", "view-full-other-user-info_description": "اجازه مشاهده نمایه کامل سایر کاربران از جمله تاریخ ایجاد حساب، آخرین ورود و غیره", - "view-history": "مشاهده تاریخچه", - "view-history_description": "اجازه مشاهده تاریخچه کانال", "view-join-code": "مشاهده تاریخ کد", "view-join-code_description": "مجاز به مشاهده کد کانال کانال", "view-joined-room": "مشاهده اتاق اعضا", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index e8cb35434aef6..ebeafeb0f1d18 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -5245,8 +5245,6 @@ "View_full_conversation": "Näytä koko keskustelu", "view-full-other-user-info": "Näytä täydelliset muut käyttäjän tiedot", "view-full-other-user-info_description": "Oikeus tarkastella muiden käyttäjien täydellistä profiilia, mukaan lukien tilin luontipäivä, viimeinen sisäänkirjautuminen jne.", - "view-history": "Näytä historia", - "view-history_description": "Oikeus tarkastella kanavan historiaa", "onboarding.component.form.action.registerNow": "Rekisteröidy nyt", "view-join-code": "Näytä liittymiskoodi", "view-join-code_description": "Oikeus tarkastella kanavan liittymiskoodia", diff --git a/packages/i18n/src/locales/fr.i18n.json b/packages/i18n/src/locales/fr.i18n.json index 8e3f609dd663a..29e7704b4c3a3 100644 --- a/packages/i18n/src/locales/fr.i18n.json +++ b/packages/i18n/src/locales/fr.i18n.json @@ -4555,8 +4555,6 @@ "View_full_conversation": "Afficher la conversation complète", "view-full-other-user-info": "Voir les informations des autres utilisateurs", "view-full-other-user-info_description": "Autorisation d'afficher le profil complet des autres utilisateurs, y compris la date de création du compte, la dernière connexion, etc.", - "view-history": "Voir l'historique", - "view-history_description": "Autorisation de consulter l'historique du canal", "view-join-code": "Afficher le code de participation", "view-join-code_description": "Autorisation d'afficher le code de participation au canal", "view-joined-room": "Voir le salon rejoint", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index 2ff12e76f855c..d75e30c2c9229 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -5526,8 +5526,6 @@ "View_full_conversation": "पूरी बातचीत देखें", "view-full-other-user-info": "अन्य उपयोगकर्ता की पूरी जानकारी देखें", "view-full-other-user-info_description": "खाता निर्माण तिथि, अंतिम लॉगिन आदि सहित अन्य उपयोगकर्ताओं की पूरी प्रोफ़ाइल देखने की अनुमति।", - "view-history": "इतिहास देखें", - "view-history_description": "चैनल इतिहास देखने की अनुमति", "view-join-code": "जॉइन कोड देखें", "view-join-code_description": "चैनल जॉइन कोड देखने की अनुमति", "view-joined-room": "सम्मिलित कक्ष देखें", diff --git a/packages/i18n/src/locales/hr.i18n.json b/packages/i18n/src/locales/hr.i18n.json index c13f5f016da0f..2b9d5fc3340c8 100644 --- a/packages/i18n/src/locales/hr.i18n.json +++ b/packages/i18n/src/locales/hr.i18n.json @@ -2761,8 +2761,6 @@ "view-d-room_description": "Dopuštenje za prikaz izravnih poruka", "view-full-other-user-info": "Pogledajte ostale korisničke informacije", "view-full-other-user-info_description": "Dopuštenje za pregled cijelog profila drugih korisnika, uključujući datum stvaranja računa, posljednju prijavu itd.", - "view-history": "Prikaži povijest", - "view-history_description": "Dopuštenje za prikaz povijesti kanala", "view-join-code": "Pogledajte pridruživanje kodu", "view-join-code_description": "Dopuštenje za pregled kanala za pridruživanje kanalu", "view-joined-room": "Pogledajte povezanu sobu", diff --git a/packages/i18n/src/locales/hu.i18n.json b/packages/i18n/src/locales/hu.i18n.json index ba8ccf5e3e057..1165f2d1294de 100644 --- a/packages/i18n/src/locales/hu.i18n.json +++ b/packages/i18n/src/locales/hu.i18n.json @@ -5044,8 +5044,6 @@ "View_full_conversation": "Teljes beszélgetés megtekintése", "view-full-other-user-info": "Mások teljes felhasználó-információinak megtekintése", "view-full-other-user-info_description": "Jogosultság más felhasználók teljes profiljának megtekintéséhez, beleértve a fiók létrehozásának dátumát, az utolsó bejelentkezést stb.", - "view-history": "Előzmények megtekintése", - "view-history_description": "Jogosultság a csatorna előzményeinek megtekintéséhez", "view-join-code": "Csatlakozási kód megtekintése", "view-join-code_description": "Jogosultság a csatorna csatlakozási kódjának megtekintéséhez", "view-joined-room": "Csatlakozott szoba megtekintése", diff --git a/packages/i18n/src/locales/id.i18n.json b/packages/i18n/src/locales/id.i18n.json index 2cbf226f01632..3131657ec519b 100644 --- a/packages/i18n/src/locales/id.i18n.json +++ b/packages/i18n/src/locales/id.i18n.json @@ -2635,8 +2635,6 @@ "view-d-room_description": "Izin untuk melihat pesan langsung", "view-full-other-user-info": "Lihat Info Pengguna Lengkap Lainnya", "view-full-other-user-info_description": "Izin untuk melihat profil lengkap pengguna lain termasuk tanggal pembuatan akun, login terakhir, dll.", - "view-history": "Lihat Riwayat", - "view-history_description": "Izin untuk melihat riwayat saluran", "view-join-code": "Lihat Kode Gabung", "view-join-code_description": "Izin untuk melihat kode join channel", "view-joined-room": "Lihat Ruang Bergabung", diff --git a/packages/i18n/src/locales/it.i18n.json b/packages/i18n/src/locales/it.i18n.json index a7c9c71c9fb9e..9a13dddc370ae 100644 --- a/packages/i18n/src/locales/it.i18n.json +++ b/packages/i18n/src/locales/it.i18n.json @@ -3214,8 +3214,6 @@ "view-d-room_description": "Autorizzazione a visualizzare i messaggi diretti", "view-full-other-user-info": "Visualizza tutte le altre informazioni utente", "view-full-other-user-info_description": "Autorizzazione a visualizzare il profilo completo di altri utenti tra cui data di creazione dell'account, ultimo accesso, ecc.", - "view-history": "Visualizza cronologia", - "view-history_description": "Autorizzazione a visualizzare la cronologia del canale", "view-join-code": "Visualizza unire il codice", "view-join-code_description": "Autorizzazione a visualizzare il codice di unione del canale", "view-joined-room": "Visualizza camera unita", diff --git a/packages/i18n/src/locales/ja.i18n.json b/packages/i18n/src/locales/ja.i18n.json index 3d6b7a69aeb2a..cdf67b056bd8a 100644 --- a/packages/i18n/src/locales/ja.i18n.json +++ b/packages/i18n/src/locales/ja.i18n.json @@ -4498,8 +4498,6 @@ "View_full_conversation": "すべての会話を表示", "view-full-other-user-info": "他のすべてのユーザー情報の表示", "view-full-other-user-info_description": "アカウント作成日、最終ログインなど、他のユーザーの完全なプロフィールを表示する権限", - "view-history": "履歴の表示", - "view-history_description": "チャネル履歴を表示する権限", "view-join-code": "参加コードの表示", "view-join-code_description": "チャネル参加コードを表示する権限", "view-joined-room": "参加したRoomの表示", diff --git a/packages/i18n/src/locales/ka-GE.i18n.json b/packages/i18n/src/locales/ka-GE.i18n.json index cd5f35d0a97a3..aac3c689457b0 100644 --- a/packages/i18n/src/locales/ka-GE.i18n.json +++ b/packages/i18n/src/locales/ka-GE.i18n.json @@ -3513,8 +3513,6 @@ "view-d-room_description": "პირდაპირი შეტყობინებების ნახვის უფლება", "view-full-other-user-info": "იხილეთ სხვა მომხმარებლის დრული ინფორმაცია", "view-full-other-user-info_description": "სხვა მომხმარებლების სრული პროფილის ნახვის ნებართვა, მათ შორის ანგარიშის შექმნის თარიღის, ბოლო შესვლის და ა.შ.", - "view-history": "ისტორიის ნახვა", - "view-history_description": "არხის ისტორიის ნახვის ნებართვა", "view-join-code": "იხილეთ გაწევრიანების კოდი", "view-join-code_description": "არხში გაწევრიანების კოდის ნახვის ნებართვა", "view-joined-room": "ნახეთ შეერთებული ოთახი", diff --git a/packages/i18n/src/locales/km.i18n.json b/packages/i18n/src/locales/km.i18n.json index 509814035ad4b..4217963fa8a2a 100644 --- a/packages/i18n/src/locales/km.i18n.json +++ b/packages/i18n/src/locales/km.i18n.json @@ -2976,8 +2976,6 @@ "view-d-room_description": "ការអនុញ្ញាតដើម្បីមើលសារដោយផ្ទាល់", "view-full-other-user-info": "មើលពេញលេញព័ត៌មានអ្នកប្រើផ្សេងទៀត", "view-full-other-user-info_description": "ការអនុញ្ញាតដើម្បីមើលទម្រង់ពេញលេញនៃអ្នកប្រើផ្សេងទៀតរួមបញ្ចូលកាលបរិច្ឆេទបង្កើតគណនីការចូលចុងក្រោយ។ ល។", - "view-history": "មើលប្រវត្តិ", - "view-history_description": "សិទ្ធិដើម្បីមើលប្រវត្តិឆានែល", "view-join-code": "មើលភ្ជាប់កូដ", "view-join-code_description": "សិទ្ធិដើម្បីមើលឆានែលភ្ជាប់កូដ", "view-joined-room": "មើលបន្ទប់ចូលរួម", diff --git a/packages/i18n/src/locales/ko.i18n.json b/packages/i18n/src/locales/ko.i18n.json index 054d079e469f0..76472af94d5f0 100644 --- a/packages/i18n/src/locales/ko.i18n.json +++ b/packages/i18n/src/locales/ko.i18n.json @@ -3843,8 +3843,6 @@ "view-d-room_description": "개인 대화방을 볼 수 있는 권한", "view-full-other-user-info": "다른 사용자 전체 정보 보기", "view-full-other-user-info_description": "계정 생성 날짜, 마지막 로그인 등 다른 사용자의 전체 프로필을 볼 수 있는 권한", - "view-history": "기록 보기", - "view-history_description": "채널 기록을 볼 수 있는 권한", "view-join-code": "가입 코드 보기", "view-join-code_description": "채널 가입 코드를 볼 수 있는 권한", "view-joined-room": "참여한 대화방 보기", diff --git a/packages/i18n/src/locales/ku.i18n.json b/packages/i18n/src/locales/ku.i18n.json index 2b520a699a550..4e94705ce77f9 100644 --- a/packages/i18n/src/locales/ku.i18n.json +++ b/packages/i18n/src/locales/ku.i18n.json @@ -2620,8 +2620,6 @@ "view-d-room_description": "Destûra ku peyamên rasterast bibînin", "view-full-other-user-info": "Agahdariya din Full Info Bikarhêner", "view-full-other-user-info_description": "Destûra ku ji bo bikarhênerên çêkirina hesab, navnîşana dawîn, etc.", - "view-history": "Dîroka View", - "view-history_description": "Destûrkirina dîroka kanala xwe bibînin", "view-join-code": "Dîtin beşdar bikin", "view-join-code_description": "Destûra ku kanala kanalê kodê bibin", "view-joined-room": "Nêrîngeha Xwendegehê bibînin", diff --git a/packages/i18n/src/locales/lo.i18n.json b/packages/i18n/src/locales/lo.i18n.json index 4c7819cea53ab..f4ddab5a2a581 100644 --- a/packages/i18n/src/locales/lo.i18n.json +++ b/packages/i18n/src/locales/lo.i18n.json @@ -2664,8 +2664,6 @@ "view-d-room_description": "ການອະນຸຍາດໃຫ້ເບິ່ງຂໍ້ຄວາມໂດຍກົງ", "view-full-other-user-info": "ເບິ່ງຂໍ້ມູນຜູ້ໃຊ້ອື່ນໆຢ່າງເຕັມທີ່", "view-full-other-user-info_description": "ການອະນຸຍາດໃຫ້ເບິ່ງລາຍລະອຽດຂອງຜູ້ໃຊ້ອື່ນໆລວມທັງວັນສ້າງບັນຊີ, ການເຂົ້າສູ່ລະບົບຄັ້ງສຸດທ້າຍ, ແລະອື່ນໆ.", - "view-history": "ເບິ່ງປະຫວັດສາດ", - "view-history_description": "ການອະນຸຍາດໃຫ້ເບິ່ງປະຫວັດຂອງຊ່ອງທາງ", "view-join-code": "ເບິ່ງລະຫັດເຂົ້າຮ່ວມ", "view-join-code_description": "ການອະນຸຍາດໃຫ້ເບິ່ງຊ່ອງເຂົ້າຮ່ວມລະຫັດ", "view-joined-room": "ເບິ່ງຫ້ອງທີ່ເຂົ້າຮ່ວມ", diff --git a/packages/i18n/src/locales/lt.i18n.json b/packages/i18n/src/locales/lt.i18n.json index ea0f7775cc9dc..131eb20d4e89f 100644 --- a/packages/i18n/src/locales/lt.i18n.json +++ b/packages/i18n/src/locales/lt.i18n.json @@ -2682,8 +2682,6 @@ "view-d-room_description": "Leidimas peržiūrėti tiesioginius pranešimus", "view-full-other-user-info": "Peržiūrėti visą kitą vartotojo informaciją", "view-full-other-user-info_description": "Leidimas peržiūrėti visus naudotojų profilius, įskaitant paskyros sukūrimo datą, paskutinį prisijungimą ir kt.", - "view-history": "Žiūrėti istoriją", - "view-history_description": "Leidimas peržiūrėti kanalo istoriją", "view-join-code": "Peržiūrėti prisijungimo kodą", "view-join-code_description": "Leidimas peržiūrėti kanalo prisijungimo kodą", "view-joined-room": "Žiūrėti įjungtą kambarį", diff --git a/packages/i18n/src/locales/lv.i18n.json b/packages/i18n/src/locales/lv.i18n.json index c61b464997994..8fe44db8ba3d5 100644 --- a/packages/i18n/src/locales/lv.i18n.json +++ b/packages/i18n/src/locales/lv.i18n.json @@ -2625,8 +2625,6 @@ "view-d-room_description": "Atļauja skatīt ziņojumus", "view-full-other-user-info": "Skatīt citu lietotāju informāciju", "view-full-other-user-info_description": "Atļauja, lai skatītu citu lietotāju pilnu profilu, tostarp konta izveides datumu, pēdējo pieteikšanos u.c.", - "view-history": "Skatīt vēsturi", - "view-history_description": "Atļauja skatīt kanāla vēsturi", "view-join-code": "Skatīt pievienošanās kodu", "view-join-code_description": "Atļauja skatīt kanāla pievienošanās kodu", "view-joined-room": "Skatīt istabu kurā pievienojies", diff --git a/packages/i18n/src/locales/mn.i18n.json b/packages/i18n/src/locales/mn.i18n.json index 1516959dd2143..d66141b425fbe 100644 --- a/packages/i18n/src/locales/mn.i18n.json +++ b/packages/i18n/src/locales/mn.i18n.json @@ -2617,8 +2617,6 @@ "view-d-room_description": "Шууд мессеж харах зөвшөөрөл", "view-full-other-user-info": "Бүрэн Бусад Хэрэглэгчийн мэдээллийг харах", "view-full-other-user-info_description": "Бүртгэл үүсгэх огноо, сүүлчийн нэвтрэх гэх мэт бусад хэрэглэгчдийн бүрэн профайлыг үзэх зөвшөөрөл.", - "view-history": "Түүхийг харах", - "view-history_description": "Сувгийн түүхийг харах зөвшөөрөл", "view-join-code": "Сумтай кодыг харах", "view-join-code_description": "Суваг харах кодыг зөвшөөрөх", "view-joined-room": "Гишүүн элсүүлэх өрөө харах", diff --git a/packages/i18n/src/locales/ms-MY.i18n.json b/packages/i18n/src/locales/ms-MY.i18n.json index ba690e8e21d76..42156cb7aaebe 100644 --- a/packages/i18n/src/locales/ms-MY.i18n.json +++ b/packages/i18n/src/locales/ms-MY.i18n.json @@ -2631,8 +2631,6 @@ "view-d-room_description": "Kebenaran untuk melihat mesej langsung", "view-full-other-user-info": "Lihat Maklumat Pengguna Lain Penuh", "view-full-other-user-info_description": "Kebenaran untuk melihat profil pengguna lain termasuk tarikh penciptaan akaun, log masuk terakhir, dan sebagainya.", - "view-history": "Lihat Sejarah", - "view-history_description": "Kebenaran untuk melihat sejarah saluran", "view-join-code": "Lihat Sertai Kod", "view-join-code_description": "Kebenaran untuk melihat saluran menyertai kod", "view-joined-room": "Lihat Bilik Gabung", diff --git a/packages/i18n/src/locales/nl.i18n.json b/packages/i18n/src/locales/nl.i18n.json index c2c8c2cbb4a57..b9500f2305996 100644 --- a/packages/i18n/src/locales/nl.i18n.json +++ b/packages/i18n/src/locales/nl.i18n.json @@ -4540,8 +4540,6 @@ "View_full_conversation": "Bekijk het volledige gesprek", "view-full-other-user-info": "Bekijk volledige andere gebruikersinformatie", "view-full-other-user-info_description": "Toestemming om het volledige profiel van andere gebruikers te bekijken, inclusief aanmaakdatum van het account, laatste login, enz.", - "view-history": "Bekijk geschiedenis", - "view-history_description": "Toestemming om de kanaalgeschiedenis te bekijken", "view-join-code": "Bekijk de deelnamecode", "view-join-code_description": "Toestemming om de kanaalverbindingscode te bekijken", "view-joined-room": "Bekijk toegetreden kamers", diff --git a/packages/i18n/src/locales/nn.i18n.json b/packages/i18n/src/locales/nn.i18n.json index 1ef3ae519b21a..9786da87acdcc 100644 --- a/packages/i18n/src/locales/nn.i18n.json +++ b/packages/i18n/src/locales/nn.i18n.json @@ -4331,8 +4331,6 @@ "view-device-management": "Se enhetsstyring", "view-full-other-user-info": "Se full annen brukerinformasjon", "view-full-other-user-info_description": "Tillatelse til å se hele profilen til andre brukere, inkludert kontoopprettelsesdato, siste innlogging, etc.", - "view-history": "Se historikk", - "view-history_description": "Tillatelse til å se kanalhistorikken", "onboarding.component.form.action.registerNow": "Registrer deg nå", "view-join-code": "Vis Bli medlem", "view-join-code_description": "Tillatelse til å vise kanalen bli med koden", diff --git a/packages/i18n/src/locales/no.i18n.json b/packages/i18n/src/locales/no.i18n.json index 1ef3ae519b21a..9786da87acdcc 100644 --- a/packages/i18n/src/locales/no.i18n.json +++ b/packages/i18n/src/locales/no.i18n.json @@ -4331,8 +4331,6 @@ "view-device-management": "Se enhetsstyring", "view-full-other-user-info": "Se full annen brukerinformasjon", "view-full-other-user-info_description": "Tillatelse til å se hele profilen til andre brukere, inkludert kontoopprettelsesdato, siste innlogging, etc.", - "view-history": "Se historikk", - "view-history_description": "Tillatelse til å se kanalhistorikken", "onboarding.component.form.action.registerNow": "Registrer deg nå", "view-join-code": "Vis Bli medlem", "view-join-code_description": "Tillatelse til å vise kanalen bli med koden", diff --git a/packages/i18n/src/locales/pl.i18n.json b/packages/i18n/src/locales/pl.i18n.json index 667f78c5b514e..bb0feccfd6383 100644 --- a/packages/i18n/src/locales/pl.i18n.json +++ b/packages/i18n/src/locales/pl.i18n.json @@ -5039,8 +5039,6 @@ "View_full_conversation": "Zobacz pełną rozmowę", "view-full-other-user-info": "Wyświetl pełną informację o użytkowniku", "view-full-other-user-info_description": "Zezwolenie na wyświetlanie pełnego profilu innych użytkowników, w tym daty utworzenia konta, ostatniego logowania itp.", - "view-history": "Wyświetl historię", - "view-history_description": "Zezwolenie na przeglądanie historii kanału", "view-join-code": "Wyświetl kod dołączenia do kanału", "view-join-code_description": "Zezwolenie na przeglądanie kodu dołączenia do kanału", "view-joined-room": "Wyświetl Połączone pokoje", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 0f9bba883b9c1..a5e79c8ae9db7 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -4697,8 +4697,6 @@ "View_full_conversation": "Visualizar conversa completa", "view-full-other-user-info": "Visualizar informações completas de outros usuários", "view-full-other-user-info_description": "Permissão para visualizar o perfil completo de outros usuários, incluindo a data de criação da conta, último login, etc.", - "view-history": "Ver histórico", - "view-history_description": "Permissão para visualizar o histórico de canais", "view-join-code": "Ver código de associação", "view-join-code_description": "Permissão para visualizar o código de associação do canal", "view-joined-room": "Ver sala incorporada", diff --git a/packages/i18n/src/locales/pt.i18n.json b/packages/i18n/src/locales/pt.i18n.json index 3e81cead35644..62316ffb6f15b 100644 --- a/packages/i18n/src/locales/pt.i18n.json +++ b/packages/i18n/src/locales/pt.i18n.json @@ -3031,8 +3031,6 @@ "view-d-room_description": "Permissão para visualizar mensagens directas", "view-full-other-user-info": "Visualizar informações completas de outros utilizadores", "view-full-other-user-info_description": "Permissão para visualizar o perfil completo de outros utilizadores, incluindo a data de criação da conta, último login, etc.", - "view-history": "Ver histórico", - "view-history_description": "Permissão para visualizar o histórico de canais", "view-join-code": "Ver código de associação", "view-join-code_description": "Permissão para visualizar o código de associação do canal", "view-joined-room": "Exibir canal ligado", diff --git a/packages/i18n/src/locales/ro.i18n.json b/packages/i18n/src/locales/ro.i18n.json index 70c05dcf1d64b..d94a55107b111 100644 --- a/packages/i18n/src/locales/ro.i18n.json +++ b/packages/i18n/src/locales/ro.i18n.json @@ -2624,8 +2624,6 @@ "view-d-room_description": "Permisiune pentru a vedea mesaje directe", "view-full-other-user-info": "Vizualizați alte informații despre utilizatori", "view-full-other-user-info_description": "Permisiunea de a vizualiza profilul complet al altor utilizatori, inclusiv data creării contului, ultima autentificare etc.", - "view-history": "Vezi istoricul", - "view-history_description": "Permisiune pentru a vedea istoricul canalului", "view-join-code": "Afișați codul de conectare", "view-join-code_description": "Permisiune pentru a vedea codul de intrare a canalului", "view-joined-room": "Afișați camera asociată", diff --git a/packages/i18n/src/locales/ru.i18n.json b/packages/i18n/src/locales/ru.i18n.json index dd7700a1c7d36..c7f66b4375c9c 100644 --- a/packages/i18n/src/locales/ru.i18n.json +++ b/packages/i18n/src/locales/ru.i18n.json @@ -4747,8 +4747,6 @@ "View_full_conversation": "Просмотр полный текст разговора", "view-full-other-user-info": "Просмотр полной информации о других пользователях", "view-full-other-user-info_description": "Разрешение на просмотр полных профилей других пользователей, включая дату создания аккаунта, последнего входа и т. д.", - "view-history": "Просматривать историю", - "view-history_description": "Разрешение на просмотр истории канала", "view-join-code": "Просмотр кода присоединения", "view-join-code_description": "Разрешение на просмотр кода присоединения к каналу", "view-joined-room": "Просматривать чаты, к которым присоединился", diff --git a/packages/i18n/src/locales/se.i18n.json b/packages/i18n/src/locales/se.i18n.json index 99dc1b976155e..3838e9d551929 100644 --- a/packages/i18n/src/locales/se.i18n.json +++ b/packages/i18n/src/locales/se.i18n.json @@ -5885,8 +5885,6 @@ "View_full_conversation": "View full conversation", "view-full-other-user-info": "View Full Other User Info", "view-full-other-user-info_description": "Permission to view full profile of other users including account creation date, last login, etc.", - "view-history": "View History", - "view-history_description": "Permission to view the channel history", "onboarding.component.form.action.registerNow": "Register now", "view-join-code": "View Join Code", "view-join-code_description": "Permission to view the channel join code", diff --git a/packages/i18n/src/locales/sk-SK.i18n.json b/packages/i18n/src/locales/sk-SK.i18n.json index 8bd9d313857dd..bb879df65f167 100644 --- a/packages/i18n/src/locales/sk-SK.i18n.json +++ b/packages/i18n/src/locales/sk-SK.i18n.json @@ -2635,8 +2635,6 @@ "view-d-room_description": "Povolenie na zobrazenie priamych správ", "view-full-other-user-info": "Zobraziť úplné ďalšie informácie o používateľovi", "view-full-other-user-info_description": "Povolenie na zobrazenie úplného profilu ostatných používateľov vrátane dátumu vytvorenia účtu, posledného prihlásenia atď.", - "view-history": "Zobraziť históriu", - "view-history_description": "Povolenie na zobrazenie histórie kanálov", "view-join-code": "Zobraziť kód pripojenia", "view-join-code_description": "Povolenie na zobrazenie kódu pripojenia kanála", "view-joined-room": "Zobraziť spojenú miestnosť", diff --git a/packages/i18n/src/locales/sl-SI.i18n.json b/packages/i18n/src/locales/sl-SI.i18n.json index 2a9f043970a27..557fb1f0a4987 100644 --- a/packages/i18n/src/locales/sl-SI.i18n.json +++ b/packages/i18n/src/locales/sl-SI.i18n.json @@ -2615,8 +2615,6 @@ "view-d-room_description": "Dovoljenje za ogled neposrednih sporočil", "view-full-other-user-info": "Ogled polnih drugih informacij o uporabnikih", "view-full-other-user-info_description": "Dovoljenje za ogled celotnega profila drugih uporabnikov, vključno z datumom ustvarjanja računa, zadnjo prijavo itd.", - "view-history": "Ogled zgodovine", - "view-history_description": "Dovoljenje za ogled zgodovine kanalov", "view-join-code": "Ogled kode za pridružitev", "view-join-code_description": "Dovoljenje za ogled kode za pridružitev kanala", "view-joined-room": "Ogled pridružene sobe", diff --git a/packages/i18n/src/locales/sq.i18n.json b/packages/i18n/src/locales/sq.i18n.json index 9665ce7a84f70..40936b38a3d10 100644 --- a/packages/i18n/src/locales/sq.i18n.json +++ b/packages/i18n/src/locales/sq.i18n.json @@ -2625,8 +2625,6 @@ "view-d-room_description": "Leja për të parë mesazhe të drejtpërdrejta", "view-full-other-user-info": "Shiko Informacionin e plotë të Përdoruesit të Plotë", "view-full-other-user-info_description": "Leja për të parë profilin e plotë të përdoruesve të tjerë, përfshirë datën e krijimit të llogarisë, hyrjen e fundit, etj.", - "view-history": "Shiko historikun", - "view-history_description": "Leje për të parë historinë e kanalit", "view-join-code": "Shiko bashkësinë e kodit", "view-join-code_description": "Leja për të parë kodin e bashkëngjitjes së kanalit", "view-joined-room": "Shiko Dhomën e Bashkangjitur", diff --git a/packages/i18n/src/locales/sr.i18n.json b/packages/i18n/src/locales/sr.i18n.json index e4ebf289babf8..4e1ce1b6f34bd 100644 --- a/packages/i18n/src/locales/sr.i18n.json +++ b/packages/i18n/src/locales/sr.i18n.json @@ -2418,8 +2418,6 @@ "view-d-room_description": "Дозвола за преглед директних порука", "view-full-other-user-info": "Погледај остале информације о корисницима", "view-full-other-user-info_description": "Дозвола за преглед целог профила других корисника укључујући датум креирања налога, последње пријаве итд.", - "view-history": "Преглед историје", - "view-history_description": "Дозвола за преглед историје канала", "view-join-code": "Погледајте придружени код", "view-join-code_description": "Дозвола за преглед канала за придруживање", "view-joined-room": "Погледајте придружену собу", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index fe5d100c127b3..42caa2802c108 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -5255,8 +5255,6 @@ "View_full_conversation": "Visa hela konversationen", "view-full-other-user-info": "Visa fullständig annan användarinformation", "view-full-other-user-info_description": "Tillstånd att visa fullständig profil för andra användare, inklusive datum för registrering av konto, senaste inloggning etc.", - "view-history": "Se historik", - "view-history_description": "Tillstånd att visa kanalhistoriken", "onboarding.component.form.action.registerNow": "Registrera dig nu", "view-join-code": "Visa koden för deltagande", "view-join-code_description": "Tillstånd att se koden för kanalanslutning", diff --git a/packages/i18n/src/locales/ta-IN.i18n.json b/packages/i18n/src/locales/ta-IN.i18n.json index a653cfa811769..cab6bfaeb8edb 100644 --- a/packages/i18n/src/locales/ta-IN.i18n.json +++ b/packages/i18n/src/locales/ta-IN.i18n.json @@ -2626,8 +2626,6 @@ "view-d-room_description": "நேரடி செய்திகளைக் காண அனுமதி", "view-full-other-user-info": "முழு பிற பயனர் தகவலைக் காண்க", "view-full-other-user-info_description": "கணக்கு உருவாக்கம் தேதி, கடைசி உள்நுழைவு, முதலியன உட்பட பிற பயனர்களின் முழு சுயவிவரத்தைக் காண அனுமதி.", - "view-history": "வரலாறு காண்க", - "view-history_description": "சேனல் வரலாற்றைக் காண அனுமதி", "view-join-code": "கோட் சேர", "view-join-code_description": "சேனல் சேர்ப்பதற்கான குறியீட்டைக் காண அனுமதி", "view-joined-room": "இணைக்கப்பட்ட அறை காண்க", diff --git a/packages/i18n/src/locales/th-TH.i18n.json b/packages/i18n/src/locales/th-TH.i18n.json index 24692225c49d6..321ed8676512e 100644 --- a/packages/i18n/src/locales/th-TH.i18n.json +++ b/packages/i18n/src/locales/th-TH.i18n.json @@ -2613,8 +2613,6 @@ "view-d-room_description": "อนุญาตให้ดูข้อความโดยตรง", "view-full-other-user-info": "ดูข้อมูลผู้ใช้อื่น ๆ", "view-full-other-user-info_description": "อนุญาตให้ดูโปรไฟล์แบบเต็มของผู้ใช้รายอื่นรวมถึงวันที่สร้างบัญชีการเข้าสู่ระบบครั้งสุดท้าย ฯลฯ", - "view-history": "ดูประวัติ", - "view-history_description": "อนุญาตให้ดูประวัติช่อง", "view-join-code": "ดูรหัสการเข้าร่วม", "view-join-code_description": "อนุญาตให้ดูรหัสเข้าร่วมช่อง", "view-joined-room": "ดูห้องที่เข้าร่วม", diff --git a/packages/i18n/src/locales/tr.i18n.json b/packages/i18n/src/locales/tr.i18n.json index 955b07dd744f5..d758aea6a4193 100644 --- a/packages/i18n/src/locales/tr.i18n.json +++ b/packages/i18n/src/locales/tr.i18n.json @@ -3116,8 +3116,6 @@ "view-d-room_description": "Doğrudan iletileri görüntüleme izni", "view-full-other-user-info": "Tam Diğer Kullanıcı Bilgisi", "view-full-other-user-info_description": "Hesap oluşturma tarihi, son giriş, vb. De dahil olmak üzere diğer kullanıcıların tam profilini görüntüleme izni.", - "view-history": "Geçmişi Görüntüle", - "view-history_description": "Kanal geçmişini görüntüleme izni", "view-join-code": "Katılma Kodunu Görüntüle", "view-join-code_description": "Kanal katılma kodunu görüntüleme izni", "view-joined-room": "Katılınmış Oda Görüntüle", diff --git a/packages/i18n/src/locales/uk.i18n.json b/packages/i18n/src/locales/uk.i18n.json index 17540025f3585..691ef48eb2992 100644 --- a/packages/i18n/src/locales/uk.i18n.json +++ b/packages/i18n/src/locales/uk.i18n.json @@ -3202,8 +3202,6 @@ "view-d-room_description": "Дозвіл на перегляд прямих повідомлень", "view-full-other-user-info": "Переглянути іншу інформацію про користувача", "view-full-other-user-info_description": "Дозвіл на перегляд повного профілю інших користувачів, включаючи дату створення облікового запису, останню реєстраційну інформацію тощо.", - "view-history": "Переглянути історію", - "view-history_description": "Дозвіл на перегляд історії каналу", "view-join-code": "Переглянути код приєднання", "view-join-code_description": "Дозвіл переглядати код приєднання каналу", "view-joined-room": "Переглянути зареєстрований номер", diff --git a/packages/i18n/src/locales/vi-VN.i18n.json b/packages/i18n/src/locales/vi-VN.i18n.json index 4e6240e950ed9..75cc46bad7eb6 100644 --- a/packages/i18n/src/locales/vi-VN.i18n.json +++ b/packages/i18n/src/locales/vi-VN.i18n.json @@ -2723,8 +2723,6 @@ "view-d-room_description": "Cho phép xem tin nhắn trực tiếp", "view-full-other-user-info": "Xem thông tin người dùng đầy đủ khác", "view-full-other-user-info_description": "Cho phép xem hồ sơ đầy đủ của người dùng khác bao gồm ngày tạo tài khoản, đăng nhập lần cuối, v.v ...", - "view-history": "Xem lịch sử", - "view-history_description": "Cho phép xem lịch sử kênh", "view-join-code": "View Join Code", "view-join-code_description": "Cho phép xem mã kết nối kênh", "view-joined-room": "Xem phòng gia nhập", diff --git a/packages/i18n/src/locales/zh-HK.i18n.json b/packages/i18n/src/locales/zh-HK.i18n.json index 8b872f759de6d..e0c26531d67b5 100644 --- a/packages/i18n/src/locales/zh-HK.i18n.json +++ b/packages/i18n/src/locales/zh-HK.i18n.json @@ -2647,8 +2647,6 @@ "view-d-room_description": "查看直接信息的权限", "view-full-other-user-info": "查看完整的其他用户信息", "view-full-other-user-info_description": "允许查看其他用户的完整个人资料,包括帐户创建日期,上次登录等。", - "view-history": "查看历史", - "view-history_description": "查看频道历史记录的权限", "view-join-code": "查看加入代码", "view-join-code_description": "查看频道连接代码的权限", "view-joined-room": "查看加入的房间", diff --git a/packages/i18n/src/locales/zh-TW.i18n.json b/packages/i18n/src/locales/zh-TW.i18n.json index 0a4e68014d38e..945721f6e82d9 100644 --- a/packages/i18n/src/locales/zh-TW.i18n.json +++ b/packages/i18n/src/locales/zh-TW.i18n.json @@ -4288,8 +4288,6 @@ "view-d-room_description": "檢視直接訊息的權限", "view-full-other-user-info": "檢視完整的其他使用者訊息", "view-full-other-user-info_description": "允許檢視其他使用者的完整個人資料,包括帳號建立日期,上次登入等。", - "view-history": "檢視歷史", - "view-history_description": "檢視頻道歷史記錄的權限", "view-join-code": "檢視加入代碼", "view-join-code_description": "檢視頻道連接代碼的權限", "view-joined-room": "檢視加入的 Room", diff --git a/packages/i18n/src/locales/zh.i18n.json b/packages/i18n/src/locales/zh.i18n.json index 83df0b124ccc7..d0ea0cbeacf77 100644 --- a/packages/i18n/src/locales/zh.i18n.json +++ b/packages/i18n/src/locales/zh.i18n.json @@ -3942,8 +3942,6 @@ "view-d-room_description": "查看私聊信息的权限", "view-full-other-user-info": "查看完整的其他用户信息", "view-full-other-user-info_description": "查看其他用户的完整个人资料的权限,包括帐户创建日期,上次登录等。", - "view-history": "查看历史", - "view-history_description": "查看频道历史记录的权限", "view-join-code": "查看加入代码", "view-join-code_description": "查看频道加入码的权限", "view-joined-room": "查看加入的Room", From 7cc821077e72d6c38f475ec59013946464e0540d Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 15 Aug 2024 16:51:25 -0300 Subject: [PATCH 24/85] chore!: Remove unused `omnichannelExternalFrameGenerateKey` (#32921) --- .../client/externalFrame/generateNewKey.ts | 12 ------------ .../app/livechat/client/externalFrame/index.ts | 1 - apps/meteor/app/livechat/client/index.ts | 1 - .../server/externalFrame/generateNewKey.ts | 18 ------------------ .../app/livechat/server/externalFrame/index.ts | 1 - apps/meteor/app/livechat/server/index.ts | 1 - apps/meteor/server/settings/omnichannel.ts | 9 --------- 7 files changed, 43 deletions(-) delete mode 100644 apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts delete mode 100644 apps/meteor/app/livechat/client/externalFrame/index.ts delete mode 100644 apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts delete mode 100644 apps/meteor/app/livechat/server/externalFrame/index.ts diff --git a/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts b/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts deleted file mode 100644 index 54c6ba4d75f16..0000000000000 --- a/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { sdk } from '../../../utils/client/lib/SDKClient'; -import { generateKey } from './crypto'; - -Meteor.methods({ - async omnichannelExternalFrameGenerateKey() { - const key = await generateKey(); - await sdk.call('saveSetting', 'Omnichannel_External_Frame_Encryption_JWK', key); - }, -}); diff --git a/apps/meteor/app/livechat/client/externalFrame/index.ts b/apps/meteor/app/livechat/client/externalFrame/index.ts deleted file mode 100644 index 5f05d53f1ccce..0000000000000 --- a/apps/meteor/app/livechat/client/externalFrame/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './generateNewKey'; diff --git a/apps/meteor/app/livechat/client/index.ts b/apps/meteor/app/livechat/client/index.ts index bc00a95bcdcdb..c6884923db308 100644 --- a/apps/meteor/app/livechat/client/index.ts +++ b/apps/meteor/app/livechat/client/index.ts @@ -2,4 +2,3 @@ import '../lib/messageTypes'; import './voip'; import './ui'; import './stylesheets/livechat.css'; -import './externalFrame'; diff --git a/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts b/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts deleted file mode 100644 index a8f145f47d394..0000000000000 --- a/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - omnichannelExternalFrameGenerateKey(): unknown; - } -} - -Meteor.methods({ - // eslint-disable-next-line @typescript-eslint/no-empty-function - omnichannelExternalFrameGenerateKey() { - return { - message: 'Generating_key', - }; - }, // only to prevent error when calling the client method -}); diff --git a/apps/meteor/app/livechat/server/externalFrame/index.ts b/apps/meteor/app/livechat/server/externalFrame/index.ts deleted file mode 100644 index 5f05d53f1ccce..0000000000000 --- a/apps/meteor/app/livechat/server/externalFrame/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './generateNewKey'; diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 9a1f40238df54..357d444ac4742 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -74,5 +74,4 @@ import './lib/stream/agentStatus'; import './sendMessageBySMS'; import './api'; import './api/rest'; -import './externalFrame'; import './methods/saveBusinessHour'; diff --git a/apps/meteor/server/settings/omnichannel.ts b/apps/meteor/server/settings/omnichannel.ts index c86cd6674d4ec..37701cea8c417 100644 --- a/apps/meteor/server/settings/omnichannel.ts +++ b/apps/meteor/server/settings/omnichannel.ts @@ -852,14 +852,5 @@ await settingsRegistry.addGroup('SMS', async function () { value: true, }, }); - - await this.add('Omnichannel_External_Frame_GenerateKey', 'omnichannelExternalFrameGenerateKey', { - type: 'action', - actionText: 'Generate_new_key', - enableQuery: { - _id: 'Omnichannel_External_Frame_Enabled', - value: true, - }, - }); }); }); From 57e5fd26cee67efff537f844a809c6d14d68664b Mon Sep 17 00:00:00 2001 From: Allan RIbeiro <35040806+AllanPazRibeiro@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:32:19 -0300 Subject: [PATCH 25/85] chore!: remove deprecated meteor eraseRoom method (#32648) Co-authored-by: Guilherme Gazzo --- .changeset/chilled-boats-sip.md | 5 +++ apps/meteor/app/api/server/v1/channels.ts | 3 +- apps/meteor/app/api/server/v1/groups.ts | 3 +- apps/meteor/app/api/server/v1/im.ts | 4 ++- apps/meteor/app/api/server/v1/rooms.ts | 2 +- apps/meteor/app/api/server/v1/teams.ts | 8 ++--- .../server/{methods => lib}/eraseRoom.ts | 32 +------------------ apps/meteor/server/methods/index.ts | 1 - 8 files changed, 18 insertions(+), 40 deletions(-) create mode 100644 .changeset/chilled-boats-sip.md rename apps/meteor/server/{methods => lib}/eraseRoom.ts (68%) diff --git a/.changeset/chilled-boats-sip.md b/.changeset/chilled-boats-sip.md new file mode 100644 index 0000000000000..8488cb69a442f --- /dev/null +++ b/.changeset/chilled-boats-sip.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +This adjustment removes the deprecated `eraseRoom` method. Moving forward, use the `room.delete` endpoint to delete rooms. diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index a713bfd29ac60..b69487bdd22bb 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -23,6 +23,7 @@ import { import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; +import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { removeUserFromRoomMethod } from '../../../../server/methods/removeUserFromRoom'; @@ -463,7 +464,7 @@ API.v1.addRoute( checkedArchived: false, }); - await Meteor.callAsync('eraseRoom', room._id); + await eraseRoom(room._id, this.userId); return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index df03e38dc1470..f2339b0a79c3b 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -5,6 +5,7 @@ import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { Filter } from 'mongodb'; +import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { removeUserFromRoomMethod } from '../../../../server/methods/removeUserFromRoom'; @@ -364,7 +365,7 @@ API.v1.addRoute( checkedArchived: false, }); - await Meteor.callAsync('eraseRoom', findResult.rid); + await eraseRoom(findResult.rid, this.userId); return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 1ee1ac4ca4000..713d0206e4c8d 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -14,6 +14,7 @@ import { import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { createDirectMessage } from '../../../../server/methods/createDirectMessage'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; @@ -26,6 +27,7 @@ import { API } from '../api'; import { addUserToFileObj } from '../helpers/addUserToFileObj'; import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage'; import { getPaginationItems } from '../helpers/getPaginationItems'; + // TODO: Refact or remove type findDirectMessageRoomProps = @@ -107,7 +109,7 @@ API.v1.addRoute( throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - await Meteor.callAsync('eraseRoom', room._id); + await eraseRoom(room._id, this.userId); return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 117ae3851c436..eaf350c0f188e 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -15,7 +15,7 @@ import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; import { omit } from '../../../../lib/utils/omit'; import * as dataExport from '../../../../server/lib/dataExport'; -import { eraseRoom } from '../../../../server/methods/eraseRoom'; +import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { muteUserInRoom } from '../../../../server/methods/muteUserInRoom'; import { unmuteUserInRoom } from '../../../../server/methods/unmuteUserInRoom'; import { canAccessRoomAsync, canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index 0b534e599ac9e..63132d811b1c6 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -15,8 +15,8 @@ import { } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; +import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { canAccessRoomAsync } from '../../../authorization/server'; import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom'; @@ -136,7 +136,7 @@ API.v1.addRoute( if (rooms.length) { for await (const room of rooms) { - await Meteor.callAsync('eraseRoom', room); + await eraseRoom(room, this.userId); } } @@ -652,7 +652,7 @@ API.v1.addRoute( // If we got a list of rooms to delete along with the team, remove them first if (rooms.length) { for await (const room of rooms) { - await Meteor.callAsync('eraseRoom', room); + await eraseRoom(room, this.userId); } } @@ -660,7 +660,7 @@ API.v1.addRoute( await Team.unsetTeamIdOfRooms(this.userId, team._id); // Remove the team's main room - await Meteor.callAsync('eraseRoom', team.roomId); + await eraseRoom(team.roomId, this.userId); // Delete all team memberships await Team.removeAllMembersFromTeam(team._id); diff --git a/apps/meteor/server/methods/eraseRoom.ts b/apps/meteor/server/lib/eraseRoom.ts similarity index 68% rename from apps/meteor/server/methods/eraseRoom.ts rename to apps/meteor/server/lib/eraseRoom.ts index e11c0fe6dcfdb..d67de3325eea9 100644 --- a/apps/meteor/server/methods/eraseRoom.ts +++ b/apps/meteor/server/lib/eraseRoom.ts @@ -1,14 +1,11 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message, Team } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { deleteRoom } from '../../app/lib/server/functions/deleteRoom'; -import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; -import { roomCoordinator } from '../lib/rooms/roomCoordinator'; +import { roomCoordinator } from './rooms/roomCoordinator'; export async function eraseRoom(rid: string, uid: string): Promise { const room = await Rooms.findOneById(rid); @@ -57,30 +54,3 @@ export async function eraseRoom(rid: string, uid: string): Promise { void Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPostRoomDeleted, room); } } - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - eraseRoom(rid: string): Promise; - } -} - -Meteor.methods({ - async eraseRoom(rid: string) { - methodDeprecationLogger.method('eraseRoom', '7.0.0'); - - check(rid, String); - - const uid = Meteor.userId(); - - if (!uid) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'eraseRoom', - }); - } - - await eraseRoom(rid, uid); - - return true; - }, -}); diff --git a/apps/meteor/server/methods/index.ts b/apps/meteor/server/methods/index.ts index 27c3459646375..a3b4b747f7159 100644 --- a/apps/meteor/server/methods/index.ts +++ b/apps/meteor/server/methods/index.ts @@ -12,7 +12,6 @@ import './channelsList'; import './createDirectMessage'; import './deleteFileMessage'; import './deleteUser'; -import './eraseRoom'; import './getAvatarSuggestion'; import './getPasswordPolicy'; import './getRoomById'; From 3ec67102d92f4b1e62730f89ceb3c96817f59dd6 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 17 Aug 2024 06:13:07 +0530 Subject: [PATCH 26/85] chore!: removed setAsset, unsetAsset, refreshClients method (#32564) --- apps/meteor/app/api/server/v1/assets.ts | 15 ++-- apps/meteor/app/assets/server/assets.ts | 99 ++++++++++--------------- apps/meteor/app/assets/server/index.ts | 2 +- 3 files changed, 46 insertions(+), 70 deletions(-) diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index f2f8ca94a989b..395bbd791afa8 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -1,7 +1,6 @@ import { isAssetsUnsetAssetProps } from '@rocket.chat/rest-typings'; -import { Meteor } from 'meteor/meteor'; -import { RocketChatAssets } from '../../../assets/server'; +import { RocketChatAssets, setAsset, unsetAsset, refreshClients } from '../../../assets/server'; import { settings } from '../../../settings/server'; import { API } from '../api'; import { getUploadFormData } from '../lib/getUploadFormData'; @@ -27,12 +26,12 @@ API.v1.addRoute( const isValidAsset = assetsKeys.includes(assetName); if (!isValidAsset) { - throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); + throw new Error('Invalid asset'); } - await Meteor.callAsync('setAsset', fileBuffer, mimetype, assetName); + await setAsset(this.userId, fileBuffer, mimetype, assetName); if (refreshAllClients) { - await Meteor.callAsync('refreshClients'); + await refreshClients(this.userId); } return API.v1.success(); @@ -51,11 +50,11 @@ API.v1.addRoute( const { assetName, refreshAllClients } = this.bodyParams; const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName); if (!isValidAsset) { - throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); + throw Error('Invalid asset'); } - await Meteor.callAsync('unsetAsset', assetName); + await unsetAsset(this.userId, assetName); if (refreshAllClients) { - await Meteor.callAsync('refreshClients'); + await refreshClients(this.userId); } return API.v1.success(); }, diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index 5d627a4b1d295..ef307f00b30cf 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -2,7 +2,6 @@ import crypto from 'crypto'; import type { ServerResponse, IncomingMessage } from 'http'; import type { IRocketChatAssets, IRocketChatAsset, ISetting } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; import type { NextHandleFunction } from 'connect'; import sizeOf from 'image-size'; @@ -12,7 +11,6 @@ import sharp from 'sharp'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { RocketChatFile } from '../../file/server'; -import { methodDeprecationLogger } from '../../lib/server/lib/deprecationWarningLogger'; import { notifyOnSettingChangedById } from '../../lib/server/lib/notifyListener'; import { settings, settingsRegistry } from '../../settings/server'; import { getExtension } from '../../utils/lib/mimeTypes'; @@ -223,6 +221,15 @@ class RocketChatAssetsClass { } public async setAsset(binaryContent: string, contentType: string, asset: string): Promise { + const file = Buffer.from(binaryContent, 'binary'); + await this.setAssetWithBuffer(file, contentType, asset); + } + + public async setAssetWithBuffer(binaryContent: Buffer, contentType: string, asset: string): Promise { + await this._setAsset(binaryContent, contentType, asset); + } + + private async _setAsset(file: Buffer, contentType: string, asset: string): Promise { const assetInstance = getAssetByKey(asset); if (!assetInstance) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { @@ -237,7 +244,6 @@ class RocketChatAssetsClass { }); } - const file = Buffer.from(binaryContent, 'binary'); if (assetInstance.constraints.width || assetInstance.constraints.height) { const dimensions = sizeOf(file); if (assetInstance.constraints.width && assetInstance.constraints.width !== dimensions.width) { @@ -406,73 +412,44 @@ Meteor.startup(() => { }, 200); }); -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - refreshClients(): boolean; - unsetAsset(asset: string): void; - setAsset(binaryContent: Buffer, contentType: string, asset: string): void; +export const refreshClients = async (userId: string) => { + if (!userId) { + throw new Error('Invalid user'); } -} -Meteor.methods({ - async refreshClients() { - const uid = Meteor.userId(); - methodDeprecationLogger.method('refreshClients', '7.0.0'); - - if (!uid) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'refreshClients', - }); - } - - const _hasPermission = await hasPermissionAsync(uid, 'manage-assets'); - if (!_hasPermission) { - throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { - method: 'refreshClients', - action: 'Managing_assets', - }); - } + const _hasPermission = await hasPermissionAsync(userId, 'manage-assets'); + if (!_hasPermission) { + throw new Error('Managing assets not allowed'); + } - return RocketChatAssets.refreshClients(); - }, + return RocketChatAssets.refreshClients(); +}; - async unsetAsset(asset) { - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'unsetAsset', - }); - } +export const unsetAsset = async (userId: string, asset: string) => { + if (!userId) { + throw new Error('Invalid user'); + } - const _hasPermission = await hasPermissionAsync(Meteor.userId() as string, 'manage-assets'); - if (!_hasPermission) { - throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { - method: 'unsetAsset', - action: 'Managing_assets', - }); - } + const _hasPermission = await hasPermissionAsync(userId, 'manage-assets'); + if (!_hasPermission) { + throw new Error('Managing assets not allowed'); + } - return RocketChatAssets.unsetAsset(asset); - }, + return RocketChatAssets.unsetAsset(asset); +}; - async setAsset(binaryContent, contentType, asset) { - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'setAsset', - }); - } +export const setAsset = async (userId: string, binaryContent: Buffer, contentType: string, asset: string) => { + if (!userId) { + throw new Error('Invalid user'); + } - const _hasPermission = await hasPermissionAsync(Meteor.userId() as string, 'manage-assets'); - if (!_hasPermission) { - throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { - method: 'setAsset', - action: 'Managing_assets', - }); - } + const _hasPermission = await hasPermissionAsync(userId, 'manage-assets'); + if (!_hasPermission) { + throw new Error('Managing assets not allowed'); + } - await RocketChatAssets.setAsset(binaryContent, contentType, asset); - }, -}); + await RocketChatAssets.setAssetWithBuffer(binaryContent, contentType, asset); +}; const listener = (req: IncomingMessage, res: ServerResponse, next: NextHandleFunction) => { if (!req.url) { diff --git a/apps/meteor/app/assets/server/index.ts b/apps/meteor/app/assets/server/index.ts index 8e5b30ff6b6f5..8aa8c123f9283 100644 --- a/apps/meteor/app/assets/server/index.ts +++ b/apps/meteor/app/assets/server/index.ts @@ -1 +1 @@ -export { RocketChatAssets } from './assets'; +export { RocketChatAssets, setAsset, unsetAsset, refreshClients } from './assets'; From ee2e38fe5ddd8a5604a467c328ab506d19e46db0 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:44:30 -0300 Subject: [PATCH 27/85] chore!: Remove `meteor/check` from `cloud` endpoints (#32533) --- apps/meteor/app/api/server/v1/cloud.ts | 24 +-- apps/meteor/tests/end-to-end/api/cloud.ts | 181 ++++++++++++++++++++++ packages/rest-typings/src/index.ts | 1 + packages/rest-typings/src/v1/cloud.ts | 6 +- 4 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 apps/meteor/tests/end-to-end/api/cloud.ts diff --git a/apps/meteor/app/api/server/v1/cloud.ts b/apps/meteor/app/api/server/v1/cloud.ts index c0694deef4fae..17000832897fe 100644 --- a/apps/meteor/app/api/server/v1/cloud.ts +++ b/apps/meteor/app/api/server/v1/cloud.ts @@ -1,4 +1,4 @@ -import { check } from 'meteor/check'; +import { isCloudConfirmationPollProps, isCloudCreateRegistrationIntentProps, isCloudManualRegisterProps } from '@rocket.chat/rest-typings'; import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -19,13 +19,9 @@ import { API } from '../api'; API.v1.addRoute( 'cloud.manualRegister', - { authRequired: true, permissionsRequired: ['register-on-cloud'] }, + { authRequired: true, permissionsRequired: ['register-on-cloud'], validateParams: isCloudManualRegisterProps }, { async post() { - check(this.bodyParams, { - cloudBlob: String, - }); - const registrationInfo = await retrieveRegistrationStatus(); if (registrationInfo.workspaceRegistered) { @@ -43,14 +39,9 @@ API.v1.addRoute( API.v1.addRoute( 'cloud.createRegistrationIntent', - { authRequired: true, permissionsRequired: ['manage-cloud'] }, + { authRequired: true, permissionsRequired: ['manage-cloud'], validateParams: isCloudCreateRegistrationIntentProps }, { async post() { - check(this.bodyParams, { - resend: Boolean, - email: String, - }); - const intentData = await startRegisterWorkspaceSetupWizard(this.bodyParams.resend, this.bodyParams.email); if (intentData) { @@ -74,17 +65,10 @@ API.v1.addRoute( API.v1.addRoute( 'cloud.confirmationPoll', - { authRequired: true, permissionsRequired: ['manage-cloud'] }, + { authRequired: true, permissionsRequired: ['manage-cloud'], validateParams: isCloudConfirmationPollProps }, { async get() { const { deviceCode } = this.queryParams; - check(this.queryParams, { - deviceCode: String, - }); - - if (!deviceCode) { - return API.v1.failure('Invalid query'); - } const pollData = await getConfirmationPoll(deviceCode); if (pollData) { diff --git a/apps/meteor/tests/end-to-end/api/cloud.ts b/apps/meteor/tests/end-to-end/api/cloud.ts new file mode 100644 index 0000000000000..dc6d08e990925 --- /dev/null +++ b/apps/meteor/tests/end-to-end/api/cloud.ts @@ -0,0 +1,181 @@ +import { expect } from 'chai'; +import { after, before, describe, it } from 'mocha'; + +import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { updatePermission } from '../../data/permissions.helper'; + +describe('[Cloud]', function () { + this.retries(0); + + before((done) => getCredentials(done)); + + describe('[/cloud.manualRegister]', () => { + before(async () => { + return updatePermission('register-on-cloud', ['admin']); + }); + + after(async () => { + return updatePermission('register-on-cloud', ['admin']); + }); + + it('should fail if user is not authenticated', async () => { + return request + .post(api('cloud.manualRegister')) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message', 'You must be logged in to do this.'); + }); + }); + + it('should fail when cloudBlob property is not provided', async () => { + return request + .post(api('cloud.manualRegister')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', "must have required property 'cloudBlob' [invalid-params]"); + }); + }); + + it('should fail when user does not have the register-on-cloud permission', async () => { + await updatePermission('register-on-cloud', []); + return request + .post(api('cloud.manualRegister')) + .set(credentials) + .send({ + cloudBlob: 'test-blob', + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); + + describe('[/cloud.createRegistrationIntent]', () => { + before(async () => { + return updatePermission('manage-cloud', ['admin']); + }); + + after(async () => { + return updatePermission('manage-cloud', ['admin']); + }); + + it('should fail if user is not authenticated', async () => { + return request + .post(api('cloud.createRegistrationIntent')) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message', 'You must be logged in to do this.'); + }); + }); + + it('should fail when resend property is not provided', async () => { + return request + .post(api('cloud.createRegistrationIntent')) + .set(credentials) + .send({ + email: 'test-mail@example.com', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', "must have required property 'resend' [invalid-params]"); + }); + }); + + it('should fail when email property is not provided', async () => { + return request + .post(api('cloud.createRegistrationIntent')) + .set(credentials) + .send({ + resend: true, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', "must have required property 'email' [invalid-params]"); + }); + }); + + it('should fail when user does not have the manage-cloud permission', async () => { + await updatePermission('manage-cloud', []); + return request + .post(api('cloud.createRegistrationIntent')) + .set(credentials) + .send({ + email: 'test-mail@example.com', + resend: true, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); + + describe('[/cloud.confirmationPoll]', () => { + before(async () => { + return updatePermission('manage-cloud', ['admin']); + }); + + after(async () => { + return updatePermission('manage-cloud', ['admin']); + }); + + it('should fail if user is not authenticated', async () => { + return request + .get(api('cloud.confirmationPoll')) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message', 'You must be logged in to do this.'); + }); + }); + + it('should fail when deviceCode property is not provided', async () => { + return request + .get(api('cloud.confirmationPoll')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', "must have required property 'deviceCode' [invalid-params]"); + }); + }); + + it('should fail when user does not have the manage-cloud permission', async () => { + await updatePermission('manage-cloud', []); + return request + .get(api('cloud.confirmationPoll')) + .set(credentials) + .query({ + deviceCode: 'test-code', + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); +}); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index eda85b03dbc86..cd02e14a71ce8 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -270,3 +270,4 @@ export * from './v1/rooms'; export * from './v1/groups'; export * from './v1/chat'; export * from './v1/auth'; +export * from './v1/cloud'; diff --git a/packages/rest-typings/src/v1/cloud.ts b/packages/rest-typings/src/v1/cloud.ts index 7602034b69120..8a4d040d06a4a 100644 --- a/packages/rest-typings/src/v1/cloud.ts +++ b/packages/rest-typings/src/v1/cloud.ts @@ -53,14 +53,10 @@ const CloudConfirmationPollSchema = { properties: { deviceCode: { type: 'string', - }, - resend: { - type: 'string', - nullable: true, + minLength: 1, }, }, required: ['deviceCode'], - optionalProperties: ['resend'], additionalProperties: false, }; From 00e4c542d9f71313709d9cc6b27816371b718199 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 17 Aug 2024 06:16:04 +0530 Subject: [PATCH 28/85] chore!: removed checkUsernameAvailability method (#32488) --- apps/meteor/app/lib/server/index.ts | 1 - .../methods/checkUsernameAvailability.ts | 34 ------------------- 2 files changed, 35 deletions(-) delete mode 100644 apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts diff --git a/apps/meteor/app/lib/server/index.ts b/apps/meteor/app/lib/server/index.ts index 49fad2002c753..ecfdb2ed81cd1 100644 --- a/apps/meteor/app/lib/server/index.ts +++ b/apps/meteor/app/lib/server/index.ts @@ -13,7 +13,6 @@ import './methods/addUserToRoom'; import './methods/archiveRoom'; import './methods/blockUser'; import './methods/checkRegistrationSecretURL'; -import './methods/checkUsernameAvailability'; import './methods/cleanRoomHistory'; import './methods/createChannel'; import './methods/createPrivateGroup'; diff --git a/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts b/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts deleted file mode 100644 index 4df2706bb4796..0000000000000 --- a/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { checkUsernameAvailabilityWithValidation } from '../functions/checkUsernameAvailability'; -import { RateLimiter } from '../lib'; -import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - checkUsernameAvailability(username: string): boolean; - } -} - -Meteor.methods({ - async checkUsernameAvailability(username) { - methodDeprecationLogger.method('checkUsernameAvailability', '7.0.0'); - - check(username, String); - const userId = Meteor.userId(); - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'checkUsernameAvailability' }); - } - - return checkUsernameAvailabilityWithValidation(userId, username); - }, -}); - -RateLimiter.limitMethod('checkUsernameAvailability', 1, 1000, { - userId() { - return true; - }, -}); From 37cdff88864ff93b33b8916c66a4baeecd4188af Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 17 Aug 2024 06:17:04 +0530 Subject: [PATCH 29/85] refactor!: removed roomNameExists method (#32484) Co-authored-by: Marcos Spessatto Defendi --- apps/meteor/app/api/server/v1/rooms.ts | 4 ++- apps/meteor/server/methods/index.ts | 1 - apps/meteor/server/methods/roomNameExists.ts | 34 -------------------- apps/meteor/tests/end-to-end/api/rooms.ts | 17 +++++++++- 4 files changed, 19 insertions(+), 37 deletions(-) delete mode 100644 apps/meteor/server/methods/roomNameExists.ts diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index eaf350c0f188e..4a42adf6f5bd5 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -89,7 +89,9 @@ API.v1.addRoute( async get() { const { roomName } = this.queryParams; - return API.v1.success({ exists: await Meteor.callAsync('roomNameExists', roomName) }); + const room = await Rooms.findOneByName(roomName, { projection: { _id: 1 } }); + + return API.v1.success({ exists: !!room }); }, }, ); diff --git a/apps/meteor/server/methods/index.ts b/apps/meteor/server/methods/index.ts index a3b4b747f7159..8182c10f5008a 100644 --- a/apps/meteor/server/methods/index.ts +++ b/apps/meteor/server/methods/index.ts @@ -41,7 +41,6 @@ import './removeUserFromRoom'; import './reportMessage'; import './requestDataDownload'; import './resetAvatar'; -import './roomNameExists'; import './saveUserPreferences'; import './saveUserProfile'; import './sendConfirmationEmail'; diff --git a/apps/meteor/server/methods/roomNameExists.ts b/apps/meteor/server/methods/roomNameExists.ts deleted file mode 100644 index f510391ddd8e3..0000000000000 --- a/apps/meteor/server/methods/roomNameExists.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Rooms } from '@rocket.chat/models'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - roomNameExists(roomName: string): boolean; - } -} - -Meteor.methods({ - async roomNameExists(roomName) { - check(roomName, String); - - methodDeprecationLogger.method('roomNameExists', '7.0.0'); - - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'roomExists', - }); - } - const room = await Rooms.findOneByName(roomName, { projection: { _id: 1 } }); - - if (room === undefined || room === null) { - return false; - } - - return true; - }, -}); diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index 5047af7956d8e..e5a62137d42cc 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -941,7 +941,22 @@ describe('[Rooms]', () => { .end(done); }); - it('should return an error when send an invalid room', (done) => { + it('should return false if this room name does not exist', (done) => { + void request + .get(api('rooms.nameExists')) + .set(credentials) + .query({ + roomName: 'foo', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', false); + }) + .end(done); + }); + + it('should return an error when the require parameter (roomName) is not provided', (done) => { void request .get(api('rooms.nameExists')) .set(credentials) From 01ce6b98e7add8255b854959cd9828c8616cc4fd Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 17 Aug 2024 06:19:00 +0530 Subject: [PATCH 30/85] chore!: removed reportMessage method (#32633) --- .../ReportMessageModal/ReportMessageModal.tsx | 6 +- apps/meteor/server/methods/index.ts | 1 - apps/meteor/server/methods/reportMessage.ts | 97 ------------------- 3 files changed, 3 insertions(+), 101 deletions(-) delete mode 100644 apps/meteor/server/methods/reportMessage.ts diff --git a/apps/meteor/client/views/room/modals/ReportMessageModal/ReportMessageModal.tsx b/apps/meteor/client/views/room/modals/ReportMessageModal/ReportMessageModal.tsx index 1bf96c910a5a9..4a4fc5c0c2dad 100644 --- a/apps/meteor/client/views/room/modals/ReportMessageModal/ReportMessageModal.tsx +++ b/apps/meteor/client/views/room/modals/ReportMessageModal/ReportMessageModal.tsx @@ -1,7 +1,7 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { TextAreaInput, FieldGroup, Field, FieldRow, FieldError, Box } from '@rocket.chat/fuselage'; -import { useToastMessageDispatch, useTranslation, useMethod } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; import { useForm } from 'react-hook-form'; @@ -31,13 +31,13 @@ const ReportMessageModal = ({ message, onClose }: ReportMessageModalProps): Reac handleSubmit, } = useForm(); const dispatchToastMessage = useToastMessageDispatch(); - const reportMessage = useMethod('reportMessage'); + const reportMessage = useEndpoint('POST', '/v1/chat.reportMessage'); const { _id } = message; const handleReportMessage = async ({ description }: ReportMessageModalsFields): Promise => { try { - await reportMessage(_id, description); + await reportMessage({ messageId: _id, description }); dispatchToastMessage({ type: 'success', message: t('Report_has_been_sent') }); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); diff --git a/apps/meteor/server/methods/index.ts b/apps/meteor/server/methods/index.ts index 8182c10f5008a..6ae87421c11af 100644 --- a/apps/meteor/server/methods/index.ts +++ b/apps/meteor/server/methods/index.ts @@ -38,7 +38,6 @@ import './removeRoomLeader'; import './removeRoomModerator'; import './removeRoomOwner'; import './removeUserFromRoom'; -import './reportMessage'; import './requestDataDownload'; import './resetAvatar'; import './saveUserPreferences'; diff --git a/apps/meteor/server/methods/reportMessage.ts b/apps/meteor/server/methods/reportMessage.ts deleted file mode 100644 index a2a1deca45faa..0000000000000 --- a/apps/meteor/server/methods/reportMessage.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Apps, AppEvents } from '@rocket.chat/apps'; -import type { IMessage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { ModerationReports, Rooms, Users, Messages } from '@rocket.chat/models'; -import { check } from 'meteor/check'; -import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; -import { Meteor } from 'meteor/meteor'; - -import { canAccessRoomAsync } from '../../app/authorization/server/functions/canAccessRoom'; -import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - reportMessage(messageId: IMessage['_id'], description: string): Promise; - } -} - -Meteor.methods({ - async reportMessage(messageId, description) { - methodDeprecationLogger.method('reportMessage', '7.0.0'); - - check(messageId, String); - check(description, String); - - const uid = Meteor.userId(); - - if (!uid) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'reportMessage', - }); - } - - const user = await Users.findOneById(uid); - - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'reportMessage', - }); - } - - if (description == null || description.trim() === '') { - throw new Meteor.Error('error-invalid-description', 'Invalid description', { - method: 'reportMessage', - }); - } - - const message = await Messages.findOneById(messageId); - if (!message) { - throw new Meteor.Error('error-invalid-message_id', 'Invalid message id', { - method: 'reportMessage', - }); - } - - const { rid } = message; - // If the user can't access the room where the message is, report that the message id is invalid - const room = await Rooms.findOneById(rid); - if (!room || !(await canAccessRoomAsync(room, { _id: user._id }))) { - throw new Meteor.Error('error-invalid-message_id', 'Invalid message id', { - method: 'reportMessage', - }); - } - - const reportedBy = { - _id: user._id, - username: user.username, - name: user.name, - createdAt: user.createdAt, - }; - - const roomInfo = { - _id: rid, - name: room.name, - t: room.t, - federated: room.federated, - fname: room.fname, - }; - - await ModerationReports.createWithMessageDescriptionAndUserId(message, description, roomInfo, reportedBy); - - await Apps.self?.triggerEvent(AppEvents.IPostMessageReported, message, await Meteor.userAsync(), description); - - return true; - }, -}); - -DDPRateLimiter.addRule( - { - type: 'method', - name: 'reportMessage', - userId() { - return true; - }, - }, - 5, - 60000, -); From ec5d9c109849e16b2f1b9a788c01e993774c6724 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 16 Aug 2024 21:59:29 -0300 Subject: [PATCH 31/85] fix: Missing retention's `ignoreThreads` param for old channels (#33028) --- .changeset/gold-knives-sparkle.md | 5 +++++ .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v306.ts | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 .changeset/gold-knives-sparkle.md create mode 100644 apps/meteor/server/startup/migrations/v306.ts diff --git a/.changeset/gold-knives-sparkle.md b/.changeset/gold-knives-sparkle.md new file mode 100644 index 0000000000000..144573944d499 --- /dev/null +++ b/.changeset/gold-knives-sparkle.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where ignore threads parameter were not being affected by retention policy overriding in old channels diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index e7a18e836722b..e7efdc89ebde1 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -38,5 +38,6 @@ import './v301'; import './v303'; import './v304'; import './v305'; +import './v306'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v306.ts b/apps/meteor/server/startup/migrations/v306.ts new file mode 100644 index 0000000000000..fcd6ae9e35586 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v306.ts @@ -0,0 +1,22 @@ +import { Rooms } from '@rocket.chat/models'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 306, + name: 'Adds missing ignoreThreads parameter for old rooms with retention policy overridden', + async up() { + await Rooms.updateMany( + { + 'retention.enabled': true, + 'retention.overrideGlobal': true, + 'retention.ignoreThreads': { $exists: false }, + }, + { + $set: { + 'retention.ignoreThreads': false, + }, + }, + ); + }, +}); From 781ab50b1d283ec73389c1ddcca04f8a283d06cf Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:20:40 -0300 Subject: [PATCH 32/85] chore!: Remove `meteor/check` from `chat` endpoints (#32532) Co-authored-by: Guilherme Gazzo --- .changeset/tiny-rice-train.md | 6 + apps/meteor/app/api/server/v1/chat.ts | 38 +--- apps/meteor/client/lib/lists/ThreadsList.ts | 2 +- .../room/contextualBar/Threads/ThreadList.tsx | 1 - apps/meteor/tests/end-to-end/api/chat.ts | 189 ++++++++++++++++++ packages/rest-typings/src/v1/chat.ts | 16 +- 6 files changed, 221 insertions(+), 31 deletions(-) create mode 100644 .changeset/tiny-rice-train.md diff --git a/.changeset/tiny-rice-train.md b/.changeset/tiny-rice-train.md new file mode 100644 index 0000000000000..a523c3e698c0d --- /dev/null +++ b/.changeset/tiny-rice-train.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": major +"@rocket.chat/rest-typings": major +--- + +Removed `meteor/check` from `chat` endpoints diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index d04d1a2418b54..98c28d24581ea 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -1,9 +1,14 @@ import { Message } from '@rocket.chat/core-services'; import type { IMessage } from '@rocket.chat/core-typings'; import { Messages, Users, Rooms, Subscriptions } from '@rocket.chat/models'; -import { isChatReportMessageProps, isChatGetURLPreviewProps } from '@rocket.chat/rest-typings'; +import { + isChatReportMessageProps, + isChatGetURLPreviewProps, + isChatUpdateProps, + isChatGetThreadsListProps, + isChatDeleteProps, +} from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { reportMessage } from '../../../../server/lib/moderation/reportMessage'; @@ -26,18 +31,9 @@ import { findDiscussionsFromRoom, findMentionedMessages, findStarredMessages } f API.v1.addRoute( 'chat.delete', - { authRequired: true }, + { authRequired: true, validateParams: isChatDeleteProps }, { async post() { - check( - this.bodyParams, - Match.ObjectIncluding({ - msgId: String, - roomId: String, - asUser: Match.Maybe(Boolean), - }), - ); - const msg = await Messages.findOneById(this.bodyParams.msgId, { projection: { u: 1, rid: 1 } }); if (!msg) { @@ -308,20 +304,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.update', - { authRequired: true }, + { authRequired: true, validateParams: isChatUpdateProps }, { async post() { - check( - this.bodyParams, - Match.ObjectIncluding({ - roomId: String, - msgId: String, - text: String, // Using text to be consistant with chat.postMessage - customFields: Match.Maybe(Object), - previewUrls: Match.Maybe([String]), - }), - ); - const msg = await Messages.findOneById(this.bodyParams.msgId); // Ensure the message exists @@ -504,13 +489,10 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getThreadsList', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetThreadsListProps }, { async get() { const { rid, type, text } = this.queryParams; - check(rid, String); - check(type, Match.Maybe(String)); - check(text, Match.Maybe(String)); const { offset, count } = await getPaginationItems(this.queryParams); const { sort, fields, query } = await this.parseJsonQuery(); diff --git a/apps/meteor/client/lib/lists/ThreadsList.ts b/apps/meteor/client/lib/lists/ThreadsList.ts index ac592a3668654..5e77f0254a08c 100644 --- a/apps/meteor/client/lib/lists/ThreadsList.ts +++ b/apps/meteor/client/lib/lists/ThreadsList.ts @@ -18,7 +18,7 @@ export type ThreadsListOptions = { uid: IUser['_id']; } | { - type: 'all'; + type: undefined; } ); diff --git a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx index cb5c5875f00c9..e9ec3d61d1e76 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx @@ -83,7 +83,6 @@ const ThreadList = () => { return { rid, text, - type: 'all', }; } switch (type) { diff --git a/apps/meteor/tests/end-to-end/api/chat.ts b/apps/meteor/tests/end-to-end/api/chat.ts index 7c10eeba45bc0..9e55fb168e4c6 100644 --- a/apps/meteor/tests/end-to-end/api/chat.ts +++ b/apps/meteor/tests/end-to-end/api/chat.ts @@ -1425,6 +1425,87 @@ describe('[Chat]', () => { await updateSetting('Message_CustomFields_Enabled', false); await updateSetting('Message_CustomFields', ''); }); + it('should fail updating a message if no room id is provided', () => { + return request + .post(api('chat.update')) + .set(credentials) + .send({ + msgId: message._id, + text: 'This message was edited via API', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should fail updating a message if no message id is provided', () => { + return request + .post(api('chat.update')) + .set(credentials) + .send({ + roomId: testChannel._id, + text: 'This message was edited via API', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should fail updating a message if no text is provided', () => { + return request + .post(api('chat.update')) + .set(credentials) + .send({ + roomId: testChannel._id, + msgId: message._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should fail updating a message if an invalid message id is provided', () => { + return request + .post(api('chat.update')) + .set(credentials) + .send({ + roomId: testChannel._id, + msgId: 'invalid-id', + text: 'This message was edited via API', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'No message found with the id of "invalid-id".'); + }); + }); + + it('should fail updating a message if it is not in the provided room', () => { + return request + .post(api('chat.update')) + .set(credentials) + .send({ + roomId: 'invalid-room', + msgId: message._id, + text: 'This message was edited via API', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'The room id provided does not match where the message is from.'); + }); + }); it('should update a message successfully', (done) => { void request @@ -1643,6 +1724,64 @@ describe('[Chat]', () => { }) .end(done); }); + it('should fail deleting a message if no message id is provided', async () => { + return request + .post(api('chat.delete')) + .set(credentials) + .send({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + it('should fail deleting a message if no room id is provided', async () => { + return request + .post(api('chat.delete')) + .set(credentials) + .send({ + msgId, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + it('should fail deleting a message if it is not in the provided room', async () => { + return request + .post(api('chat.delete')) + .set(credentials) + .send({ + roomId: 'invalid-room', + msgId, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'The room id provided does not match where the message is from.'); + }); + }); + it('should fail deleting a message if an invalid id is provided', async () => { + return request + .post(api('chat.delete')) + .set(credentials) + .send({ + roomId: testChannel._id, + msgId: 'invalid-id', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', `No message found with the id of "invalid-id".`); + }); + }); it('should delete a message successfully', (done) => { void request .post(api('chat.delete')) @@ -2950,6 +3089,56 @@ describe('Threads', () => { }); }); + it("should fail returning a room's thread list if no roomId is provided", async () => { + await updatePermission('view-c-room', ['admin', 'user']); + return request + .get(api('chat.getThreadsList')) + .set(credentials) + .query({}) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it("should fail returning a room's thread list if an invalid type is provided", async () => { + return request + .get(api('chat.getThreadsList')) + .set(credentials) + .query({ + rid: testChannel._id, + type: 'invalid-type', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it("should return the room's thread list", async () => { + return request + .get(api('chat.getThreadsList')) + .set(credentials) + .query({ + rid: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('threads').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body.threads).to.have.lengthOf(1); + expect(res.body.threads[0]._id).to.be.equal(threadMessage.tmid); + }); + }); + it("should return the room's thread list even requested with count and offset params", (done) => { void updatePermission('view-c-room', ['admin', 'user']).then(() => { void request diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index ce486da5534cb..84e51b3c379c6 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -255,8 +255,9 @@ export const isChatReportMessageProps = ajv.compile(ChatRepor type ChatGetThreadsList = PaginatedRequest<{ rid: IRoom['_id']; - type: 'unread' | 'following' | 'all'; + type?: 'unread' | 'following'; text?: string; + fields?: string; }>; const ChatGetThreadsListSchema = { @@ -267,6 +268,7 @@ const ChatGetThreadsListSchema = { }, type: { type: 'string', + enum: ['following', 'unread'], nullable: true, }, text: { @@ -281,6 +283,18 @@ const ChatGetThreadsListSchema = { type: 'number', nullable: true, }, + sort: { + type: 'string', + nullable: true, + }, + query: { + type: 'string', + nullable: true, + }, + fields: { + type: 'string', + nullable: true, + }, }, required: ['rid'], additionalProperties: false, From 8015513074b72379d8ab9cfe6e9f2fe719882dfb Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Sun, 18 Aug 2024 11:14:45 -0300 Subject: [PATCH 33/85] chore!: Remove `meteor/check` from `banners` endpoints (#32509) --- apps/meteor/app/api/server/v1/banners.ts | 47 +------ apps/meteor/tests/end-to-end/api/banners.ts | 144 +++++++++++++++++++- packages/rest-typings/src/index.ts | 1 + packages/rest-typings/src/v1/banners.ts | 19 +-- 4 files changed, 152 insertions(+), 59 deletions(-) diff --git a/apps/meteor/app/api/server/v1/banners.ts b/apps/meteor/app/api/server/v1/banners.ts index 48c94d3711bdd..598497fcb837e 100644 --- a/apps/meteor/app/api/server/v1/banners.ts +++ b/apps/meteor/app/api/server/v1/banners.ts @@ -1,6 +1,5 @@ import { Banner } from '@rocket.chat/core-services'; -import { BannerPlatform } from '@rocket.chat/core-typings'; -import { Match, check } from 'meteor/check'; +import { isBannersDismissProps, isBannersGetNewProps, isBannersProps } from '@rocket.chat/rest-typings'; import { API } from '../api'; @@ -52,17 +51,10 @@ import { API } from '../api'; */ API.v1.addRoute( 'banners.getNew', - { authRequired: true, deprecation: { version: '8.0.0', alternatives: ['banners/:id', 'banners'] } }, + { authRequired: true, validateParams: isBannersGetNewProps, deprecation: { version: '8.0.0', alternatives: ['banners/:id', 'banners'] } }, { + // deprecated async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - platform: Match.OneOf(...Object.values(BannerPlatform)), - bid: Match.Maybe(String), - }), - ); - const { platform, bid: bannerId } = this.queryParams; const banners = await Banner.getBannersForUser(this.userId, platform, bannerId ?? undefined); @@ -120,23 +112,10 @@ API.v1.addRoute( */ API.v1.addRoute( 'banners/:id', - { authRequired: true }, + { authRequired: true, validateParams: isBannersProps }, { // TODO: move to users/:id/banners async get() { - check( - this.urlParams, - Match.ObjectIncluding({ - id: Match.Where((id: unknown): id is string => typeof id === 'string' && Boolean(id.trim())), - }), - ); - check( - this.queryParams, - Match.ObjectIncluding({ - platform: Match.OneOf(...Object.values(BannerPlatform)), - }), - ); - const { platform } = this.queryParams; const { id } = this.urlParams; @@ -186,16 +165,9 @@ API.v1.addRoute( */ API.v1.addRoute( 'banners', - { authRequired: true }, + { authRequired: true, validateParams: isBannersProps }, { async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - platform: Match.OneOf(...Object.values(BannerPlatform)), - }), - ); - const { platform } = this.queryParams; const banners = await Banner.getBannersForUser(this.userId, platform); @@ -240,16 +212,9 @@ API.v1.addRoute( */ API.v1.addRoute( 'banners.dismiss', - { authRequired: true }, + { authRequired: true, validateParams: isBannersDismissProps }, { async post() { - check( - this.bodyParams, - Match.ObjectIncluding({ - bannerId: Match.Where((id: unknown): id is string => typeof id === 'string' && Boolean(id.trim())), - }), - ); - const { bannerId } = this.bodyParams; await Banner.dismiss(this.userId, bannerId); diff --git a/apps/meteor/tests/end-to-end/api/banners.ts b/apps/meteor/tests/end-to-end/api/banners.ts index a4fd2638e7ddf..054f48532f68c 100644 --- a/apps/meteor/tests/end-to-end/api/banners.ts +++ b/apps/meteor/tests/end-to-end/api/banners.ts @@ -99,12 +99,12 @@ describe('banners', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', "Match error: Missing key 'bannerId'"); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); - it('should fail if missing bannerId is empty', (done) => { + it('should fail if bannerId is empty', (done) => { void request .post(api('banners.dismiss')) .set(credentials) @@ -118,7 +118,7 @@ describe('banners', () => { .end(done); }); - it('should fail if missing bannerId is invalid', (done) => { + it('should fail if bannerId is invalid', (done) => { void request .post(api('banners.dismiss')) .set(credentials) @@ -132,4 +132,142 @@ describe('banners', () => { .end(done); }); }); + + describe('[/banners]', () => { + it('should fail if not logged in', async () => { + return request + .get(api('banners')) + .query({ + platform: 'web', + }) + .expect(401) + .expect((res) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message'); + }); + }); + + it('should fail if missing platform', async () => { + return request + .get(api('banners')) + .set(credentials) + .query({}) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should fail if platform is invalid', async () => { + return request + .get(api('banners')) + .set(credentials) + .query({ + platform: 'invalid-platform', + }) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should succesfully return web banners', async () => { + return request + .get(api('banners')) + .set(credentials) + .query({ + platform: 'web', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('banners').that.is.an('array'); + }); + }); + + it('should succesfully return mobile banners', async () => { + return request + .get(api('banners')) + .set(credentials) + .query({ + platform: 'mobile', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('banners').that.is.an('array'); + }); + }); + }); + + describe('[/banners/:id]', () => { + it('should fail if not logged in', async () => { + return request + .get(api('banners/some-id')) + .query({ + platform: 'web', + }) + .expect(401) + .expect((res) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message'); + }); + }); + + it('should fail if missing platform', async () => { + return request + .get(api('banners/some-id')) + .set(credentials) + .query({}) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should fail if platform is invalid', async () => { + return request + .get(api('banners/some-id')) + .set(credentials) + .query({ + platform: 'invalid-platform', + }) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should succesfully return a web banner by id', async () => { + return request + .get(api('banners/some-id')) + .set(credentials) + .query({ + platform: 'web', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('banners').that.is.an('array'); + }); + }); + + it('should succesfully return a mobile banner by id', async () => { + return request + .get(api('banners/some-id')) + .set(credentials) + .query({ + platform: 'mobile', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('banners').that.is.an('array'); + }); + }); + }); }); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index cd02e14a71ce8..36c4d19b8ae34 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -271,3 +271,4 @@ export * from './v1/groups'; export * from './v1/chat'; export * from './v1/auth'; export * from './v1/cloud'; +export * from './v1/banners'; diff --git a/packages/rest-typings/src/v1/banners.ts b/packages/rest-typings/src/v1/banners.ts index 5ab1fc09c8f87..d3cedbcef75fb 100644 --- a/packages/rest-typings/src/v1/banners.ts +++ b/packages/rest-typings/src/v1/banners.ts @@ -15,13 +15,13 @@ const BannersGetNewSchema = { properties: { platform: { type: 'string', - enum: ['1', '2'], + enum: ['web', 'mobile'], }, bid: { type: 'string', }, }, - required: ['platform', 'bid'], + required: ['platform'], additionalProperties: false, }; @@ -31,19 +31,6 @@ type BannersId = { platform: BannerPlatform; }; -const BannersIdSchema = { - type: 'object', - properties: { - platform: { - type: 'string', - }, - }, - required: ['platform'], - additionalProperties: false, -}; - -export const isBannersIdProps = ajv.compile(BannersIdSchema); - type Banners = { platform: BannerPlatform; }; @@ -53,6 +40,7 @@ const BannersSchema = { properties: { platform: { type: 'string', + enum: ['web', 'mobile'], }, }, required: ['platform'], @@ -70,6 +58,7 @@ const BannersDismissSchema = { properties: { bannerId: { type: 'string', + minLength: 1, }, }, required: ['bannerId'], From e36247ec08604dbc913e72bd06b46af7b075179f Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Fri, 27 Sep 2024 18:22:49 -0300 Subject: [PATCH 34/85] chore!: Remove deprecated URL App installation (#33210) --- .changeset/heavy-carrots-reflect.md | 5 +++ .../views/marketplace/AppInstallPage.tsx | 32 ++++--------------- .../views/marketplace/hooks/useInstallApp.tsx | 28 +++------------- .../ee/server/apps/communication/rest.ts | 6 ---- packages/rest-typings/src/apps/index.ts | 3 +- 5 files changed, 16 insertions(+), 58 deletions(-) create mode 100644 .changeset/heavy-carrots-reflect.md diff --git a/.changeset/heavy-carrots-reflect.md b/.changeset/heavy-carrots-reflect.md new file mode 100644 index 0000000000000..1d85ebe67e698 --- /dev/null +++ b/.changeset/heavy-carrots-reflect.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Removes private App installation via URL method following a deprecation warning. diff --git a/apps/meteor/client/views/marketplace/AppInstallPage.tsx b/apps/meteor/client/views/marketplace/AppInstallPage.tsx index 5168f978b4bca..68ecc313991ab 100644 --- a/apps/meteor/client/views/marketplace/AppInstallPage.tsx +++ b/apps/meteor/client/views/marketplace/AppInstallPage.tsx @@ -1,6 +1,6 @@ -import { Button, ButtonGroup, Icon, Field, FieldGroup, FieldLabel, FieldRow, TextInput, Callout } from '@rocket.chat/fuselage'; +import { Button, ButtonGroup, Field, FieldGroup, FieldLabel, FieldRow, TextInput } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import { useTranslation, useRouter, useSearchParameter } from '@rocket.chat/ui-contexts'; +import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; import { useForm, Controller } from 'react-hook-form'; @@ -8,17 +8,13 @@ import { Page, PageHeader, PageScrollableContent } from '../../components/Page'; import { useSingleFileInput } from '../../hooks/useSingleFileInput'; import { useInstallApp } from './hooks/useInstallApp'; -const PLACEHOLDER_URL = 'https://rocket.chat/apps/package.zip'; - const AppInstallPage = () => { const t = useTranslation(); const router = useRouter(); - const queryUrl = useSearchParameter('url'); - - const { control, setValue, watch } = useForm<{ file: File; url: string }>({ defaultValues: { url: queryUrl || '' } }); - const { file, url } = watch(); - const { install, isInstalling } = useInstallApp(file, url); + const { control, setValue, watch } = useForm<{ file: File }>(); + const { file } = watch(); + const { install, isInstalling } = useInstallApp(file); const [handleUploadButtonClick] = useSingleFileInput((value) => setValue('file', value), 'app'); @@ -32,7 +28,6 @@ const AppInstallPage = () => { }); }, [router]); - const urlField = useUniqueId(); const fileField = useUniqueId(); return ( @@ -40,21 +35,6 @@ const AppInstallPage = () => { - - {t('App_Url_to_Install_From')} - - {t('App_Installation_Deprecation')} - - - ( - } {...field} /> - )} - /> - - {t('App_Url_to_Install_From_File')} @@ -79,7 +59,7 @@ const AppInstallPage = () => { - diff --git a/apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx b/apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx index 3b784e24c375b..a544413bb6a51 100644 --- a/apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx @@ -1,5 +1,5 @@ import type { App, AppPermission } from '@rocket.chat/core-typings'; -import { useRouter, useSetModal, useUpload, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useRouter, useSetModal, useUpload } from '@rocket.chat/ui-contexts'; import { useMutation } from '@tanstack/react-query'; import React, { useCallback, useState } from 'react'; @@ -15,7 +15,7 @@ import { handleInstallError } from '../helpers/handleInstallError'; import { getManifestFromZippedApp } from '../lib/getManifestFromZippedApp'; import { useAppsCountQuery } from './useAppsCountQuery'; -export const useInstallApp = (file: File, url: string): { install: () => void; isInstalling: boolean } => { +export const useInstallApp = (file: File): { install: () => void; isInstalling: boolean } => { const reloadAppsList = useAppsReload(); const openExternalLink = useExternalLink(); const setModal = useSetModal(); @@ -28,9 +28,6 @@ export const useInstallApp = (file: File, url: string): { install: () => void; i const uploadAppEndpoint = useUpload('/apps'); const uploadUpdateEndpoint = useUpload('/apps/update'); - // TODO: This function should not be called in a next major version, it will be changed by an endpoint deprecation. - const downloadPrivateAppFromUrl = useEndpoint('POST', '/apps'); - const [isInstalling, setInstalling] = useState(false); const { mutate: sendFile } = useMutation( @@ -102,17 +99,6 @@ export const useInstallApp = (file: File, url: string): { install: () => void; i await handleAppPermissionsReview(permissions, appFile); }; - /** @deprecated */ - const getAppFile = async (): Promise => { - try { - const { buff } = (await downloadPrivateAppFromUrl({ url, downloadOnly: true })) as { buff: { data: ArrayLike } }; - - return new File([Uint8Array.from(buff.data)], 'app.zip', { type: 'application/zip' }); - } catch (error) { - handleInstallError(error as Error); - } - }; - const extractManifestFromAppFile = async (appFile: File) => { try { return getManifestFromZippedApp(appFile); @@ -122,19 +108,13 @@ export const useInstallApp = (file: File, url: string): { install: () => void; i }; const install = async () => { - let appFile: File | undefined; - setInstalling(true); if (!appCountQuery.data) { return cancelAction(); } - if (!file) { - appFile = await getAppFile(); - } else { - appFile = file; - } + const appFile = file; if (!appFile) { return cancelAction(); @@ -157,7 +137,7 @@ export const useInstallApp = (file: File, url: string): { install: () => void; i limit={appCountQuery.data.limit} appName={manifest.name} handleClose={cancelAction} - handleConfirm={() => uploadFile(appFile as File, manifest)} + handleConfirm={() => uploadFile(appFile, manifest)} handleEnableUnlimitedApps={() => { openExternalLink(manageSubscriptionUrl); setModal(null); diff --git a/apps/meteor/ee/server/apps/communication/rest.ts b/apps/meteor/ee/server/apps/communication/rest.ts index 3eed45f4ddad6..f3420d2ccce91 100644 --- a/apps/meteor/ee/server/apps/communication/rest.ts +++ b/apps/meteor/ee/server/apps/communication/rest.ts @@ -328,12 +328,6 @@ export class AppsRestApi { orchestrator.getRocketChatLogger().error('Error getting the app from url:', e.response.data); return API.v1.internalError(); } - - if (this.bodyParams.downloadOnly) { - apiDeprecationLogger.parameter(this.request.route, 'downloadOnly', '7.0.0', this.response); - - return API.v1.success({ buff }); - } } else if ('appId' in this.bodyParams && this.bodyParams.appId && this.bodyParams.marketplace && this.bodyParams.version) { const baseUrl = orchestrator.getMarketplaceUrl(); diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index f037f6b59c74a..b25297a61e7a1 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -248,9 +248,8 @@ export type AppsEndpoints = { version: string; permissionsGranted?: IPermission[]; url?: string; - downloadOnly?: boolean; } - | { url: string; downloadOnly?: boolean }, + | { url: string }, ): | { app: App; From f8e27dab3cbcc3583f429bf14bb70f588584a1c8 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 1 Oct 2024 06:32:36 +0530 Subject: [PATCH 35/85] chore!: remove deprecated livechat:loadHistory method (#33390) Signed-off-by: Abhinav Kumar --- .changeset/slow-crabs-run.md | 5 ++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/loadHistory.ts | 48 --------------- .../api/livechat/methods/loadHistory.ts | 61 ------------------- 4 files changed, 5 insertions(+), 110 deletions(-) create mode 100644 .changeset/slow-crabs-run.md delete mode 100644 apps/meteor/app/livechat/server/methods/loadHistory.ts delete mode 100644 apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts diff --git a/.changeset/slow-crabs-run.md b/.changeset/slow-crabs-run.md new file mode 100644 index 0000000000000..8df05f8937d6b --- /dev/null +++ b/.changeset/slow-crabs-run.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Removed deprecated method `livechat:loadHistory` method. Moving forward use the `ivechat/messages.history/:rid` endpoint diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 357d444ac4742..675f0531996d0 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -29,7 +29,6 @@ import './methods/getAnalyticsChartData'; import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; import './methods/getRoutingConfig'; -import './methods/loadHistory'; import './methods/loginByToken'; import './methods/pageVisited'; import './methods/registerGuest'; diff --git a/apps/meteor/app/livechat/server/methods/loadHistory.ts b/apps/meteor/app/livechat/server/methods/loadHistory.ts deleted file mode 100644 index 1004f9dd604dd..0000000000000 --- a/apps/meteor/app/livechat/server/methods/loadHistory.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { IMessage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; -import { check, Match } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { loadMessageHistory } from '../../../lib/server/functions/loadMessageHistory'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:loadHistory'(params: { token: string; rid: string; end?: Date; limit?: number; ls: Date }): - | { - messages: IMessage[]; - firstUnread: any; - unreadNotLoaded: number; - } - | undefined; - } -} - -Meteor.methods({ - async 'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) { - methodDeprecationLogger.method('livechat:loadHistory', '7.0.0'); - - check(token, String); - check(rid, String); - check(end, Date); - check(ls, Match.OneOf(String, Date)); - check(limit, Number); - - const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); - - if (!visitor) { - throw new Meteor.Error('invalid-visitor', 'Invalid Visitor', { - method: 'livechat:loadHistory', - }); - } - - const room = await LivechatRooms.findOneByIdAndVisitorToken(rid, token, { projection: { _id: 1 } }); - if (!room) { - throw new Meteor.Error('invalid-room', 'Invalid Room', { method: 'livechat:loadHistory' }); - } - - return loadMessageHistory({ userId: visitor._id, rid, end, limit, ls }); - }, -}); diff --git a/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts b/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts deleted file mode 100644 index f251d7ebe92da..0000000000000 --- a/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { ILivechatAgent } from '@rocket.chat/core-typings'; -import { expect } from 'chai'; -import { before, describe, it, after } from 'mocha'; -import type { Response } from 'supertest'; - -import { getCredentials, request, methodCallAnon, credentials } from '../../../../data/api-data'; -import { createAgent, makeAgentAvailable, sendMessage, startANewLivechatRoomAndTakeIt } from '../../../../data/livechat/rooms'; -import { removeAgent } from '../../../../data/livechat/users'; -import { updateSetting } from '../../../../data/permissions.helper'; -import { adminUsername } from '../../../../data/user'; - -describe('livechat:loadHistory', function () { - this.retries(0); - let agent: ILivechatAgent; - - before((done) => getCredentials(done)); - - before(async () => { - await updateSetting('Livechat_enabled', true); - agent = await createAgent(adminUsername); - await makeAgentAvailable(credentials); - }); - - after('remove agent', async () => { - await removeAgent(agent._id); - }); - - describe('loadHistory', async () => { - it('prevent getting unrelated message history using regex on rid param', async () => { - const { - room: { _id: roomId }, - visitor: { token }, - } = await startANewLivechatRoomAndTakeIt(); - - await sendMessage(roomId, 'Hello from visitor', token); - - await request - .post(methodCallAnon('livechat:loadHistory')) - .send({ - message: JSON.stringify({ - msg: 'method', - id: 'id2', - method: 'livechat:loadHistory', - params: [ - { - token, - rid: { $regex: '.*' }, - }, - ], - }), - }) - .expect(200) - .expect((res: Response) => { - expect(res.body).to.have.property('success', true); - const parsedBody = JSON.parse(res.body.message); - expect(parsedBody).to.have.property('error'); - expect(parsedBody).to.not.have.property('result'); - }); - }); - }); -}); From b62877c2427a40de3820ee30fb837f9c988862ff Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 1 Oct 2024 06:34:52 +0530 Subject: [PATCH 36/85] chore!: removed removeWebdavAccount method (#33355) Signed-off-by: Abhinav Kumar --- .changeset/dull-singers-move.md | 5 +++ apps/meteor/app/api/server/v1/webdav.ts | 12 +++++- apps/meteor/app/webdav/server/index.ts | 1 - .../server/methods/removeWebdavAccount.ts | 42 ------------------- 4 files changed, 15 insertions(+), 45 deletions(-) create mode 100644 .changeset/dull-singers-move.md delete mode 100644 apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts diff --git a/.changeset/dull-singers-move.md b/.changeset/dull-singers-move.md new file mode 100644 index 0000000000000..6d744cc946112 --- /dev/null +++ b/.changeset/dull-singers-move.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +This adjustment removes the deprecated `removeWebdavAccount` method. Moving forward, use the `webdav.removeWebdavAccount` endpoint to remove WebDAV accounts. diff --git a/apps/meteor/app/api/server/v1/webdav.ts b/apps/meteor/app/api/server/v1/webdav.ts index b282d5ed6a564..2bdd2ba8f2e46 100644 --- a/apps/meteor/app/api/server/v1/webdav.ts +++ b/apps/meteor/app/api/server/v1/webdav.ts @@ -1,3 +1,5 @@ +import { api } from '@rocket.chat/core-services'; +import { WebdavAccounts } from '@rocket.chat/models'; import Ajv from 'ajv'; import { API } from '../api'; @@ -45,9 +47,15 @@ API.v1.addRoute( async post() { const { accountId } = this.bodyParams; - const result = await Meteor.callAsync('removeWebdavAccount', accountId); + const removed = await WebdavAccounts.removeByUserAndId(accountId, this.userId); + if (removed) { + void api.broadcast('notify.webdav', this.userId, { + type: 'removed', + account: { _id: accountId }, + }); + } - return API.v1.success({ result }); + return API.v1.success({ result: removed }); }, }, ); diff --git a/apps/meteor/app/webdav/server/index.ts b/apps/meteor/app/webdav/server/index.ts index d99d96c7c800f..36bdfcae93324 100644 --- a/apps/meteor/app/webdav/server/index.ts +++ b/apps/meteor/app/webdav/server/index.ts @@ -1,5 +1,4 @@ import './methods/addWebdavAccount'; -import './methods/removeWebdavAccount'; import './methods/getWebdavFileList'; import './methods/getWebdavFilePreview'; import './methods/getFileFromWebdav'; diff --git a/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts b/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts deleted file mode 100644 index a68a14e29e40b..0000000000000 --- a/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { api } from '@rocket.chat/core-services'; -import type { IWebdavAccount } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { WebdavAccounts } from '@rocket.chat/models'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; -import type { DeleteResult } from 'mongodb'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - removeWebdavAccount(accountId: IWebdavAccount['_id']): DeleteResult; - } -} - -Meteor.methods({ - async removeWebdavAccount(accountId) { - const userId = Meteor.userId(); - - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid User', { - method: 'removeWebdavAccount', - }); - } - - check(accountId, String); - - methodDeprecationLogger.method('removeWebdavAccount', '7.0.0'); - - const removed = await WebdavAccounts.removeByUserAndId(accountId, userId); - if (removed) { - void api.broadcast('notify.webdav', userId, { - type: 'removed', - account: { _id: accountId }, - }); - } - - return removed; - }, -}); From 248655f56ab5216f02d4da5226fde3d761d55269 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 1 Oct 2024 06:37:22 +0530 Subject: [PATCH 37/85] chore!: remove livechat:addAgent and livechat:addManager method (#33372) Signed-off-by: Abhinav Kumar --- .changeset/tasty-goats-deny.md | 5 ++++ apps/meteor/app/livechat/server/index.ts | 2 -- .../app/livechat/server/methods/addAgent.ts | 26 ------------------- .../app/livechat/server/methods/addManager.ts | 26 ------------------- 4 files changed, 5 insertions(+), 54 deletions(-) create mode 100644 .changeset/tasty-goats-deny.md delete mode 100644 apps/meteor/app/livechat/server/methods/addAgent.ts delete mode 100644 apps/meteor/app/livechat/server/methods/addManager.ts diff --git a/.changeset/tasty-goats-deny.md b/.changeset/tasty-goats-deny.md new file mode 100644 index 0000000000000..37acfd035c34e --- /dev/null +++ b/.changeset/tasty-goats-deny.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +This adjustment removes deprecated `livechat:addAgent` and `livechat:addManager` method. Moving forward use `livechat/users/agent` and `livechat/users/manager` endpoints to add agent and manager respectively. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 675f0531996d0..37a7705136ec3 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -17,8 +17,6 @@ import './hooks/saveLastMessageToInquiry'; import './hooks/afterUserActions'; import './hooks/afterAgentRemoved'; import './hooks/afterSaveOmnichannelMessage'; -import './methods/addAgent'; -import './methods/addManager'; import './methods/changeLivechatStatus'; import './methods/closeRoom'; import './methods/discardTranscript'; diff --git a/apps/meteor/app/livechat/server/methods/addAgent.ts b/apps/meteor/app/livechat/server/methods/addAgent.ts deleted file mode 100644 index 0551e985e18e9..0000000000000 --- a/apps/meteor/app/livechat/server/methods/addAgent.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:addAgent'(username: string): Promise; - } -} - -Meteor.methods({ - async 'livechat:addAgent'(username) { - const uid = Meteor.userId(); - methodDeprecationLogger.method('livechat:addAgent', '7.0.0'); - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-agents'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:addAgent' }); - } - - return Livechat.addAgent(username); - }, -}); diff --git a/apps/meteor/app/livechat/server/methods/addManager.ts b/apps/meteor/app/livechat/server/methods/addManager.ts deleted file mode 100644 index a954d8111773d..0000000000000 --- a/apps/meteor/app/livechat/server/methods/addManager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:addManager'(username: string): Promise; - } -} - -Meteor.methods({ - async 'livechat:addManager'(username) { - const uid = Meteor.userId(); - methodDeprecationLogger.method('livechat:addManager', '7.0.0'); - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-managers'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:addManager' }); - } - - return Livechat.addManager(username); - }, -}); From 531b25b25916b771b5aabe0c505a0ab7aff65854 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:05:55 -0300 Subject: [PATCH 38/85] fix: Private apps restrictions are not applied on license removal (#33400) * feat: Apply CE restrictions related to private apps on license removal * Create changeset --- .changeset/eighty-items-behave.md | 5 +++++ apps/meteor/ee/server/startup/apps/trialExpiration.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .changeset/eighty-items-behave.md diff --git a/.changeset/eighty-items-behave.md b/.changeset/eighty-items-behave.md new file mode 100644 index 0000000000000..db8bc26e564f9 --- /dev/null +++ b/.changeset/eighty-items-behave.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Adds missing readjustment to private apps restrictions on license removal diff --git a/apps/meteor/ee/server/startup/apps/trialExpiration.ts b/apps/meteor/ee/server/startup/apps/trialExpiration.ts index e6bb9e47c7491..46ccac7ee810c 100644 --- a/apps/meteor/ee/server/startup/apps/trialExpiration.ts +++ b/apps/meteor/ee/server/startup/apps/trialExpiration.ts @@ -1,9 +1,12 @@ import { License } from '@rocket.chat/license'; import { Meteor } from 'meteor/meteor'; -Meteor.startup(() => { - License.onInvalidateLicense(async () => { - const { Apps } = await import('../../apps'); +Meteor.startup(async () => { + const { Apps } = await import('../../apps'); + License.onInvalidateLicense(() => { + void Apps.disableApps(); + }); + License.onRemoveLicense(() => { void Apps.disableApps(); }); }); From ecc557334b0b22f1a0026ac6839b65d301a7c970 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Tue, 1 Oct 2024 02:14:11 -0300 Subject: [PATCH 39/85] feat: New Private apps limitations (#33316) * feat: New empty state for upgrading private Apps * chore: Change Marketplace info modal text (#33239) * feat: New tooltips and color behavior for private apps bar (#33243) * feat: New tooltips and behavior for private apps bar * Create brown-pants-press.md * feat: new modal on Private Apps install (#33275) * feat: new modal on Private Apps install * add more variations * Create eleven-rockets-hug.md * chore: change grandfathered modal text (#33291) * chore: Use apps provider to check maxPrivateApps * fix: adds minor fixes to UI and changes requested on review * Update changeset * Replace negative boolean * Refactor `AppsUsageCard` * Add unit test for `AppsUsageCard` * Add unit test for `PrivateEmptyState` * Add unit test for `EnabledAppsCount` * Move tooltip logic away from `useAppsCountQuery` --------- Co-authored-by: Lucas Pelegrino Co-authored-by: Tasso --- .changeset/brown-pants-press.md | 6 ++ .../GenericResourceUsage.tsx | 31 ++++++- .../GenericResourceUsageSkeleton.tsx | 7 +- apps/meteor/client/contexts/AppsContext.tsx | 2 + .../providers/AppsProvider/AppsProvider.tsx | 29 ++++--- .../components/FeatureUsageCard.tsx | 2 +- .../components/InfoTextIconModal.tsx | 4 +- .../components/cards/AppsUsageCard.tsx | 71 ---------------- .../AppsUsageCard/AppsUsageCard.spec.tsx | 84 +++++++++++++++++++ .../cards/AppsUsageCard/AppsUsageCard.tsx | 84 +++++++++++++++++++ .../AppsUsageCard/AppsUsageCardSection.tsx | 42 ++++++++++ .../components/cards/AppsUsageCard/index.ts | 1 + .../AppsPage/PrivateEmptyState.spec.tsx | 63 ++++++++++++++ .../AppsPage/PrivateEmptyState.tsx | 19 ++--- .../AppsPage/PrivateEmptyStateDefault.tsx | 17 ++++ .../AppsPage/PrivateEmptyStateUpgrade.tsx | 28 +++++++ .../components/EnabledAppsCount.spec.tsx | 62 ++++++++++++++ .../components/EnabledAppsCount.tsx | 23 +++-- .../components/MarketplaceHeader.tsx | 57 ++++++++++--- .../PrivateAppInstallModal.tsx | 56 +++++++++++++ .../UninstallGrandfatheredAppModal.tsx | 9 +- .../marketplace/hooks/useAppsCountQuery.ts | 12 +-- .../hooks/usePrivateAppsEnabled.ts | 9 ++ .../meteor/tests/mocks/client/marketplace.tsx | 1 + packages/i18n/src/locales/en.i18n.json | 14 +++- packages/i18n/src/locales/es.i18n.json | 4 +- packages/i18n/src/locales/fi.i18n.json | 1 - packages/i18n/src/locales/hi-IN.i18n.json | 3 +- packages/i18n/src/locales/pl.i18n.json | 4 +- packages/i18n/src/locales/sv.i18n.json | 1 - 30 files changed, 604 insertions(+), 142 deletions(-) create mode 100644 .changeset/brown-pants-press.md delete mode 100644 apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx create mode 100644 apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx create mode 100644 apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx create mode 100644 apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCardSection.tsx create mode 100644 apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/index.ts create mode 100644 apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.spec.tsx create mode 100644 apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateDefault.tsx create mode 100644 apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx create mode 100644 apps/meteor/client/views/marketplace/components/EnabledAppsCount.spec.tsx create mode 100644 apps/meteor/client/views/marketplace/components/PrivateAppInstallModal/PrivateAppInstallModal.tsx create mode 100644 apps/meteor/client/views/marketplace/hooks/usePrivateAppsEnabled.ts diff --git a/.changeset/brown-pants-press.md b/.changeset/brown-pants-press.md new file mode 100644 index 0000000000000..d65eff3db6891 --- /dev/null +++ b/.changeset/brown-pants-press.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": major +"@rocket.chat/i18n": major +--- + +Changes some displays to reflect new rules for private apps and adds a new modal before uploading a private app diff --git a/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsage.tsx b/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsage.tsx index c226139b94893..80c40183655b1 100644 --- a/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsage.tsx +++ b/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsage.tsx @@ -1,4 +1,5 @@ import { Box, ProgressBar } from '@rocket.chat/fuselage'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import type { ReactNode } from 'react'; import React from 'react'; @@ -10,6 +11,7 @@ const GenericResourceUsage = ({ threshold = 80, variant = percentage < threshold ? 'success' : 'danger', subTitle, + tooltip, ...props }: { title: string; @@ -19,17 +21,40 @@ const GenericResourceUsage = ({ percentage: number; threshold?: number; variant?: 'warning' | 'danger' | 'success'; + tooltip?: string; }) => { + const labelId = useUniqueId(); + return ( - + - {title} + + {title} + {subTitle && {subTitle}} {value}/{max} - + ); }; diff --git a/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsageSkeleton.tsx b/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsageSkeleton.tsx index 2820379e7469c..8c82a66486741 100644 --- a/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsageSkeleton.tsx +++ b/apps/meteor/client/components/GenericResourceUsage/GenericResourceUsageSkeleton.tsx @@ -1,7 +1,12 @@ import { Box, Skeleton } from '@rocket.chat/fuselage'; +import type { ComponentProps } from 'react'; import React from 'react'; -const GenericResourceUsageSkeleton = ({ title, ...props }: { title?: string }) => { +type GenericResourceUsageSkeletonProps = { + title?: string; +} & ComponentProps; + +const GenericResourceUsageSkeleton = ({ title, ...props }: GenericResourceUsageSkeletonProps) => { return ( {title ? {title} : } diff --git a/apps/meteor/client/contexts/AppsContext.tsx b/apps/meteor/client/contexts/AppsContext.tsx index 2be8e74c2d672..ee3d2e46a45a0 100644 --- a/apps/meteor/client/contexts/AppsContext.tsx +++ b/apps/meteor/client/contexts/AppsContext.tsx @@ -32,6 +32,7 @@ export type AppsContextValue = { privateApps: Omit, 'error'>; reload: () => Promise; orchestrator?: IAppsOrchestrator; + privateAppsEnabled: boolean; }; export const AppsContext = createContext({ @@ -49,4 +50,5 @@ export const AppsContext = createContext({ }, reload: () => Promise.resolve(), orchestrator: undefined, + privateAppsEnabled: false, }); diff --git a/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx b/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx index cf1d4d671d94b..b2145a07fb7ad 100644 --- a/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx +++ b/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx @@ -6,8 +6,7 @@ import React, { useEffect } from 'react'; import { AppClientOrchestratorInstance } from '../../apps/orchestrator'; import { AppsContext } from '../../contexts/AppsContext'; -import { useIsEnterprise } from '../../hooks/useIsEnterprise'; -import { useInvalidateLicense } from '../../hooks/useLicense'; +import { useInvalidateLicense, useLicense } from '../../hooks/useLicense'; import type { AsyncState } from '../../lib/asyncState'; import { AsyncStatePhase } from '../../lib/asyncState'; import { useInvalidateAppsCountQueryCallback } from '../../views/marketplace/hooks/useAppsCountQuery'; @@ -36,8 +35,8 @@ const AppsProvider = ({ children }: AppsProviderProps) => { const queryClient = useQueryClient(); - const { data } = useIsEnterprise(); - const isEnterprise = !!data?.isEnterprise; + const { isLoading: isLicenseInformationLoading, data: { license, limits } = {} } = useLicense({ loadValues: true }); + const isEnterprise = isLicenseInformationLoading ? undefined : !!license; const invalidateAppsCountQuery = useInvalidateAppsCountQueryCallback(); const invalidateLicenseQuery = useInvalidateLicense(); @@ -95,25 +94,29 @@ const AppsProvider = ({ children }: AppsProviderProps) => { }, ); - const store = useQuery(['marketplace', 'apps-stored', instance.data, marketplace.data], () => storeQueryFunction(marketplace, instance), { - enabled: marketplace.isFetched && instance.isFetched, - keepPreviousData: true, - }); + const { isLoading: isMarketplaceDataLoading, data: marketplaceData } = useQuery( + ['marketplace', 'apps-stored', instance.data, marketplace.data], + () => storeQueryFunction(marketplace, instance), + { + enabled: marketplace.isFetched && instance.isFetched, + keepPreviousData: true, + }, + ); - const [marketplaceAppsData, installedAppsData, privateAppsData] = store.data || []; - const { isLoading } = store; + const [marketplaceAppsData, installedAppsData, privateAppsData] = marketplaceData || []; return ( { await Promise.all([queryClient.invalidateQueries(['marketplace'])]); }, orchestrator: AppClientOrchestratorInstance, + privateAppsEnabled: (limits?.privateApps?.max ?? 0) > 0, }} /> ); diff --git a/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx index 3e736062bd7d4..34a9746b1b530 100644 --- a/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx @@ -11,7 +11,7 @@ type FeatureUsageCardProps = { export type CardProps = { title: string; - infoText?: string; + infoText?: ReactNode; upgradeButton?: ReactNode; }; diff --git a/apps/meteor/client/views/admin/subscription/components/InfoTextIconModal.tsx b/apps/meteor/client/views/admin/subscription/components/InfoTextIconModal.tsx index 9316949dcb66c..1f6fb6db19094 100644 --- a/apps/meteor/client/views/admin/subscription/components/InfoTextIconModal.tsx +++ b/apps/meteor/client/views/admin/subscription/components/InfoTextIconModal.tsx @@ -1,6 +1,6 @@ import { IconButton } from '@rocket.chat/fuselage'; import { useSetModal } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import React, { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,7 +8,7 @@ import GenericModal from '../../../../components/GenericModal'; export type InfoTextIconModalProps = { title: string; - infoText: string; + infoText: ReactNode; }; const InfoTextIconModal = ({ title, infoText }: InfoTextIconModalProps): ReactElement => { diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx deleted file mode 100644 index dbd402ef2c7f2..0000000000000 --- a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Box, ProgressBar, Skeleton } from '@rocket.chat/fuselage'; -import type { ReactElement } from 'react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -import type { CardProps } from '../FeatureUsageCard'; -import FeatureUsageCard from '../FeatureUsageCard'; -import UpgradeButton from '../UpgradeButton'; - -type AppsUsageCardProps = { - privateAppsLimit?: { value?: number; max: number }; - marketplaceAppsLimit?: { value?: number; max: number }; -}; - -const AppsUsageCard = ({ privateAppsLimit, marketplaceAppsLimit }: AppsUsageCardProps): ReactElement => { - const { t } = useTranslation(); - - const marketplaceAppsEnabled = marketplaceAppsLimit?.value || 0; - const marketplaceAppsLimitCount = marketplaceAppsLimit?.max || 5; - const marketplaceAppsPercentage = Math.round((marketplaceAppsEnabled / marketplaceAppsLimitCount) * 100); - - const privateAppsEnabled = privateAppsLimit?.value || 0; - const privateAppsLimitCount = privateAppsLimit?.max || 3; - const privateAppsPercentage = Math.round((privateAppsEnabled / privateAppsLimitCount) * 100); - - const card: CardProps = { - title: t('Apps'), - infoText: t('Apps_InfoText'), - ...((marketplaceAppsPercentage || 0) >= 80 && { - upgradeButton: ( - - {t('Upgrade')} - - ), - }), - }; - - if (!privateAppsLimit || !marketplaceAppsLimit) { - return ( - - - - ); - } - - return ( - - - -
{t('Marketplace_apps')}
- = 80 ? 'font-danger' : 'status-font-on-success'}> - {marketplaceAppsEnabled} / {marketplaceAppsLimitCount} - -
- - = 80 ? 'danger' : 'success'} /> -
- - -
{t('Private_apps')}
- = 80 ? 'font-danger' : 'status-font-on-success'}> - {privateAppsEnabled} / {privateAppsLimitCount} - -
- - = 80 ? 'danger' : 'success'} /> -
-
- ); -}; -export default AppsUsageCard; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx new file mode 100644 index 0000000000000..b5d44e3bf942c --- /dev/null +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx @@ -0,0 +1,84 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import AppsUsageCard from './AppsUsageCard'; + +const appRoot = mockAppRoot().withTranslations('en', 'core', { + Apps_InfoText_limited: + 'Community workspaces can enable up to {{marketplaceAppsMaxCount}} marketplace apps. Private apps can only be enabled in <1>premium plans.', + Apps_InfoText: + 'Community allows up to {{privateAppsMaxCount}} private apps and {{marketplaceAppsMaxCount}} marketplace apps to be enabled', +}); + +it('should render a skeleton if no data', () => { + render(, { wrapper: appRoot.build(), legacyRoot: true }); + + expect(screen.getByRole('heading', { name: 'Apps' })).toBeInTheDocument(); + expect(screen.getByRole('presentation')).toBeInTheDocument(); +}); + +it('should render data as progress bars', async () => { + render(, { + wrapper: appRoot.build(), + legacyRoot: true, + }); + + expect(screen.getByRole('heading', { name: 'Apps' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Click_here_for_more_info' })).toBeInTheDocument(); + + expect(screen.getByRole('progressbar', { name: 'Marketplace_apps' })).toBeInTheDocument(); + expect(screen.getByRole('progressbar', { name: 'Marketplace_apps' })).toHaveAttribute('aria-valuenow', '40'); + expect(screen.getByText('2 / 5')).toBeInTheDocument(); + + expect(screen.getByRole('progressbar', { name: 'Private_apps' })).toBeInTheDocument(); + expect(screen.getByRole('progressbar', { name: 'Private_apps' })).toHaveAttribute('aria-valuenow', '33'); + expect(screen.getByText('1 / 3')).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: 'Click_here_for_more_info' })); + + expect( + screen.getByText('Community workspaces can enable up to 5 marketplace apps. Private apps can only be enabled in premium plans.'), + ).toBeInTheDocument(); +}); + +it('should render an upgrade button if marketplace apps reached 80% of the limit', async () => { + render(, { + wrapper: appRoot.build(), + legacyRoot: true, + }); + + expect(screen.getByRole('heading', { name: 'Apps' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Click_here_for_more_info' })).toBeInTheDocument(); + + expect(screen.getByRole('button', { name: 'Upgrade' })).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: 'Click_here_for_more_info' })); + + expect( + screen.getByText('Community workspaces can enable up to 5 marketplace apps. Private apps can only be enabled in premium plans.'), + ).toBeInTheDocument(); +}); + +it('should render a full progress bar with private apps disabled', async () => { + render(, { + wrapper: appRoot.build(), + legacyRoot: true, + }); + + expect(screen.getByRole('heading', { name: 'Apps' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Click_here_for_more_info' })).toBeInTheDocument(); + + expect(screen.getByRole('progressbar', { name: 'Marketplace_apps' })).toBeInTheDocument(); + expect(screen.getByRole('progressbar', { name: 'Marketplace_apps' })).toHaveAttribute('aria-valuenow', '40'); + expect(screen.getByText('2 / 5')).toBeInTheDocument(); + + expect(screen.getByRole('progressbar', { name: 'Private_apps' })).toBeInTheDocument(); + expect(screen.getByRole('progressbar', { name: 'Private_apps' })).toHaveAttribute('aria-valuenow', '100'); + expect(screen.getByText('0 / 0')).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: 'Click_here_for_more_info' })); + + expect(screen.getByText('Community allows up to 0 private apps and 5 marketplace apps to be enabled')).toBeInTheDocument(); +}); diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx new file mode 100644 index 0000000000000..aa8e91c0f8575 --- /dev/null +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx @@ -0,0 +1,84 @@ +import { Box, Skeleton } from '@rocket.chat/fuselage'; +import type { ReactElement } from 'react'; +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import type { CardProps } from '../../FeatureUsageCard'; +import FeatureUsageCard from '../../FeatureUsageCard'; +import UpgradeButton from '../../UpgradeButton'; +import AppsUsageCardSection from './AppsUsageCardSection'; + +// Magic numbers +const marketplaceAppsMaxCountFallback = 5; +const privateAppsMaxCountFallback = 0; +const defaultWarningThreshold = 80; + +type AppsUsageCardProps = { + privateAppsLimit?: { value?: number; max: number }; + marketplaceAppsLimit?: { value?: number; max: number }; +}; + +const AppsUsageCard = ({ privateAppsLimit, marketplaceAppsLimit }: AppsUsageCardProps): ReactElement => { + const { t } = useTranslation(); + + if (!privateAppsLimit || !marketplaceAppsLimit) { + // FIXME: not accessible enough + return ( + + + + ); + } + + const marketplaceAppsCount = marketplaceAppsLimit?.value || 0; + const marketplaceAppsMaxCount = marketplaceAppsLimit?.max || marketplaceAppsMaxCountFallback; + const marketplaceAppsPercentage = Math.round((marketplaceAppsCount / marketplaceAppsMaxCount) * 100) || 0; + const marketplaceAppsAboveWarning = marketplaceAppsPercentage >= defaultWarningThreshold; + + const privateAppsCount = privateAppsLimit?.value || 0; + const privateAppsMaxCount = privateAppsLimit?.max || privateAppsMaxCountFallback; + + const card: CardProps = { + title: t('Apps'), + infoText: + privateAppsCount > 0 ? ( + + Community workspaces can enable up to {{ marketplaceAppsMaxCount }} marketplace apps. Private apps can only be enabled in{' '} + + premium plans + + . + + ) : ( + t('Apps_InfoText', { privateAppsMaxCount, marketplaceAppsMaxCount }) + ), + ...(marketplaceAppsAboveWarning && { + upgradeButton: ( + + {t('Upgrade')} + + ), + }), + }; + + return ( + + + + + + ); +}; + +export default AppsUsageCard; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCardSection.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCardSection.tsx new file mode 100644 index 0000000000000..ac17957eda01c --- /dev/null +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCardSection.tsx @@ -0,0 +1,42 @@ +import { Box, ProgressBar } from '@rocket.chat/fuselage'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; +import type { ReactNode } from 'react'; +import React from 'react'; + +type AppsUsageCardSectionProps = { + title: ReactNode; + tip?: string; + appsCount: number; + appsMaxCount: number; + warningThreshold: number; +}; + +const AppsUsageCardSection = ({ title, tip, appsCount, appsMaxCount, warningThreshold }: AppsUsageCardSectionProps) => { + const percentage = appsMaxCount === 0 ? 100 : Math.round((appsCount * 100) / appsMaxCount); + const warningThresholdCrossed = percentage >= warningThreshold; + const labelId = useUniqueId(); + + return ( + + +
{title}
+ + + {appsCount} / {appsMaxCount} + +
+ + +
+ ); +}; + +export default AppsUsageCardSection; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/index.ts b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/index.ts new file mode 100644 index 0000000000000..2a076ecba05e1 --- /dev/null +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/index.ts @@ -0,0 +1 @@ +export { default } from './AppsUsageCard'; diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.spec.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.spec.tsx new file mode 100644 index 0000000000000..c2d309d6102fb --- /dev/null +++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.spec.tsx @@ -0,0 +1,63 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { AppsContext } from '../../../contexts/AppsContext'; +import { asyncState } from '../../../lib/asyncState'; +import PrivateEmptyState from './PrivateEmptyState'; + +describe('with private apps enabled', () => { + const appRoot = mockAppRoot() + .withTranslations('en', 'core', { + Private_apps_upgrade_empty_state_title: 'Upgrade to unlock private apps', + No_private_apps_installed: 'No private apps installed', + }) + .wrap((children) => ( + Promise.resolve(), + orchestrator: undefined, + privateAppsEnabled: true, + }} + > + {children} + + )); + + it('should offer to upgrade to unlock private apps', () => { + render(, { wrapper: appRoot.build(), legacyRoot: true }); + + expect(screen.getByRole('heading', { name: 'No private apps installed' })).toBeInTheDocument(); + }); +}); + +describe('without private apps enabled', () => { + const appRoot = mockAppRoot() + .withTranslations('en', 'core', { + Private_apps_upgrade_empty_state_title: 'Upgrade to unlock private apps', + No_private_apps_installed: 'No private apps installed', + }) + .wrap((children) => ( + Promise.resolve(), + orchestrator: undefined, + privateAppsEnabled: false, + }} + > + {children} + + )); + + it('should offer to upgrade to unlock private apps', () => { + render(, { wrapper: appRoot.build(), legacyRoot: true }); + + expect(screen.getByRole('heading', { name: 'Upgrade to unlock private apps' })).toBeInTheDocument(); + }); +}); diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx index b7fec778401ab..aaa2be18ee3f8 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx @@ -1,19 +1,14 @@ -import { States, StatesIcon, StatesTitle, StatesSubtitle, Box } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; +import { Box } from '@rocket.chat/fuselage'; import React from 'react'; +import { usePrivateAppsEnabled } from '../hooks/usePrivateAppsEnabled'; +import PrivateEmptyStateDefault from './PrivateEmptyStateDefault'; +import PrivateEmptyStateUpgrade from './PrivateEmptyStateUpgrade'; + const PrivateEmptyState = () => { - const t = useTranslation(); + const privateAppsEnabled = usePrivateAppsEnabled(); - return ( - - - - {t('No_private_apps_installed')} - {t('Private_apps_are_side-loaded')} - - - ); + return {privateAppsEnabled ? : }; }; export default PrivateEmptyState; diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateDefault.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateDefault.tsx new file mode 100644 index 0000000000000..0c8bb909de570 --- /dev/null +++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateDefault.tsx @@ -0,0 +1,17 @@ +import { States, StatesIcon, StatesTitle, StatesSubtitle } from '@rocket.chat/fuselage'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +const PrivateEmptyStateDefault = () => { + const { t } = useTranslation(); + + return ( + + + {t('No_private_apps_installed')} + {t('Private_apps_upgrade_empty_state_description')} + + ); +}; + +export default PrivateEmptyStateDefault; diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx new file mode 100644 index 0000000000000..e3ae7d0e31974 --- /dev/null +++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx @@ -0,0 +1,28 @@ +import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions } from '@rocket.chat/fuselage'; +import { usePermission } from '@rocket.chat/ui-contexts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import UpgradeButton from '../../admin/subscription/components/UpgradeButton'; + +const PrivateEmptyStateUpgrade = () => { + const { t } = useTranslation(); + const isAdmin = usePermission('manage-apps'); + + return ( + + + {t('Private_apps_upgrade_empty_state_title')} + {t('Private_apps_upgrade_empty_state_description')} + {isAdmin && ( + + + {t('Upgrade')} + + + )} + + ); +}; + +export default PrivateEmptyStateUpgrade; diff --git a/apps/meteor/client/views/marketplace/components/EnabledAppsCount.spec.tsx b/apps/meteor/client/views/marketplace/components/EnabledAppsCount.spec.tsx new file mode 100644 index 0000000000000..584f6e10fb8ac --- /dev/null +++ b/apps/meteor/client/views/marketplace/components/EnabledAppsCount.spec.tsx @@ -0,0 +1,62 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import EnabledAppsCount from './EnabledAppsCount'; + +describe('in private context', () => { + const context = 'private'; + + it('should work under the limit', async () => { + render(, { + wrapper: mockAppRoot() + .withTranslations('en', 'core', { + Private_Apps_Count_Enabled_one: '{{count}} private app enabled', + Private_Apps_Count_Enabled_other: '{{count}} private apps enabled', + }) + .build(), + legacyRoot: true, + }); + + expect(screen.getByText('1 private app enabled')).toBeInTheDocument(); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemin', '0'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemax', '100'); + }); + + it('should work with private apps disabled', async () => { + render(, { + wrapper: mockAppRoot() + .withTranslations('en', 'core', { + Private_Apps_Count_Enabled_one: '{{count}} private app enabled', + Private_Apps_Count_Enabled_other: '{{count}} private apps enabled', + }) + .build(), + legacyRoot: true, + }); + + expect(screen.getByText('0 private apps enabled')).toBeInTheDocument(); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemin', '0'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '100'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemax', '100'); + }); +}); + +describe.each(['explore', 'installed', 'premium', 'requested'] as const)('in %s context', (context) => { + it('should work', async () => { + render(, { + wrapper: mockAppRoot() + .withTranslations('en', 'core', { + Apps_Count_Enabled_one: '{{count}} app enabled', + Apps_Count_Enabled_other: '{{count}} apps enabled', + }) + .build(), + legacyRoot: true, + }); + + expect(screen.getByText('1 app enabled')).toBeInTheDocument(); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemin', '0'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuemax', '100'); + }); +}); diff --git a/apps/meteor/client/views/marketplace/components/EnabledAppsCount.tsx b/apps/meteor/client/views/marketplace/components/EnabledAppsCount.tsx index 9578d49961c8f..3aeac42fe6378 100644 --- a/apps/meteor/client/views/marketplace/components/EnabledAppsCount.tsx +++ b/apps/meteor/client/views/marketplace/components/EnabledAppsCount.tsx @@ -1,24 +1,36 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; -import React from 'react'; +import React, { useMemo } from 'react'; import { GenericResourceUsage } from '../../../components/GenericResourceUsage'; const EnabledAppsCount = ({ - variant, - percentage, limit, enabled, context, + tooltip, }: { - variant: 'warning' | 'danger' | 'success'; - percentage: number; limit: number; enabled: number; context: 'private' | 'explore' | 'installed' | 'premium' | 'requested'; + tooltip?: string; }): ReactElement | null => { const t = useTranslation(); + const variant = useMemo(() => { + if (enabled + 1 === limit) { + return 'warning'; + } + + if (limit === 0 || enabled >= limit) { + return 'danger'; + } + + return 'success'; + }, [enabled, limit]); + + const percentage = limit === 0 ? 100 : Math.round((enabled * 100) / limit); + return ( ); }; diff --git a/apps/meteor/client/views/marketplace/components/MarketplaceHeader.tsx b/apps/meteor/client/views/marketplace/components/MarketplaceHeader.tsx index dfc3033e9812f..07e024ef93473 100644 --- a/apps/meteor/client/views/marketplace/components/MarketplaceHeader.tsx +++ b/apps/meteor/client/views/marketplace/components/MarketplaceHeader.tsx @@ -1,36 +1,62 @@ -import { Button, ButtonGroup } from '@rocket.chat/fuselage'; -import { usePermission, useRoute, useRouteParameter, useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import { Button, ButtonGroup, Margins } from '@rocket.chat/fuselage'; +import { usePermission, useRoute, useRouteParameter, useSetModal } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; -import React, { useCallback } from 'react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { GenericResourceUsageSkeleton } from '../../../components/GenericResourceUsage'; import { PageHeader } from '../../../components/Page'; +import UpgradeButton from '../../admin/subscription/components/UpgradeButton'; import UnlimitedAppsUpsellModal from '../UnlimitedAppsUpsellModal'; import { useAppsCountQuery } from '../hooks/useAppsCountQuery'; +import { usePrivateAppsEnabled } from '../hooks/usePrivateAppsEnabled'; import EnabledAppsCount from './EnabledAppsCount'; +import PrivateAppInstallModal from './PrivateAppInstallModal/PrivateAppInstallModal'; const MarketplaceHeader = ({ title }: { title: string }): ReactElement | null => { - const t = useTranslation(); + const { t } = useTranslation(); const isAdmin = usePermission('manage-apps'); const context = (useRouteParameter('context') || 'explore') as 'private' | 'explore' | 'installed' | 'premium' | 'requested'; const route = useRoute('marketplace'); const setModal = useSetModal(); - const result = useAppsCountQuery(context); + const { isLoading, isError, isSuccess, data } = useAppsCountQuery(context); - const handleUploadButtonClick = useCallback((): void => { + const privateAppsEnabled = usePrivateAppsEnabled(); + + const handleProceed = (): void => { + setModal(null); route.push({ context, page: 'install' }); - }, [context, route]); + }; + + const handleClickPrivate = () => { + if (!privateAppsEnabled) { + setModal( setModal(null)} onProceed={handleProceed} />); + return; + } - if (result.isError) { + route.push({ context, page: 'install' }); + }; + + if (isError) { return null; } return ( + {isLoading && } + + {isSuccess && !data.hasUnlimitedApps && ( + + + + )} + - {result.isLoading && } - {result.isSuccess && !result.data.hasUnlimitedApps && } - {isAdmin && result.isSuccess && !result.data.hasUnlimitedApps && ( + {isAdmin && isSuccess && !data.hasUnlimitedApps && context !== 'private' && ( )} - {isAdmin && context === 'private' && } + + {isAdmin && context === 'private' && } + + {isAdmin && isSuccess && !privateAppsEnabled && context === 'private' && ( + + {t('Upgrade')} + + )} ); diff --git a/apps/meteor/client/views/marketplace/components/PrivateAppInstallModal/PrivateAppInstallModal.tsx b/apps/meteor/client/views/marketplace/components/PrivateAppInstallModal/PrivateAppInstallModal.tsx new file mode 100644 index 0000000000000..5205ccbfc6a2a --- /dev/null +++ b/apps/meteor/client/views/marketplace/components/PrivateAppInstallModal/PrivateAppInstallModal.tsx @@ -0,0 +1,56 @@ +import { Box, Button, Modal } from '@rocket.chat/fuselage'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useExternalLink } from '../../../../hooks/useExternalLink'; +import { useCheckoutUrl } from '../../../admin/subscription/hooks/useCheckoutUrl'; +import { PRICING_LINK } from '../../../admin/subscription/utils/links'; + +type PrivateAppInstallModalProps = { + onClose: () => void; + onProceed: () => void; +}; + +const PrivateAppInstallModal = ({ onClose, onProceed }: PrivateAppInstallModalProps) => { + const { t } = useTranslation(); + + const openExternalLink = useExternalLink(); + const manageSubscriptionUrl = useCheckoutUrl()({ target: 'private-apps-page', action: 'upgrade' }); + + const goToManageSubscriptionPage = (): void => { + openExternalLink(manageSubscriptionUrl); + onClose(); + }; + + return ( + + + + {t('Private_app_install_modal_title')} + + + + + + {t('Private_app_install_modal_content')} + {t('Upgrade_subscription_to_enable_private_apps')} + + + + + + {t('Compare_plans')} + + + + + + + + + ); +}; + +export default PrivateAppInstallModal; diff --git a/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx b/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx index ce87f0d4673a6..13d71cc2108b1 100644 --- a/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx +++ b/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx @@ -4,6 +4,7 @@ import React from 'react'; import MarkdownText from '../../../../components/MarkdownText'; import type { MarketplaceRouteContext } from '../../hooks/useAppsCountQuery'; +import { usePrivateAppsEnabled } from '../../hooks/usePrivateAppsEnabled'; type UninstallGrandfatheredAppModalProps = { context: MarketplaceRouteContext; @@ -15,6 +16,12 @@ type UninstallGrandfatheredAppModalProps = { const UninstallGrandfatheredAppModal = ({ context, limit, appName, handleUninstall, handleClose }: UninstallGrandfatheredAppModalProps) => { const t = useTranslation(); + const privateAppsEnabled = usePrivateAppsEnabled(); + + const modalContent = + context === 'private' && !privateAppsEnabled + ? t('App_will_lose_grandfathered_status_private') + : t('App_will_lose_grandfathered_status', { limit }); return ( @@ -25,7 +32,7 @@ const UninstallGrandfatheredAppModal = ({ context, limit, appName, handleUninsta - + diff --git a/apps/meteor/client/views/marketplace/hooks/useAppsCountQuery.ts b/apps/meteor/client/views/marketplace/hooks/useAppsCountQuery.ts index b23c19a2df403..712bcf9da9411 100644 --- a/apps/meteor/client/views/marketplace/hooks/useAppsCountQuery.ts +++ b/apps/meteor/client/views/marketplace/hooks/useAppsCountQuery.ts @@ -2,15 +2,6 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQueryClient, useQuery } from '@tanstack/react-query'; import { useCallback } from 'react'; -type Variant = 'success' | 'warning' | 'danger'; - -const getProgressBarValues = (numberOfEnabledApps: number, enabledAppsLimit: number): { variant: Variant; percentage: number } => ({ - variant: 'success', - ...(numberOfEnabledApps + 1 === enabledAppsLimit && { variant: 'warning' }), - ...(numberOfEnabledApps >= enabledAppsLimit && { variant: 'danger' }), - percentage: Math.round((numberOfEnabledApps / enabledAppsLimit) * 100), -}); - export type MarketplaceRouteContext = 'private' | 'explore' | 'installed' | 'premium' | 'requested' | 'details'; export function isMarketplaceRouteContext(context: string): context is MarketplaceRouteContext { @@ -28,11 +19,12 @@ export const useAppsCountQuery = (context: MarketplaceRouteContext) => { const numberOfEnabledApps = context === 'private' ? data.totalPrivateEnabled : data.totalMarketplaceEnabled; const enabledAppsLimit = context === 'private' ? data.maxPrivateApps : data.maxMarketplaceApps; const hasUnlimitedApps = enabledAppsLimit === -1; + return { hasUnlimitedApps, enabled: numberOfEnabledApps, limit: enabledAppsLimit, - ...getProgressBarValues(numberOfEnabledApps, enabledAppsLimit), + // tooltip, }; }, { staleTime: 10_000 }, diff --git a/apps/meteor/client/views/marketplace/hooks/usePrivateAppsEnabled.ts b/apps/meteor/client/views/marketplace/hooks/usePrivateAppsEnabled.ts new file mode 100644 index 0000000000000..49050960b4df9 --- /dev/null +++ b/apps/meteor/client/views/marketplace/hooks/usePrivateAppsEnabled.ts @@ -0,0 +1,9 @@ +import { useContext } from 'react'; + +import { AppsContext } from '../../../contexts/AppsContext'; + +export const usePrivateAppsEnabled = () => { + const { privateAppsEnabled } = useContext(AppsContext); + + return privateAppsEnabled; +}; diff --git a/apps/meteor/tests/mocks/client/marketplace.tsx b/apps/meteor/tests/mocks/client/marketplace.tsx index f0147509cb12d..3cad0aad502b4 100644 --- a/apps/meteor/tests/mocks/client/marketplace.tsx +++ b/apps/meteor/tests/mocks/client/marketplace.tsx @@ -62,6 +62,7 @@ export const mockedAppsContext = (children: ReactNode) => ( }, reload: () => Promise.resolve(), orchestrator: mockAppsOrchestrator(), + privateAppsEnabled: false, }} > {children} diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index f7eb8d638420b..eae11406ed5e1 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -561,6 +561,9 @@ "Apps_Count_Enabled_other": "{{count}} apps enabled", "Private_Apps_Count_Enabled_one": "{{count}} private app enabled", "Private_Apps_Count_Enabled_other": "{{count}} private apps enabled", + "Private_apps_premium_message": "Private apps can only be enabled in premium plans", + "Private_apps_upgrade_empty_state_title": "Upgrade to unlock private apps", + "Private_apps_upgrade_empty_state_description": "Tailor Rocket.Chat according to your needs with private apps.", "Apps_Count_Enabled_tooltip": "Community workspaces can enable up to {{number}} {{context}} apps", "Apps_disabled_when_Premium_trial_ended": "Apps disabled when Premium plan trial ended", "Apps_disabled_when_Premium_trial_ended_description": "Workspaces on Community can have up to 5 marketplace apps and 3 private apps enabled. Ask your workspace admin to reenable apps.", @@ -2575,7 +2578,7 @@ "GoogleTagManager_id": "Google Tag Manager Id", "Got_it": "Got it", "Government": "Government", - "Grandfathered_app": "Grandfathered app - counts towards app limit but limit is not applied to this app", + "Grandfathered_app": "Grandfathered app – this app is exempt from the app limit policy", "Graphql_CORS": "GraphQL CORS", "Graphql_Enabled": "GraphQL Enabled", "Graphql_Subscription_Port": "GraphQL Subscription Port", @@ -4313,6 +4316,8 @@ "private": "private", "Private_channels": "Private channels", "Private_Apps": "Private Apps", + "Private_app_install_modal_title": "Upload disabled private app", + "Private_app_install_modal_content": "Community workspaces cannot enable private apps. You can upload this app but it will be disabled.", "Private_Channel": "Private Channel", "Private_Channels": "Private channels", "Private_Chats": "Private Chats", @@ -6454,7 +6459,8 @@ "cloud.RegisterWorkspace_Setup_Terms_Privacy": "I agree with <1>Terms and Conditions and <3>Privacy Policy", "Larger_amounts_of_active_connections": "For larger amounts of active connections you can consider our <1>multiple instance solutions.", "Uninstall_grandfathered_app": "Uninstall {{appName}}?", - "App_will_lose_grandfathered_status": "**This {{context}} app will lose its grandfathered status.** \n \nWorkspaces on Community can have up to {{limit}} {{context}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.", + "App_will_lose_grandfathered_status": "**This app will lose its grandfathered status.** \n \nWorkspaces on Community can have up to {{limit}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.", + "App_will_lose_grandfathered_status_private": "**This app will lose its grandfathered status.** \n \nBecause Community workspaces cannot enable private apps, this workspace will require a premium plan in order to enable this app again in future.", "All_rooms": "All rooms", "All_visible": "All visible", "all": "all", @@ -6493,6 +6499,7 @@ "ActiveSessions_available": "sessions available", "Monthly_active_contacts": "Monthly active contacts", "Upgrade": "Upgrade", + "Upgrade_subscription_to_enable_private_apps": "Upgrade subscription to enable private apps.", "Seats": "Seats", "Marketplace_apps": "Marketplace apps", "Private_apps": "Private apps", @@ -6528,7 +6535,8 @@ "MAC_InfoText": "(MAC) the number of unique omnichannel contacts engaged with during the billing month.", "CountMAC_InfoText": "(MAC) the number of unique omnichannel contacts engaged with during the calendar month.", "ActiveSessions_InfoText": "Total concurrent connections. A single user can be connected multiple times. User presence service is disabled at 200 or more to prevent performance issues.", - "Apps_InfoText": "Community allows up to 3 private apps and 5 marketplace apps to be enabled", + "Apps_InfoText": "Community allows up to {{privateAppsMaxCount}} private apps and {{marketplaceAppsMaxCount}} marketplace apps to be enabled", + "Apps_InfoText_limited": "Community workspaces can enable up to {{marketplaceAppsMaxCount}} marketplace apps. Private apps can only be enabled in <1>premium plans.", "Remove_RocketChat_Watermark_InfoText": "Watermark is automatically removed when a paid license is active.", "Remove_RocketChat_Watermark": "Remove Rocket.Chat watermark", "High_scalabaility": "High scalabaility", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index a486580cf821e..c66c07dda80c4 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -5066,7 +5066,7 @@ "MAC_InfoText": "Contactos Activos Mensuales (MAC). El número de contactos únicos de Omnichannel con quienes se interactuó durante un mes de facturación", "CountMAC_InfoText": "Contactos Activos Mensuales (MAC). El número de contactos únicos de Omnichannel con quienes se interactuó durante un mes calendario", "ActiveSessions_InfoText": "Total de conexiones concurrentes. Un usuario puede estar conectado varias veces. El servicio de presencia de usuario se deshabilita cuando el total llega a 200 conexiones para prevenir problemas de rendimiento", - "Apps_InfoText": "Comunidad permite hasta 3 aplicaciones privadas y 5 aplicaciones de la tienda ser habilitadas", + "Apps_InfoText": "Comunidad permite hasta {{privateAppsMaxCount}} aplicaciones privadas y {{marketplaceAppsMaxCount}} aplicaciones de la tienda ser habilitadas", "Remove_RocketChat_Watermark_InfoText": "La marca de agua es removida automticamente cuando una licencia de paga es activada", "Remove_RocketChat_Watermark": "Remover marca de agua de Rocket.Chat", "High_scalabaility": "Alta escalabilidad", @@ -5085,4 +5085,4 @@ "Unlimited_seats": "Puestos ilimitados", "Unlimited_MACs": "Contactos Activos por Mes (MAC) ilimitados", "Unlimited_seats_MACs": "Puestos y Contactos Activos por Mes (MAC) ilimitados" -} \ No newline at end of file +} diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index ebeafeb0f1d18..40387d02e4b14 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -2319,7 +2319,6 @@ "GoogleTagManager_id": "Google Tag Manager -tunnus", "Got_it": "Selvä", "Government": "Valtionjohto", - "Grandfathered_app": "Aikaisemmin käytössä ollut sovellus - lasketaan mukaan sovellusrajoitukseen, mutta rajoitusta ei sovelleta tähän sovellukseen", "Graphql_CORS": "GraphQL CORS", "Graphql_Enabled": "GraphQL käytössä", "Graphql_Subscription_Port": "GraphQL-tilausportti", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index d75e30c2c9229..2f8c20ae63521 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -2418,7 +2418,6 @@ "GoogleTagManager_id": "Google टैग प्रबंधक आईडी", "Got_it": "समझ गया", "Government": "सरकार", - "Grandfathered_app": "दादाजी ऐप - ऐप सीमा में गिना जाता है लेकिन इस ऐप पर सीमा लागू नहीं होती है", "Graphql_CORS": "ग्राफक्यूएल कॉर्स", "Graphql_Enabled": "ग्राफक्यूएल सक्षम", "Graphql_Subscription_Port": "ग्राफक्यूएल सदस्यता पोर्ट", @@ -6111,7 +6110,7 @@ "MAC_InfoText": "(मैक) बिलिंग माह के दौरान जुड़े अद्वितीय सर्वचैनल संपर्कों की संख्या।", "CountMAC_InfoText": "(मैक) कैलेंडर माह के दौरान जुड़े अद्वितीय ओमनीचैनल संपर्कों की संख्या।", "ActiveSessions_InfoText": "कुल समवर्ती कनेक्शन. एक ही यूजर को कई बार कनेक्ट किया जा सकता है। प्रदर्शन समस्याओं को रोकने के लिए उपयोगकर्ता उपस्थिति सेवा 200 या उससे अधिक पर अक्षम है।", - "Apps_InfoText": "समुदाय 3 निजी ऐप्स और 5 मार्केटप्लेस ऐप्स को सक्षम करने की अनुमति देता है", + "Apps_InfoText": "समुदाय {{privateAppsMaxCount}} निजी ऐप्स और {{marketplaceAppsMaxCount}} मार्केटप्लेस ऐप्स को सक्षम करने की अनुमति देता है", "Remove_RocketChat_Watermark_InfoText": "सशुल्क लाइसेंस सक्रिय होने पर वॉटरमार्क स्वचालित रूप से हटा दिया जाता है।", "Remove_RocketChat_Watermark": "रॉकेट.चैट वॉटरमार्क हटाएँ", "High_scalabaility": "उच्च मापनीयता", diff --git a/packages/i18n/src/locales/pl.i18n.json b/packages/i18n/src/locales/pl.i18n.json index bb0feccfd6383..08dfbd471e862 100644 --- a/packages/i18n/src/locales/pl.i18n.json +++ b/packages/i18n/src/locales/pl.i18n.json @@ -5420,10 +5420,10 @@ "UpgradeToGetMore_accessibility-certification_Body": "Zgodność ze standardami WCAG i BITV dzięki programowi dostępności Rocket.Chat.", "UpgradeToGetMore_engagement-dashboard_Title": "Analityka", "UpgradeToGetMore_auditing_Title": "Audyt wiadomości", - "Apps_InfoText": "Wersja Community umożliwia włączenie do 3 aplikacji prywatnych i 5 aplikacji marketplace", + "Apps_InfoText": "Wersja Community umożliwia włączenie do {{privateAppsMaxCount}} aplikacji prywatnych i {{marketplaceAppsMaxCount}} aplikacji marketplace", "Anyone_can_react_to_messages": "Każdy może reagować na wiadomości", "Anyone_can_access": "Każdy może uzyskać dostęp", "Broadcast_hint_enabled": "Tylko właściciele {{roomType}} mogą pisać nowe wiadomości, ale każdy może odpowiadać w wątku", "Anyone_can_send_new_messages": "Każdy może wysyłać nowe wiadomości", "Select_messages_to_hide": "Wybierz wiadomości do ukrycia" -} \ No newline at end of file +} diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index 42caa2802c108..774dde2b4f17c 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -2323,7 +2323,6 @@ "GoogleTagManager_id": "Google Tag manager id", "Got_it": "Uppfattat", "Government": "Regering", - "Grandfathered_app": "Gamla appar - räknas mot appgränsen men gränsen tillämpas inte på denna app", "Graphql_CORS": "GraphQL CORS", "Graphql_Enabled": "GraphQL aktiverat", "Graphql_Subscription_Port": "GraphQL-prenumerationsport", From b015c79976077683be32e08f58fc7212ee71be47 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:42:10 -0300 Subject: [PATCH 40/85] =?UTF-8?q?Revert=20"fix:=20Private=20apps=20restric?= =?UTF-8?q?tions=20are=20not=20applied=20on=20license=20removal=20(#3?= =?UTF-8?q?=E2=80=A6"=20(#33409)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 6c43d22c8a2c9edea65d8b23f05884ccbdf9708c. --- .changeset/eighty-items-behave.md | 5 ----- apps/meteor/ee/server/startup/apps/trialExpiration.ts | 9 +++------ 2 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 .changeset/eighty-items-behave.md diff --git a/.changeset/eighty-items-behave.md b/.changeset/eighty-items-behave.md deleted file mode 100644 index db8bc26e564f9..0000000000000 --- a/.changeset/eighty-items-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Adds missing readjustment to private apps restrictions on license removal diff --git a/apps/meteor/ee/server/startup/apps/trialExpiration.ts b/apps/meteor/ee/server/startup/apps/trialExpiration.ts index 46ccac7ee810c..e6bb9e47c7491 100644 --- a/apps/meteor/ee/server/startup/apps/trialExpiration.ts +++ b/apps/meteor/ee/server/startup/apps/trialExpiration.ts @@ -1,12 +1,9 @@ import { License } from '@rocket.chat/license'; import { Meteor } from 'meteor/meteor'; -Meteor.startup(async () => { - const { Apps } = await import('../../apps'); - License.onInvalidateLicense(() => { - void Apps.disableApps(); - }); - License.onRemoveLicense(() => { +Meteor.startup(() => { + License.onInvalidateLicense(async () => { + const { Apps } = await import('../../apps'); void Apps.disableApps(); }); }); From 4f51cbe9ef772d6081e5ed51110dfc039f18180f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 2 Oct 2024 00:09:30 +0530 Subject: [PATCH 41/85] chore!: remove deprecated livechat:searchAgent method (#33373) Signed-off-by: Abhinav Kumar Co-authored-by: Guilherme Gazzo --- .changeset/forty-needles-sit.md | 5 +++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/searchAgent.ts | 45 ------------------- 3 files changed, 5 insertions(+), 46 deletions(-) create mode 100644 .changeset/forty-needles-sit.md delete mode 100644 apps/meteor/app/livechat/server/methods/searchAgent.ts diff --git a/.changeset/forty-needles-sit.md b/.changeset/forty-needles-sit.md new file mode 100644 index 0000000000000..f3a0e160c9fa6 --- /dev/null +++ b/.changeset/forty-needles-sit.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +This adjustment removes the deprecated method `livechat:searchAgent`. Moving forward, use `livechat/users/agent/:_id` endpoint. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 37a7705136ec3..7716abcf80dec 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -46,7 +46,6 @@ import './methods/saveInfo'; import './methods/saveIntegration'; import './methods/saveSurveyFeedback'; import './methods/saveTrigger'; -import './methods/searchAgent'; import './methods/sendMessageLivechat'; import './methods/sendFileLivechatMessage'; import './methods/sendOfflineMessage'; diff --git a/apps/meteor/app/livechat/server/methods/searchAgent.ts b/apps/meteor/app/livechat/server/methods/searchAgent.ts deleted file mode 100644 index 932eb51e89d66..0000000000000 --- a/apps/meteor/app/livechat/server/methods/searchAgent.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Users } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:searchAgent'(username: string): { _id: string; username?: string } | undefined; - } -} - -Meteor.methods({ - async 'livechat:searchAgent'(username) { - methodDeprecationLogger.method('livechat:searchAgent', '7.0.0'); - - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:searchAgent', - }); - } - - if (!username || typeof username.valueOf() !== 'string') { - throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { - method: 'livechat:searchAgent', - }); - } - - const user = await Users.findOneByUsernameIgnoringCase>(username, { - projection: { _id: 1, username: 1 }, - }); - - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'livechat:searchAgent', - }); - } - - return user; - }, -}); From 3c3ea47fcbce37f5056e4287c73503b8770375a1 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 2 Oct 2024 00:10:30 +0530 Subject: [PATCH 42/85] chore!: remove deprecated livechat:loginByToken method (#33391) Signed-off-by: Abhinav Kumar Co-authored-by: Guilherme Gazzo --- .changeset/grumpy-weeks-appear.md | 5 ++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/loginByToken.ts | 28 -------- .../api/livechat/methods/loginByToken.ts | 66 ------------------- 4 files changed, 5 insertions(+), 95 deletions(-) create mode 100644 .changeset/grumpy-weeks-appear.md delete mode 100644 apps/meteor/app/livechat/server/methods/loginByToken.ts delete mode 100644 apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts diff --git a/.changeset/grumpy-weeks-appear.md b/.changeset/grumpy-weeks-appear.md new file mode 100644 index 0000000000000..3253870b84e41 --- /dev/null +++ b/.changeset/grumpy-weeks-appear.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removed deprecated method `livechat:loginByToken`. Moving forward, use the endpoint `livechat/visitor/:token`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 7716abcf80dec..0405d6fb6609f 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -27,7 +27,6 @@ import './methods/getAnalyticsChartData'; import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; import './methods/getRoutingConfig'; -import './methods/loginByToken'; import './methods/pageVisited'; import './methods/registerGuest'; import './methods/removeAgent'; diff --git a/apps/meteor/app/livechat/server/methods/loginByToken.ts b/apps/meteor/app/livechat/server/methods/loginByToken.ts deleted file mode 100644 index 3b82413e038af..0000000000000 --- a/apps/meteor/app/livechat/server/methods/loginByToken.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatVisitors } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:loginByToken'(token: string): { _id: string } | undefined; - } -} - -Meteor.methods({ - async 'livechat:loginByToken'(token) { - methodDeprecationLogger.method('livechat:loginByToken', '7.0.0'); - check(token, String); - const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); - - if (!visitor) { - return; - } - - return { - _id: visitor._id, - }; - }, -}); diff --git a/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts b/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts deleted file mode 100644 index be6fee9144d8a..0000000000000 --- a/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { ILivechatAgent, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { expect } from 'chai'; -import { before, describe, it, after } from 'mocha'; -import type { Response } from 'supertest'; - -import { getCredentials, request, methodCallAnon, credentials } from '../../../../data/api-data'; -import { - closeOmnichannelRoom, - createAgent, - makeAgentAvailable, - sendMessage, - startANewLivechatRoomAndTakeIt, -} from '../../../../data/livechat/rooms'; -import { removeAgent } from '../../../../data/livechat/users'; -import { updateSetting } from '../../../../data/permissions.helper'; -import { adminUsername } from '../../../../data/user'; - -describe('livechat:loginByTokens', function () { - let visitor: ILivechatVisitor; - let agent: ILivechatAgent; - let room: IOmnichannelRoom; - - this.retries(0); - - before((done) => getCredentials(done)); - - before(async () => { - await updateSetting('Livechat_enabled', true); - agent = await createAgent(adminUsername); - await makeAgentAvailable(credentials); - }); - - before('open livechat room', async () => { - const data = await startANewLivechatRoomAndTakeIt(); - visitor = data.visitor; - room = data.room; - await sendMessage(data.room._id, 'Hello from visitor!', visitor.token); - }); - - after('remove agent and close room', async () => { - await closeOmnichannelRoom(room._id); - await removeAgent(agent._id); - }); - - describe('loginByTokens', async () => { - it('prevent getting arbitrary visitor id using regex in params', async () => { - await request - .post(methodCallAnon('livechat:loginByToken')) - .send({ - message: JSON.stringify({ - msg: 'method', - id: 'id1', - method: 'livechat:loginByToken', - params: [{ $regex: `.*` }], - }), - }) - .expect(200) - .expect((res: Response) => { - expect(res.body).to.have.property('success', true); - const parsedBody = JSON.parse(res.body.message); - expect(parsedBody).to.have.property('error'); - expect(parsedBody).to.not.have.property('result'); - }); - }); - }); -}); From a96a2ae9d1c9e1d7160203670a03957e642e382a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 2 Oct 2024 00:12:18 +0530 Subject: [PATCH 43/85] chore!: remove livechat:getCustomFields method (#33371) Signed-off-by: Abhinav Kumar Co-authored-by: Guilherme Gazzo --- .changeset/tender-readers-run.md | 5 +++++ apps/meteor/app/livechat/server/index.ts | 1 - .../server/methods/getCustomFields.ts | 20 ------------------- 3 files changed, 5 insertions(+), 21 deletions(-) create mode 100644 .changeset/tender-readers-run.md delete mode 100644 apps/meteor/app/livechat/server/methods/getCustomFields.ts diff --git a/.changeset/tender-readers-run.md b/.changeset/tender-readers-run.md new file mode 100644 index 0000000000000..d8bcfaf3e1a6d --- /dev/null +++ b/.changeset/tender-readers-run.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +This adjustment removes deprecated `livechat:getCustomFields` method. Moving forward use the `livechat/custom-fields` endpoint. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 0405d6fb6609f..38462ef56c844 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -20,7 +20,6 @@ import './hooks/afterSaveOmnichannelMessage'; import './methods/changeLivechatStatus'; import './methods/closeRoom'; import './methods/discardTranscript'; -import './methods/getCustomFields'; import './methods/getAgentData'; import './methods/getAgentOverviewData'; import './methods/getAnalyticsChartData'; diff --git a/apps/meteor/app/livechat/server/methods/getCustomFields.ts b/apps/meteor/app/livechat/server/methods/getCustomFields.ts deleted file mode 100644 index 36dca08f08596..0000000000000 --- a/apps/meteor/app/livechat/server/methods/getCustomFields.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ILivechatCustomField } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatCustomField } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:getCustomFields'(): ILivechatCustomField[]; - } -} - -Meteor.methods({ - async 'livechat:getCustomFields'() { - methodDeprecationLogger.method('livechat:getCustomFields', '7.0.0'); - return LivechatCustomField.find().toArray(); - }, -}); From 1cda9916d9879ffb6df04968d7ee600aa3afdccb Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:22:08 -0300 Subject: [PATCH 44/85] fix: Upgrade option is being presented to Premium workspaces when uploading private apps (#33416) --- apps/meteor/client/providers/AppsProvider/AppsProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx b/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx index b2145a07fb7ad..df96b0f2f01fe 100644 --- a/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx +++ b/apps/meteor/client/providers/AppsProvider/AppsProvider.tsx @@ -116,7 +116,7 @@ const AppsProvider = ({ children }: AppsProviderProps) => { await Promise.all([queryClient.invalidateQueries(['marketplace'])]); }, orchestrator: AppClientOrchestratorInstance, - privateAppsEnabled: (limits?.privateApps?.max ?? 0) > 0, + privateAppsEnabled: (limits?.privateApps?.max ?? 0) !== 0, }} /> ); From 429225a6071db3b1b6ca8c689e66ff992157fc35 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 2 Oct 2024 10:19:21 -0600 Subject: [PATCH 45/85] refactor!: Room's Key ID generation (#33329) Co-authored-by: Hugo Costa Co-authored-by: Guilherme Gazzo --- .changeset/spicy-eggs-march.md | 8 ++ apps/meteor/app/e2e/client/helper.js | 7 ++ .../app/e2e/client/rocketchat.e2e.room.js | 16 ++-- apps/meteor/app/e2e/client/rocketchat.e2e.ts | 80 ++++++++++--------- 4 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 .changeset/spicy-eggs-march.md diff --git a/.changeset/spicy-eggs-march.md b/.changeset/spicy-eggs-march.md new file mode 100644 index 0000000000000..a88098afd96ed --- /dev/null +++ b/.changeset/spicy-eggs-march.md @@ -0,0 +1,8 @@ +--- +"@rocket.chat/meteor": major +--- + +Randomizes `e2eKeyId` generation instead of derive it from encoded key. Previously, we used the stringified & encoded version of the key to extract a keyID, however this generated the same keyID for all rooms. As we didn't use this keyID, and rooms didn't have the capability of having multiple keys, this was harmless. +This PR introduces a new way of generating that identifier, making it random and unique, so multiple room keys can be used on the same room as long as the keyID is different. + +NOTE: new E2EE rooms created _after_ this PR is merged will not be compatible with older versions of Rocket.Chat. Old rooms created before this update will continue to be compatible. diff --git a/apps/meteor/app/e2e/client/helper.js b/apps/meteor/app/e2e/client/helper.js index 49b157c5ccf45..ddf49b262b918 100644 --- a/apps/meteor/app/e2e/client/helper.js +++ b/apps/meteor/app/e2e/client/helper.js @@ -146,3 +146,10 @@ export async function generateMnemonicPhrase(n, sep = ' ') { } return result.join(sep); } + +export async function createSha256Hash(data) { + const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data)); + return Array.from(new Uint8Array(hash)) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js b/apps/meteor/app/e2e/client/rocketchat.e2e.room.js index fe61156240a87..1b2357067028b 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.js @@ -24,6 +24,7 @@ import { readFileAsArrayBuffer, encryptAESCTR, generateAESCTRKey, + createSha256Hash, } from './helper'; import { log, logError } from './logger'; import { e2e } from './rocketchat.e2e'; @@ -67,12 +68,13 @@ export class E2ERoom extends Emitter { [PAUSED] = undefined; - constructor(userId, roomId, t) { + constructor(userId, room) { super(); this.userId = userId; - this.roomId = roomId; - this.typeOfRoom = t; + this.roomId = room._id; + this.typeOfRoom = room.t; + this.roomKeyId = room.e2eKeyId; this.once(E2ERoomState.READY, () => this.decryptPendingMessages()); this.once(E2ERoomState.READY, () => this.decryptSubscription()); @@ -280,7 +282,11 @@ export class E2ERoom extends Emitter { return false; } - this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12); + // When a new e2e room is created, it will be initialized without an e2e key id + // This will prevent new rooms from storing `undefined` as the keyid + if (!this.keyID) { + this.keyID = this.roomKeyId || (await createSha256Hash(this.sessionKeyExportedString)).slice(0, 12); + } // Import session key for use. try { @@ -308,7 +314,7 @@ export class E2ERoom extends Emitter { try { const sessionKeyExported = await exportJWKKey(this.groupSessionKey); this.sessionKeyExportedString = JSON.stringify(sessionKeyExported); - this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12); + this.keyID = (await createSha256Hash(this.sessionKeyExportedString)).slice(0, 12); await sdk.call('e2e.setRoomKeyID', this.roomId, this.keyID); await this.encryptKeyForOtherParticipants(); diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 50224cb89dbbe..5485fe31fc15d 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -145,52 +145,54 @@ class E2E extends Emitter { this.log('observing subscriptions'); } - observeSubscriptions() { - this.observable?.stop(); + async onSubscriptionChanged(sub: ISubscription) { + this.log('Subscription changed', sub); + if (!sub.encrypted && !sub.E2EKey) { + this.removeInstanceByRoomId(sub.rid); + return; + } - this.observable = Subscriptions.find().observe({ - changed: (sub: ISubscription) => { - setTimeout(async () => { - this.log('Subscription changed', sub); - if (!sub.encrypted && !sub.E2EKey) { - this.removeInstanceByRoomId(sub.rid); - return; - } + const e2eRoom = await this.getInstanceByRoomId(sub.rid); + if (!e2eRoom) { + return; + } - const e2eRoom = await this.getInstanceByRoomId(sub.rid); - if (!e2eRoom) { - return; - } + if (sub.E2ESuggestedKey) { + if (await e2eRoom.importGroupKey(sub.E2ESuggestedKey)) { + await this.acceptSuggestedKey(sub.rid); + e2eRoom.keyReceived(); + } else { + console.warn('Invalid E2ESuggestedKey, rejecting', sub.E2ESuggestedKey); + await this.rejectSuggestedKey(sub.rid); + } + } - if (sub.E2ESuggestedKey) { - if (await e2eRoom.importGroupKey(sub.E2ESuggestedKey)) { - await this.acceptSuggestedKey(sub.rid); - e2eRoom.keyReceived(); - } else { - console.warn('Invalid E2ESuggestedKey, rejecting', sub.E2ESuggestedKey); - await this.rejectSuggestedKey(sub.rid); - } - } + sub.encrypted ? e2eRoom.resume() : e2eRoom.pause(); - sub.encrypted ? e2eRoom.resume() : e2eRoom.pause(); + // Cover private groups and direct messages + if (!e2eRoom.isSupportedRoomType(sub.t)) { + e2eRoom.disable(); + return; + } - // Cover private groups and direct messages - if (!e2eRoom.isSupportedRoomType(sub.t)) { - e2eRoom.disable(); - return; - } + if (sub.E2EKey && e2eRoom.isWaitingKeys()) { + e2eRoom.keyReceived(); + return; + } - if (sub.E2EKey && e2eRoom.isWaitingKeys()) { - e2eRoom.keyReceived(); - return; - } + if (!e2eRoom.isReady()) { + return; + } - if (!e2eRoom.isReady()) { - return; - } + await e2eRoom.decryptSubscription(); + } - await e2eRoom.decryptSubscription(); - }, 0); + observeSubscriptions() { + this.observable?.stop(); + + this.observable = Subscriptions.find().observe({ + changed: (sub: ISubscription) => { + setTimeout(() => this.onSubscriptionChanged(sub), 0); }, added: (sub: ISubscription) => { setTimeout(async () => { @@ -263,7 +265,7 @@ class E2E extends Emitter { } if (!this.instancesByRoomId[rid]) { - this.instancesByRoomId[rid] = new E2ERoom(Meteor.userId(), rid, room.t); + this.instancesByRoomId[rid] = new E2ERoom(Meteor.userId(), room); } return this.instancesByRoomId[rid]; From 1efc7e0321465da1c503c9c1093ce8152ee26c51 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:25:56 -0300 Subject: [PATCH 46/85] chore!: Update private apps cap on Community Edition (#33399) * chore!: update private apps cap on CE --------- Co-authored-by: Tasso --- .../tabs/AppStatus/AppStatus.tsx | 2 +- .../views/marketplace/AppsList/AppRow.tsx | 1 + .../AppInstallModal/AppInstallModal.tsx | 2 +- .../views/marketplace/hooks/useAppMenu.tsx | 2 +- .../tests/e2e/apps/apps-contextualbar.spec.ts | 2 + apps/meteor/tests/e2e/apps/apps-modal.spec.ts | 2 + .../e2e/apps/private-apps-upload.spec.ts | 70 ++++++++++++++++++ .../tests/e2e/channel-management.spec.ts | 2 + apps/meteor/tests/e2e/e2e-encryption.spec.ts | 4 +- .../e2e/fixtures/files/test-app_0.0.1.zip | Bin 0 -> 19886 bytes apps/meteor/tests/e2e/page-objects/index.ts | 1 + .../tests/e2e/page-objects/marketplace.ts | 45 +++++++++++ .../meteor/tests/e2e/video-conference.spec.ts | 2 + .../tests/end-to-end/apps/apps-uninstall.ts | 3 +- .../tests/end-to-end/apps/installation.ts | 27 ++++++- .../end-to-end/apps/send-messages-as-user.ts | 3 +- .../tests/end-to-end/apps/send-messages.ts | 3 +- .../apps/slash-command-test-simple.ts | 3 +- .../apps/slash-command-test-with-arguments.ts | 3 +- .../end-to-end/apps/video-conferences.ts | 3 +- .../__tests__/DefaultRestrictions.spec.ts | 4 +- .../license/src/getLicenseLimit.spec.ts | 2 +- ee/packages/license/src/license.spec.ts | 2 +- .../src/validation/validateDefaultLimits.ts | 2 +- 24 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts create mode 100644 apps/meteor/tests/e2e/fixtures/files/test-app_0.0.1.zip create mode 100644 apps/meteor/tests/e2e/page-objects/marketplace.ts diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx index db46d87c18d80..643dfbd0215b1 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx @@ -151,7 +151,7 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro {statuses?.map((status, index) => ( - + {handleAppRequestsNumber(status)} {t(`${status.label}` as TranslationKey)} diff --git a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx index 33e54edd90bbb..338828e3f47d5 100644 --- a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx +++ b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx @@ -50,6 +50,7 @@ const AppRow = ({ className, ...props }: App & { className?: string }): ReactEle return (
- {getTitle()} + {getTitle()} diff --git a/apps/meteor/client/views/marketplace/hooks/useAppMenu.tsx b/apps/meteor/client/views/marketplace/hooks/useAppMenu.tsx index bd2071fe2d82d..7b06d2214be91 100644 --- a/apps/meteor/client/views/marketplace/hooks/useAppMenu.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useAppMenu.tsx @@ -340,7 +340,7 @@ export const useAppMenu = (app: App, isAppDetailsPage: boolean) => { const doesItReachedTheLimit = !app.migrated && !appCountQuery?.data?.hasUnlimitedApps && - !!appCountQuery?.data?.enabled && + appCountQuery?.data?.enabled !== undefined && appCountQuery?.data?.enabled >= appCountQuery?.data?.limit; const installedAppOptions = [ diff --git a/apps/meteor/tests/e2e/apps/apps-contextualbar.spec.ts b/apps/meteor/tests/e2e/apps/apps-contextualbar.spec.ts index 4753d5167083f..7ee6f8696afda 100644 --- a/apps/meteor/tests/e2e/apps/apps-contextualbar.spec.ts +++ b/apps/meteor/tests/e2e/apps/apps-contextualbar.spec.ts @@ -1,5 +1,6 @@ import type { Page } from '@playwright/test'; +import { IS_EE } from '../config/constants'; import { Users } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; import { expect, test } from '../utils/test'; @@ -7,6 +8,7 @@ import { expect, test } from '../utils/test'; test.use({ storageState: Users.user1.state }); test.describe.serial('Apps > ContextualBar', () => { + test.skip(!IS_EE, 'Premium Only'); let poHomeChannel: HomeChannel; let page: Page; diff --git a/apps/meteor/tests/e2e/apps/apps-modal.spec.ts b/apps/meteor/tests/e2e/apps/apps-modal.spec.ts index a74a439183d16..638096a66ad89 100644 --- a/apps/meteor/tests/e2e/apps/apps-modal.spec.ts +++ b/apps/meteor/tests/e2e/apps/apps-modal.spec.ts @@ -1,5 +1,6 @@ import type { Page } from '@playwright/test'; +import { IS_EE } from '../config/constants'; import { Users } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; import { Modal } from '../page-objects/modal'; @@ -8,6 +9,7 @@ import { expect, test } from '../utils/test'; test.use({ storageState: Users.user1.state }); test.describe.serial('Apps > Modal', () => { + test.skip(!IS_EE, 'Premium Only'); let poHomeChannel: HomeChannel; let poModal: Modal; diff --git a/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts b/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts new file mode 100644 index 0000000000000..05540dfc011fb --- /dev/null +++ b/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts @@ -0,0 +1,70 @@ +import { IS_EE } from '../config/constants'; +import { Users } from '../fixtures/userStates'; +import { Marketplace } from '../page-objects'; +import { expect, test } from '../utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe.serial('Private apps upload', () => { + let poMarketplace: Marketplace; + + test.beforeEach(async ({ page }) => { + poMarketplace = new Marketplace(page); + + await page.goto('/marketplace/private'); + }); + + test.describe('Premium', () => { + test.skip(!IS_EE, 'Premium Only'); + + test('expect to allow admin to upload a private app in EE, which should be enabled by default', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + + await poMarketplace.btnUploadPrivateApp.click(); + await expect(poMarketplace.btnInstallPrivateApp).toBeDisabled(); + + await poMarketplace.btnUploadPrivateAppFile.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles('./tests/e2e/fixtures/files/test-app_0.0.1.zip'); + + await expect(poMarketplace.btnInstallPrivateApp).toBeEnabled(); + await poMarketplace.btnInstallPrivateApp.click(); + await page.getByRole('button', { name: 'Agree' }).click(); + await expect(poMarketplace.appStatusTag).toHaveText('Enabled'); + }); + }); + + test.describe('Community Edition', () => { + test.skip(IS_EE, 'CE Only'); + + test('expect to allow admin to upload a private app in CE, but it should be disabled by default', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + + await poMarketplace.btnUploadPrivateApp.click(); + await expect(poMarketplace.btnConfirmAppUploadModal).toBeEnabled(); + await poMarketplace.btnConfirmAppUploadModal.click(); + + await expect(poMarketplace.btnInstallPrivateApp).toBeDisabled(); + await poMarketplace.btnUploadPrivateAppFile.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles('./tests/e2e/fixtures/files/test-app_0.0.1.zip'); + + await expect(poMarketplace.btnInstallPrivateApp).toBeEnabled(); + await poMarketplace.btnInstallPrivateApp.click(); + + await expect(poMarketplace.confirmAppUploadModalTitle).toHaveText('Private apps limit reached'); + await expect(poMarketplace.btnConfirmAppUploadModal).toBeEnabled(); + await poMarketplace.btnConfirmAppUploadModal.click(); + + await page.getByRole('button', { name: 'Agree' }).click(); + await expect(poMarketplace.appStatusTag).toHaveText('Disabled'); + }); + + test('expect not to allow enabling a recently installed private app in CE', async () => { + await poMarketplace.lastAppRow.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Disabled'); + await poMarketplace.appMenu.click(); + await expect(poMarketplace.btnEnableApp).toBeDisabled(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts index 15a0ef13eff20..84f74067db0eb 100644 --- a/apps/meteor/tests/e2e/channel-management.spec.ts +++ b/apps/meteor/tests/e2e/channel-management.spec.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker'; import type { Page } from '@playwright/test'; +import { IS_EE } from './config/constants'; import { Users } from './fixtures/userStates'; import { HomeChannel } from './page-objects'; import { createTargetChannel } from './utils'; @@ -46,6 +47,7 @@ test.describe.serial('channel-management', () => { }); test('should be able to navigate on call popup with keyboard', async ({ page }) => { + test.skip(!IS_EE, 'Premium Only'); await poHomeChannel.sidenav.openChat(targetChannel); await poHomeChannel.roomHeaderFavoriteBtn.focus(); diff --git a/apps/meteor/tests/e2e/e2e-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption.spec.ts index ad98df1aaa534..1135bc7074919 100644 --- a/apps/meteor/tests/e2e/e2e-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption.spec.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; import type { Page } from '@playwright/test'; -import { BASE_API_URL } from './config/constants'; +import { BASE_API_URL, IS_EE } from './config/constants'; import { createAuxContext } from './fixtures/createAuxContext'; import injectInitialData from './fixtures/inject-initial-data'; import { Users, storeState, restoreState } from './fixtures/userStates'; @@ -641,6 +641,7 @@ test.describe.serial('e2e-encryption', () => { }); test('expect slash commands to be enabled in an e2ee room', async ({ page }) => { + test.skip(!IS_EE, 'Premium Only'); const channelName = faker.string.uuid(); await poHomeChannel.sidenav.createEncryptedChannel(channelName); @@ -668,6 +669,7 @@ test.describe.serial('e2e-encryption', () => { }); test.describe('un-encrypted messages not allowed in e2ee rooms', () => { + test.skip(!IS_EE, 'Premium Only'); let poHomeChannel: HomeChannel; test.beforeEach(async ({ page }) => { diff --git a/apps/meteor/tests/e2e/fixtures/files/test-app_0.0.1.zip b/apps/meteor/tests/e2e/fixtures/files/test-app_0.0.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..820f6645ceed9b867243824f7510a7343de7bbc7 GIT binary patch literal 19886 zcmZ5{V~{35ukOr_ZS2^#ZQIzfZQHi-j%UZVZQHhOpZ(4~b?V;R)v3zARMJWHll0RH z(jcIyKtPa?Kyv9^n%RmI^ol@0Kq~(P=AY2n8yHy`n3)(GdQ_-Ujm^v`)+;$ujLT2U z(auRvOVG^8(@;<>%TAA}%qvn!(^5@O&VZCNEHbY!14SmA{S9mt0~aG}2TyGmGbI!g zQv(-M6D#&aH$7IAY64RDe=G}y&yiI9vv~AR;QonxO!A6Ppr2H84$1RCely@(cH-Ekd zbqj62H7aGfc#t~Ssm)R^PS+dOM)P7l&#llztre!fH!?GEhAuQPGh3uQ5ozFQvQ40S zs@_{j@Z|~g|M&&@KN0=s%Kt9Nf11I6J7j086W1p7x+LydRTOP18zp6Zq0^nd^qH=1QX z;O5AKer!vAAI7l0kG?MFDVm+P|HPU&op?P>Z-U!QsJ1(!68_tmTx0g$^mJr+F^irM z-LcALsnc=k1D3L;yIe02M8(LYjiESb1p=0J`xG3J|P%1C-1ZXrpk z%%%r4F8ruID|0|#e$G&r|EH(Tg(Cn&?^oFMla1R4xPmkUq|X(2!qPuSLx6$)=ZOCk z3XFfr^}liWr~D@r7DjfqboRDpRcasHD3RE?T#i;*vC<;Rk;s&3bboOPQwZ4_9M{w{ z#}XMrIn~;naK!5mu`Z%a(w8|Vk5(nZ4K$av9kFHSjBS}eu0L+fO_dTfCyerU|kY5%Tyu-2yWeZ zAVH~UX|uDkSQr@0?d)c{X86gSsCipGnUR4Mu0F+&?&ZqQ$7}IdaIW$hMR(l4_&TXO z+6O>f%s^s%&s~?bI5upOj1OUB+p=(7K5d`>I2-)%LGJb4faQY`yrXpgh4B?l5j7$8 z*SH+d^Wsg-vl=|%yhP;e?V-U}JO1TlRciS$m%EDG2<%L&g=kvJPo^mopGh_(@pc3t z9fz(2$+wxRHrcSsKd6SN42CB9`?t|Pf~Azz$EzW6A<4NCC9eBMQl@9E(qkGbS}FZz zKaxV4v<0?toj?J~Csuu=IvgpLch!}5DFQK@6wc+DA>XY|42m^}cEe`wwtr)l5Pxf{ z|CZj${BR6^c4=rIsgi0Pm8O&1mi-&%z2>p0nm2Cab)i zhQ#G_3ERddtI57r0eMWG^_x#(`pE-tt9=t4(k@1G+Aw`J(N2lCy9?`Lh$hyI#%iD# zZh(aY22W^Es>F(L5RyPO=- z^)L$B=HrtKc+u}NffbsvyBsdhX_D3WD51<3n|tS>zcu^e(iPlw1uDDy%<*aX1^k7I zQj{+8SPoqW6=-M%XC~(xYRY#Ir>pzEr>-6o3BRm0#B^3W0WNPw-x@dl;?;$K)MOS; zik^LR&kOc~KmZF)a4B4tEG#3y;(bqLlT!{SWLH0*EU9gaUi|`eqADU{rkJqNavwx@ zIr9Farf~s|6~ZPMumF|uZN

UhT#H5TfjGoN)%}tGt+sw2`j>pUc@h!rr-KWT6j}ehlij9 z0PA69nYw~EslEhf=pB3Vf^M-X!Jq9j1Vwwez+IHsp1ThB35T|p<#7^sATkzfwJh|@zjQT z7@&cmLplTM*cck!$(lvTS?i)CPe?wJo~iqfTLLwIMf?Gxg~T2-RsvQV&d2rjXp~gJ z828pG-lno>=sXzbEQ$eN$pU$dymq)8G$}S8@!&#Z}2CxO_KJiE7I(&GE(X^zci!$0ZXX)bEo?cY8ZiZg*{Ol+B`oHsrH89lx9`c%BT0?@y-Wn8=Pe6Crs|Ika%cSaR&DKaN`dJjUjte6 z3*^j;d#7=DoHrGY4&BAN=WntrIJ?2Pvx4t;qA#1TSF{xgR%^gj~qbNVuwYQk|$IOdz zunan|~ln^G}K7+94JU(`X39$GS zgqy5z!Ux^3Xr0V12k;5_7xMh^_tI*2+A{0m#UdBpT}4_o6Y74@ahio zrne=2+N1}t*|HMTnmoVeG?Kw5bu8C2ggV|-wLg8%ZWjehsOC>-9D{aF&@@k?_lU`4 zyJZ8`idxol1KH|Ag(nxsk#Y{T{a9?sALDHfTUtYn=02RYDI#VD2CN^K(2!J}lUO;*&1QcrL1pA=iR{^iw zjFKXakdaZtrY}x+%&*q_VU&SiDK=@kp}9OF%byvAmEo!K1y_cfi7=%e5&>WDey@w~ z%u{}QfOoAwD|8s_I?Z=Pc=2}#_ETe1bKtSfu|2JmD{r@l{ z(m%}jKm7=;wVjdGzsA7M_Nq&(p?tia?!DV@DDrK**-2d6;W?seQ&3a@(OZMOHDdFI z;5z-H#ja_~t6{A%W^QERjQF@Q4=v?ZRZZ1Y&2;(a4gbgC{iSA{exvsO!td+Ek45$3 zXQre>@_M*~;SR2+e1M1h3(5C8>IB30_$>olH|LE%ts|}H({9A|6V%yh2LFnR`-%Va zO{vJs=34JS5AxaSv*zcmuK!H*{_jJVQ;g37>XTof{t<-1mdZd)r9|Pox-Qr*xzg_G z*3fC$m7c8qXV({l)n^_wU{5A*dxIl7+O&FTAenIbP;vpTA3Xwg2bRTP&Os}-lJFZ{l(A!w7o44 z-Gk-gitOo{q+WQtxFXvM^K~BQb^^Cv^4)c}0>Ry>6tFa@s&A7&``C+2wup}vs}jnI zcFH`eUZOpldhU&(tn8Q-9d>4Uc_Xc)C59rD+SVj9&TIrL`H|YBO?WKJ>Hn^U>x**d zhWinA=acU&_E7Xm;2OOgE*ddbC5l(MmBTzq0~wT2Y75^X#zgLb>*$ssp|{1HNBk_ibqDQ)OW0!ch(&d@JZLn&++-^ds!qrKW$0KGd0fh&_(5 z+t!JptI{*bQioo_9Murk3J7m0Z`n>-CU`Dwwy#9B4lrq}D1j(m9i6pgFOx=oBwszY zkB>@mPOqh!Bm-F*1@B5m^PkN8YmAV8{tHdlH^*itz%x5xyU#u-U)M^<)A8_l4QlJS zO_;pL?R28Y&#(RC2D|dvhuwE5a^}WQon5yyJ8kUY=5&6)P^ZOQNS|F2+TrY(X4x7R zIl1Dg-C$4O>m6n+b&TQZzPTPHZL4-XRMg)@;BK8Sk`e-Ob*(dt&=GKt>s3ELuTqF61DC-We&h;c^p0n}Z4^;Wrl+$(+640}{Ud$- zm|fNjZ$}m<%2&nZea^XPyQdp(yMlYNPx6+E9Z#N7m&E`Tov6~HKI`rDI!>b_mQ;Af z++adZg7hM-r77FhqA(-pxVC_O-ta>Kt4KEdzwl|@bBg5Kp@H2gFH4S`f*!q#!=A4s zF0DV@!7L7NM$2M@n~j(okFqjoUR1VdCtRtNs}X@(amH1Bo-`gye>4N$eZsAVRBYMZ z+ITWgTkXr9ICyIMK(g0^%rPlH**~4XF6>=SYz+clQ8z^^rrnntGkr|7bzNPjiSFuK zi`fyH0wv1^4OyKuh)2ChbwXia<2yxD+z0Z;wqJH&wnaKp>+(cAFcm6iQ+y0JSom;kOF(K{ zC3afi42(WCeNf4CVy-juTmT(9OB7in457}JAyC|r0L6!y7SmPAG|eSz^bDghI|ud_ zx%!|S{(;5lDS1IU32Wn2jhowaT_OVp#K2qN%93#{aD(zxlG&CWili=Vpiv}=ZEkQ+ zowuyk{zjx}W@a)JFz71{Ny)|%=)@BqdxK_O;Gdof^FuuufJjC_FuG(BKSsm^N~ht- zY~F(sYim#3H$TRU{jOZk(pXP13ri(pvO|{+i+<5yB#uZ%4rFDndC->k;?1tT)1|iG zwj`AJGQ#w7T?$*{AD+A!%U1C9dn1@kmw-IS@+U-U62+6<-5Ui>0=_z6#htx#ubQib zdPK&yEpSx!Cu{=1EGJ-JzB5P?*o;>%GwfI;LksY>hW&=q7Ky`D^ZWI{KQLxDxJ}@j z6$i+$LPxRCylfyv@aXUnms0E85~u4eJf<;q_+xpl>x^tfLwW&4OdxP5FUgfIIs{6@ zE@MRsrC94<&C1ne1Z`LPgW20L{*wKk3rOLgTog}m&Da~fak zN4NVickjq;aXbH$gWU@V7hQOy!e;Ch=2Idp+qhTB$}}maCR0S+vPb};t5%CrwnxJt zL3Tw-*37oC_$-sNvQ7kyyp|_!G0RyA`Wg5<&LAsT)9W?tZ>#zra&Nr}gaWI}6KXxu zwnG85BK?77HWpE5US$0rvM3A$wm?F1?cB@B_Q z_lt?tGTZAR z*0mz&vcF?6>U|1ig1QW>Pi~tLH%-Y-tdbNQZf>k+#O zpP?V*D@D*^q?%+g(5|j8C6YQ}`yM{Sqc~bOCPN3Cr&^}~zo$&1qILT`!xkRSkOyxa z)2SGU@N>AzYh6415Rc3}YdHAz_`{vEpU!0Jbttb^O4jsq za0C~R4NU6ExMAH$qKsxC374B_v1LhCftP38uxU5p>pWPrz+-VEfny6YpTTNfK=~$_ z4iJcS3QmQ8jb6)xo$BrApJT3Um9JO+4h{>Z30|nXc=c!)D)Wqja3V)5EXBgP%0o&*} zR@E7;B$>|H7gkZ32eGB7F1w`j5Op5o}@&g*UGKkf*1YQSZ1KOc1zxn@ueBM;hqWY z=-ZhJ!11kJCI__^=g|WgQFeLPCH-t8OE+k0eh7p0EOeSta{pCm)G5eDDe7M4LtmV0 z-Vh1i@P;DavWLMnyEyo@<jG^`e}^rw zV+quIoGg-0dbV?wpH#N5_*L+2M+F4q@!~A!p=(dNby+l@F?C~=Q+6tOHXg=85P>z6 z91;*A<2-bn`l%v)04N@Q`dg-+^)kT7xi%qBhc>L7!oW7Enk((Y2I(q$ttBV-9*Z0Y>3xTYMBZL{+YHL9x68f&2N5H4mZpHPbhGhy5`E^doi+dgEmASbgV(geN!P)5JoMghx}ao2RxwcZ|HvNYNVS z76>uRsX6JSpo{AomYRLuHGwq_ zyWX}H=%6@H$OSWS<^Bf#4nfN8G`bGGgNZ!RnQN(-Ly76AO4AAf&lf(0%~~Jy-#kCg zWV+>vvL2K>w%0)8rP*JSB)NT(y#ZlvesPy?3TD;1Ap*^=p|!bn-AqXAaBb6_N=Ebz z*6VS&`kpLf$GpD}zbYy;dOhrb`s(+St@iX*AF#;k(1UbhH3p*p3%^h}|_ z=iGC8DLAOmE&lP$G0f3BA2)U>Lpy5vxfi;93N7H8DeuQ6HF!`#A-{3-uxe9+5M?|BVx=R{a5uGjgom=E#oQ^&U2xV}k?+7o37IJjr z@;JVu?<@0vLl%-7lE>#j7nY>nh(f`Q7hUc@Sd^3Trm6AON3kH>LnQZ(`$E%j_XU~H z=Np&oM5(9|(Zs`v0A{0rkU!{7x4^4Y`5m|&1HPZ8VJWq+; z?2j!l`@)&Fy-fY}#j0AR-!GBqQRJjvIdN}30lxuDS3qu}wE{~<$fIQ%HysY&bmszI zcc|8c%6zA!Na(-Qchu)O!U1Vkdo)IuY35yrgMe;S>s8d1`VzQq)3Dk-?WM!hL)c7wJaYkj5x51v)e!-DVjFM9TXDjcxYI6Tc zsCEJY0e|`vQ+pYLyK_+RKg;RWrk!3r{q7mx)otm({i)~#>hdEZAGf!NWO}RC6)}fC#4&7Y} z2$CrKK|3e{s(EMv`EdnG!vy|4a$AhAj!H?(c1%H3?f1@!U59jWW5qb0m~8s;TMB0R z&fSmBcMtxzn+l!J?e+US(n^6%^)m=$UKSU=*JHAgD~zGehZO9&09OCuA93_25rHS; z>$9jYq&05b7Hw%=7S1d+I;WDffTk0KbKqYRyh;0Xn$Mx|tpIv6;1RjY$)_$wOZMw; zEpOj;WFHczU#AB#FhXuMV&>Iv?XXoC;Edb!dlu*EO~G)kWpMzCLzI)ed9AixLJMYK zik(3m7fZk_3pa~`lK{GEJ~}|RPQ5E05{vkn7FUm#?wrH*3S|{Vcv}eW=dS!-YVX&o zlZUg#oo;2Zl7SXfZUS8K@7PIYklkH>60qz@7ggD$>;+Z1(r{7UZOKQjSi^R#aEf3m zooEUQJ}zlL)|Rf+e|guL(^$7g64j3E&W|U6b~_nv6}0RS1{bc>@aLE*Mbfn*_(g zA^A?eWH?*ASj17&^Hxea6^^8Xq2Y2U=C~V77$)!x#NXYhI3Jrm>40#(Fukx21wL#s zvG05iX>JT{yD7MmA736$ZxUzlpM&f<<%btn5s4=19Mhmk_7R?W#$Vz1k;E)~mY=YJ z%c9uHAB&dJ&V(o&GQ}ztbdGC=C~+C23P4E*u4(wZ00g!~t2%h#v`tpHuQ1#@i+s4y{FcGQ1^for+$zK)>UOv+W6gG9Q_ zK9OkYJ{6JSWQ7Ox@bHFYB~|{BQ3EB))Bfc+Y?PE}V^gDdcgA$YelIalNh9d3Rc02; zk;?(B)eq8}XnxNv5zO~){^7b4sgj>-rG@F{IkHJHi|eKxO42knzfA=EQ*Expf$Wk1 zrxyO{zQ3I$XKh{ngQ#G%^|n)*7ousuH(b=gg^|Ln0<_oM5AYJ=8OGZ$Y3V4un4y7X z;7X1DphV*`rrSfhRNTlPwZu`9(9v#HsY_%um_evAz;r#u8FOg; z1u-(pKrslcguU7Z`!GP(GO{8Dv#FRJxPG3%e`2#>Md#D!Ko>fvrElr>~#6;(r=wV`*=6b@SfKgtcf{7dDr4 zD$hJtHqMd>kUey9fj zQN?-S^&+h~E8Cy2q4ty#eiw%w#h(VXBaubu(S?~GHyZxS{jkJ`_KM7U?CAK{hV>$4 z3O9;?R`Z?fl`Mnjz`hZ<)qSVx#$Y~tbvL<+q?s{k-EwC-?!l7bZ2QuDbJLq6dr#`} zDVtx;_v~THPw1*)RXbZR{xXI(=H9bcfr`=5rD7ZV9J; zEaO2RBZx&QV>?`PEL0sTebNCr!Uq-LFti8Ve4Wt6#7!=@Rl^C8uTmP zh==1Sc{S$dem~!1=0^EdlQm)-kW>C`^mx&y30`fQJsue?0hh(~uXu#VYBO{dw%xcRf<+M!Yt)C389k;xD%W zXG#1uzjiOwv5IcR9^UF&9du<4-yGXvLSBD1%h|SJz{H6O#8VE&UFa|}uqqc< zp_^zNY2_rV6QDBa?t?E}IZ4{Cj2y9e4AP}T-{Ro8%UefjZ+&w`qp(PA$H(TT>t$qI zT$amP*pN2Bj!b(`Nx4E?+llBQc6{XMZ1Hj({Ywc!jRLQXVutlKQnSb&c`33VPNgW3 zb%J()ln){_{kThrtmzR^-N8`4GG1eNJWn0oVs|xi;|86zt}?irlVyA0e&ELRn*8du zoIA=Zm3f^^M$Evcqu7tFz2v!}_cH^GK4rSh;+_v1O;xw^Pfc~?a*Ez7dDACl+d8w! zKHt)3keHuz>o@hmVaQ+zt}dMz0vxJB1=nQ7hoI)P>st^gXulFfG>Sr=%#ES@H zbej%;<8GCzfiBph1(ER|xT3M#qU{$qpBZ(pD`ItI+Tf_ua@i)V9_l=I$=qHq$hQ|eD)Ls$ya^@IkgqrBnstWe zv%tt#s>})+oWkKHh^sQ8bapE?j zB$J6?O`$V{Tua=_5>g=#CN)I|6@|ZIAqrsOn7AyE4?)CbBj_C~(CJk|)8`$8AW2p} z67H*R36Rrc1HE>3x&6%EBAa>%`YvHwh6#XKmmeAl_Rh7r^5FA7?~~&qsrLV?P2#CG zhYcb?Tq!}Mi5S#Yi$Rcun!F{3<96(;584g(8j4kDoMpez<`Pf(Cltpdgbnzumh)5; z!t>AP^}2-b$-W27I(2vMi_wS3Uf`9o!{g%a7FA$ns7%r+wIq$~K;jr0N060#Y!zqA zLM38^@RK>$t=LJN1C-H#O4+cvzFK*#*tOy1(LvpMTCW<|BrVgEloCGPIKfj){5?SV zdWpAMCx^s?zufy~F@B>noZ>cnY_iL~<-@e#?TziZ?at{Y%U5ibSZkRh<1d6Hz_8(n zf&Ujz&um=8Bq}hA0bK7*_*K6+H{&M6_8w;IS1OC+F^QDi$zm#Ca>3E;aAHV^DpRa> z8C<-vk+r+6%@H?id^+$3trnm8w&;ynAg$lX^|7Dp(!9dl2LUvN2mnLLcD3)IN)NCo z9ONn_wB>JVj0N{Xpd|5xg^&(*AX>y98JoK-+`Ph^AL;;syn**oEMDJ}9-5GUIK?Yr zxTn_$-JatxxWBd(;WNbjW1isR%r5N#{q~hiA3M9RpkciNF^C9vn<$JBlba^~YXFuC zVo5b#m0{EY4zb;wqB;(GtnqYRt2f>BB_qECN7x%~vLfysAG*;aqTzQ}GG27mntQ!A zYFYOpaNn8`H2{9aMObKrqTdYp_#+&5dvN1HaqV|ZTG;<3zqu2JPqcmGv1Eh1@=>Sj&W5NDmUpYu9I$9$6PURd z#q0?TuF{ZR&U84v!P?&uom{29#m58*c$kRH3}|YYgpnwZqeNYL78~hC2bW6}O<5Z| zC5dMDvnPIG=Q6YB+xD=QcI#1K7!QWw;3>Y^#A-hBCJZ8NxilzUX^#+6uGpH}gtI2y zNG?7)uQt}VfLLkGMhXs((ux#F^_>AO3zj9yutfbaNV){D_iApubzb~DzMoX(wQzJd z5XES#MrA&V+6DUNyRH3lHJH`)YTJ>Ic(W+sZSj-)d{t@Bf_8EvhihJ;s-&8dl|KGO zMqI4bku89P1)9kdTvR4}DYAL(y|7MiBM{AqQp)HZi37qjZs|BFtBE~i+JG7{g~uFK zbm5M`{p9DgJHpH0x2$ewsh(@3cmw{)E@gJ;*TL#ZWZ#l8cf(lCHgLdn>*zwUV9>G~ zW?{Lk#YLvXw+ye9%4JI?n9C8<*~buROVgnR1i~ODg~cL8(&s>-TgxF-GnUPRF)`wk z=K54K-jdY+Z3_6#9N5{~?V95_cy@KsjJ`hpqZtZ~CrQ#D`n+L%zD7&OBJN9y7?R`M z&+!LyO;CNYc;r4u^gUh1c*+$kws@I38m1gblS8{Xft-x84-WjYbtr|Ip2BK~k%wsp8f{=IDL zXY&=R=waq`e!6SdxDp!oBqUZNxeD(+3%P?c-wib`{IVBIXr<<)saAN`VuC!1E2sbC zYdtE!!!RNyOJnLUac4E({AjfD#2OAHn#KLg?|gpv8EoGcgJv)8lH1Ibx3*|Njej(zu?2Vjf+MXb8H7CETRG!6hsVJ(D;i1{OO*;b{a6A6xesC90 z4VeU-2Od+&!2&QkoRp>V>Mxn!W^ne^uu0{yjX`kc&yLB~$rG*Xe#JIR+exh_PrK`Q z+&I~du3;qq?x(wMyJOxj$wj&w-L5jpTsY%1As-QPD80*aPz|-2#8ux%?v&;Q(z?5= zBuVqkz+?tY|xHO;@*yIC0?jE)D?)S6em(`+^@bl{TeL&}O zL3_HF`j3?L+1F9$Yxx8FsB8i1=Ky))(!=IP@#1XOk19rTyv!<99uL&4SsJ+q6-5d> z&&^YwV6=ViG8O|?V2WTstkA1bPhw2uQOtAFr_%*}>v#|tL=geP4X3H=HnLjzd#uw|;ZYt*<;oLmTGbdh_j}S^k_a2Mc4B|^ ze0y^3iv1d>UJDJ3k#LnXN+5H>rKNy67Wt>!zT_qZ&|M$3e=q9hq0p7oZd;?sp97Nv zrR{lG=B`XK4Ku~VkOE(tGC;sl$u$Y<#FXOg;8SR;+3^r*SKnj z1f^fESxQSLE;j+TtcP0cT+29f-x8?w{*^}Uny$0kW3xacPkecT^DeR*IHK&}Y;kk# z!|%Qzr);KeoK!ec9%DEJS(kl;9-RN@O1MkVi;<05Zaskl5M&4!yW)%-FXPGU{49GY zs*@Bp5nyfT4`nGmD}81FGG-ScH681}GNa(*=F9Y)GWtzOc1YU47v)K>`$2!QrBn92 z#`C7~R?d6?;Jg%zl0U20<=g)|sPh&5_*c3UrjJ*;=uj1|$n!pXP(EnaQdkubbLly{ z`HzzGP`9vxOO7yu;=ejc~aXWKx!avW6Fo?h|CLgnt9RhlerF@~KjsZB6e3t0! zTHu&+Po2uYMyyZRoqH(Tdh}Yi1Rt#9EdVcPD1c%etXxLcGV?_pY{TW$b%UBE=W(@3P99*hlD<^SNjk#BNX2`_T~e94=r~jC3-auRFL9UK)~NoL_IOA z?Cr*>erY?tFxB@R&eTtDgfpx<@fnLH_{ONzjb{@j1I&s((Yova zj+Ql}rBq^aU)51&&1h$ZoTc9;wcH)0OT-C`aoF~So{NWdKeDEyJ%%Y>MfSsIMW5+9 za!(>YX|1s5=i-#w$x;f+v?8??t_5C!6vDA~oE5x1gNFTdzv`geY~@%lA`b8l`!iX= zVg?rjXS0m_6ypmZ%ZyNm;=c3riKOrwncyP6h;2IYjBbFNlVa!v{c%^quZMic0#m@(0|WHlxMQ0ZN~ zX<^pIvfpBEOD&Z;@}>tnUkmh2z6gnJ6V0DmzgD#FXJ**VcTn0M=QfTYJ&Y^RopHsv zSvF+hCXCRm*ov-H7JpgKl6w3hZ;rBH;H1PDDqpY?!e68#g|@U9a^aDXo?Rs5UAHFvp*y*LSV4LZB zYCBrhK{Uig{kF3g*j1pep(@&ORH@c92^g+fET)Ed_2DLc*xSd{?8dY@|CY9Ak-gwy zo3}%2yH}JX4hCrZzgM!w0qM*$FL!46IYtKTV5#lV)-sBP19VnnH~6a5paqPMMW8f9 zU*nPy-Hg-q-d3`?ymnZLR-I{7&E212`zCqLwj8#M;TOh3sKnau>Esx1CScy+pR;3_ z&yxM2M)~ckMYsVK(h1E@kt)}lDA!^PZ0Hrmk-mpay!WGMmAJ-)S>7q`Hm^VWpY_qZ zsh})Dc;a`aF>pFl%0tC8>_?Bg0^;Wmb38F!weNfn5As)K@>98j?WlAXxi`&4_0)L; zOTQ`YpwXL}nals|2{IQg3}>M;d$*^X37o(w>$BpNmKIn-N)-yu#JSKGmToM3V_l_X zvGa$&{sOJ4-z(YQ8OghGznsi4D}3P1ddp_~IRWHa_;BVNFfnd--AUGt)wwJ`(1t~Z z!_mQ6X1-4HaizNZLcaDDKaS~VWOLVaY}R_zfm(g)LxY-UQWtQ>E{z#bv3nPf=^L@p zv@5QRA-vuOxjehpzQ&DQV#hEm*r})4-*t%Frg8#ea83Y%H7f}!S0GsTo5X>Cbt_Fm zRJmkG{z^38yj&5^?HisBIj1IhnQKMrYDdp4W(fSzdALKauw2TKbz7ikKgxMYw#AdV zKQMXPe0`g(U-)n2i%BA_1q%wKXs@GcT6e*O+@p4wV&nNvxM7VGr~wvE&nV(=SzV{D(|Ko{b{I5df1WD*AT^&|8+Xv3O3pcSfT%pve6Wa#~0<2Ii* z59w!|ADi93%p#|4*I5=KO+BDDnpmb1f<4xLqq#8*Y|M>mTiNp{dsd4T7g#vo3#97CQM z%Z(ADmPJ?sEk6_O>Ok{%(uU9*uHy$Ww$qJLOt#leMR0SE)`;C3-1ah7_4Pv6%GP)2 zRrR*d26xcS*2wsUViG#J#i7>0Ph8jsObD}EcQXD&sw{CObiSBIjAs6jy^|@SLc1%F zeT9uFqKf!C$vtWV;hcXpsPzVpLo;5`0K@Pv;|QxzLjwpHx-;mC)SMNqaswf~a%y#L zx}GujA7Nu(a_3*CFIw_Yw>Q(tHXOX=QQ`a!Qok-(v;Z>7<`{3c<76;l)GHTUtZvPS zbdi<)XTjSyB5m2?C^qbUE!06d=f0vh>FFdG70yR-E>a$wqb_gZAMh}*7VXjO$n<$# z*%pQP~cypjluZ!N21=G$uYKF3%2; z*AD!qlHo@yToCa_F&V|5Y*R_Roj^kN%#~$94l=@dP`rJi05G-%Zzchnc(EjJVevUu z&D&?s_e|0~QtnaKwCI&yxGxT7D=imgQi8yj5-}n-r!4x1G&rR#{ zn3Ra)V}uV=-|<+#rGb+%sNY~w4_e@B)hhStyzoyw)I88 z6FL^B1S9}gS~?}MLWzwV3bw6qkZo~gHjUJY8n>_9o*QnjwHY|mnI*b(ZK=YNR`d>WoW*<)O@-cWelVYyBzWy=imFr&s*1L>Fag^Yb@faUnF>+&^IcL`(v_Ch2( zWTC0Lc&6A|!*l`&gVzt1Y58qM4BRud@Cn?G)9WA*Qjr)vq@p;+z$<2mQe=!mBuS{T0Vv&eY4pk2`b8D0|&uBXA0v zGaaVELjfKnLQ9ekpSJpGnKFo8s~PL%3ikNkFJpFzS`Syy&`O`}x$~jC%@qB;scT(BE|k=jWtJTy z2$no(tu`$@!bW~37gY90w89BR!ki97$S2SI0d5RR>rJ)TD=`wjYJeptOR6@#ENR%q zFE0-TmO3o%to+}M)w1cxrU3=^i{7xnsdfutts@q3+SRE*;wW;-nv{l(6{$=M1x@YA zOg)DUJN;(1cG92D$e!|tzcbS&Ce((u780AL6eC1fJS#(mAsHD8EAP#HaYjP)j5CR3 zf(?#ty@3G5XKwJ<5&f-{p`=Vj2w4>zJnYw-f=5Q9F7NZI21+*0P0aglT?~}&xAPPI{QA$#-BV*3_Clr*+1KI!^mgS?R1r>bcQSXa>3HF-Z zUco|Roab@A)2OnCr^M&I%>n+T(U+LP->EB6BZ;r`C~jzjTRe=j;ApW<;h~jD*1Xv= zFO1EO+=VOIq=y#TU>nrtnyjnIW6SLejyh{hsL66r(bU-PZm<<1Rd=(D2!{x-rvqn) zJI0aUW%}>2qrdA~DoTv!+$R1ayEVRCx0<_9^1(-eBaWBp0a+QHNl+7r9b5F+;W{Vw zFL+iBv2C$IWYtm zrFmbtG5?{5i!4V?fLsz1B#4;ZaZ9%h_oGWk-kmoh8yAJqytT0Et-} zH%S3A25pD1==#m(xnprb1=}Kid)LW`e=q)h(NldC;r$*=FHB<_OsKQg)H=a*H}=bs zS@|jsts%}>*X!YE7}EC;sMn`5^Dn#|QNq)xhiuenp8uDrQ|&`wSqU>Po7-Jr)-NQ` z_*Rai$btZs!OP7NY&N5WSZm zO7xmhqGTAOgs6jr8AS9FBw^IQl7t|L-Xe)EqxUv?OLQ^OLe#-b5DbT$lXE2JId|vV z7klq#ulHGNee1p0H}7wu6q+-)V*M*0w_$P8{2o5=onlj_ zMn9&P)+S|2il3q=8K-#P)|rgX;%Q)eBWgycIT42tclIG76?;R8Jt*n7DEcZ=Ns23P z#n$5J{D<;K@+<4_T6!1~FD9z&8I?+scAE_gx7d0z7t}vd=vfKK6metvKob^b`4BlK zNhe(WZnKyn-GJZIUB*-0<}5{{WaTZoDJH$BW4|gmGW)QH68G+khtqM4+M!nwiGTGl z&AWu;rMQHL1~Zt~m8^bhDTSL+anRY1Uh;)Xy&di!q;ES4+|WRpJwlKZ7^tRbJ6XI$ zvKka;;k${%fkd>lL0j|X$EYuJaGqi7e%9*b^_2)(h&dp{|AdX}1* z&`*?DT};|?UAcP9UqufnFu|=#;6_g*Y0V`4oi?^C^|ltu0EW)$NW8bD+ff@2ok;k* zMkk1Q1gX9TXVXJ19~S9Hk{AkwPSG$F)Y@;|Nhvk4n3Phc5b06v!PY8K!PXw`heVj7 z0~MXgsixb)*136P)s6+Pg z!Tqhow#sr?QFgIPhsBr$`t}3rfIAj)CS+AD6`#jG5gIw$IaH>O@{u0WIeW@=cLEi3 zP3fWWa3{MENH3WR6l7Qtq3;bR69e=i(JGBMiNCbE-M`*6euyJ#_pACRH3KwLD)-Z!uSD9*W&E`O%0%2SFy3Zx9QnS9@X>b5d@RCkz={q>O zD3m6Jw?Bb+TYs5%W&0r(;ntQ3j`07nuk8y_PQ_FXx(<2;f~_HdLd-mbl}B-4XfK+5 zzB;P za=dbts_EL$J4jlM|7T+L7`si>sy-)u{XG{mN<93!Dmif#cKj1Oe(w0qY8)dVXt%BL zK~y98tk@?6gm7OtdYxwyqBC=7{Q7n3|0Q@-g+ng;P(qHQ9fGvxaD&7VfizA}>74i^ za!`_C#39{b)OtF-9UfNi3E6CW1$~R+jA;szi%s+yE7d|4M;?KLjTc35JgE=NyWILRCw7-jPay+SHoBKY*0XtY{>dW|oBMHYpDD3Af z9e}LMN|GBq7G%q{G5G#ecJ%o5YmMOiL0GX+#SUH{}Lw03p zzv@1=J8UpMdA*VJd4Cb``UV6T(MrVsRL?oVVP?}UxxDpKUJP7X0og%N_1$fl?(g`& zF4f}MFY}r@c_ks+g}&*w+-mX)y4h^MX=1yRUcR#eIdSF9Fz~BmoPTk!Cnme(+^r0> zFvN{h?Tye5;E#0JzlvD8#_2Q@&Dhhnx5=P2p3hU#88hf^GYjs%#d6uJc2n&-;W1AJ zx5$=l;iG^Vszc$LHZ-^5i1oI0kPsqMpKNG8edW!TPa1&uuNf6PsW+@%f*O))5?veq z>hT$*B5HS4UqEXMsAB7r4`WC|Z8Cb}nQVeV69|h*Eq}bo=VfhK`L#A&;v_5B#fY(| z;!iWE)Ns?Z^h%}7jj6MwUEy(s5Id{omVa%CnlG%y4wShg@kr1_NBa}D)E zmYqD#&O{2*RxDpeQl9nRPqWqy3FBk#mQi(u)hg1f;hW7cOS7>gTwdpHEF)aI*0EeM zUd%=rP(>7skQQW_WcfFX|;J6ct zS3@Z*xw;fq?8lbl7?WS4EfC1E_?wLU2|Y44CTSCVx(pdb#5 z!Vg(AWjlIt#NF6Af09x*va$!JR!(9kr&~_!)x8N~=2@lPj zf{y==^0@P(_NYTROtL}jE6vW|pKfXJ3i~42s-WC8>b;2jT#Ccdjn#18L;e1cwDfw_ z2NNW{45}c8s>U(|izBz0%?H9NV+m|Up1J~C_NU5sLp~%U$0@13ikOYc$1jtj0&k#q z(;Izy06Vbdk%HySqe4dZR9QM*VHNXYT*GUuXYeWok!dwhGYiEH(Xc2J<%hCvOvK#R z&9lpIC22(EY%*TC!n98HtZz_U;95fB+WWU^-Y4$fof2N&g9jYo(^K^6P9XkyL|ego zZ^up<4{u=lVnq8-tj{^Q$;jQO!{fuW@#Ww1x-=GNFSJFt_dB0FVPRk-hPRQw9 ztZ=IbUoHKh*PYsU%vT^= zY~_m4JDACxoQWhL;yCDw-nZs;GIKt#xZ$YQBbP|ijBHUkZ@QlS%aORUT)=AnfUS#j ze}>aHfEmYr_`H9OlWk!AGx=nW^A?D3y_)Sl+o0obWTQV*>J(=q{*_Yy*@0)zTglqQ zDZH}9LE{z$W=N^D~O7WAEmOv`dUr9 zk$(zfHc3DKU+bJ>QI@Atf;WY^fYSH-k?A zApGAHu(M%53jsgBU0{Og>-;YK6XNwx86F-2?tk|98`A5S^cN@VA~xzA4)v=LaKp0| z{-S1GRGoY8A~@i8l@-B%08 { + test.skip(!IS_EE, 'Premium Only'); let poHomeChannel: HomeChannel; let targetChannel: string; let targetReadOnlyChannel: string; diff --git a/apps/meteor/tests/end-to-end/apps/apps-uninstall.ts b/apps/meteor/tests/end-to-end/apps/apps-uninstall.ts index e51bbb31bd96c..c8831cc105c50 100644 --- a/apps/meteor/tests/end-to-end/apps/apps-uninstall.ts +++ b/apps/meteor/tests/end-to-end/apps/apps-uninstall.ts @@ -5,8 +5,9 @@ import { after, before, describe, it } from 'mocha'; import { getCredentials, request, credentials } from '../../data/api-data'; import { apps } from '../../data/apps/apps-data'; import { installTestApp, cleanupApps } from '../../data/apps/helper'; +import { IS_EE } from '../../e2e/config/constants'; -describe('Apps - Uninstall', () => { +(IS_EE ? describe : describe.skip)('Apps - Uninstall', () => { let app: App; before((done) => getCredentials(done)); diff --git a/apps/meteor/tests/end-to-end/apps/installation.ts b/apps/meteor/tests/end-to-end/apps/installation.ts index e3484eb81543b..fc1ea2a0d7f35 100644 --- a/apps/meteor/tests/end-to-end/apps/installation.ts +++ b/apps/meteor/tests/end-to-end/apps/installation.ts @@ -6,6 +6,7 @@ import { APP_URL, apps } from '../../data/apps/apps-data'; import { cleanupApps } from '../../data/apps/helper'; import { updatePermission } from '../../data/permissions.helper'; import { getUserByUsername } from '../../data/users.helper'; +import { IS_EE } from '../../e2e/config/constants'; const APP_USERNAME = 'appsrocketchattester.bot'; @@ -34,7 +35,7 @@ describe('Apps - Installation', () => { .end(done); }); }); - it('should install the app successfully from a URL', (done) => { + (IS_EE ? it : it.skip)('should succesfully install an app from a URL in EE, which should be auto-enabled', (done) => { void updatePermission('manage-apps', ['admin']).then(() => { void request .post(apps()) @@ -54,6 +55,26 @@ describe('Apps - Installation', () => { .end(done); }); }); + (!IS_EE ? it : it.skip)('should succesfully install an app from a URL in CE, which should not be enabled', (done) => { + void updatePermission('manage-apps', ['admin']).then(() => { + void request + .post(apps()) + .set(credentials) + .send({ + url: APP_URL, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('app'); + expect(res.body.app).to.have.a.property('id'); + expect(res.body.app).to.have.a.property('version'); + expect(res.body.app).to.have.a.property('status').and.to.be.equal('initialized'); + }) + .end(done); + }); + }); it('should have created the app user successfully', (done) => { void getUserByUsername(APP_USERNAME) .then((user) => { @@ -61,7 +82,7 @@ describe('Apps - Installation', () => { }) .then(done); }); - describe('Slash commands registration', () => { + (IS_EE ? describe : describe.skip)('Slash commands registration', () => { it('should have created the "test-simple" slash command successfully', (done) => { void request .get(api('commands.get')) @@ -91,7 +112,7 @@ describe('Apps - Installation', () => { .end(done); }); }); - describe('Video Conf Provider registration', () => { + (IS_EE ? describe : describe.skip)('Video Conf Provider registration', () => { it('should have created two video conf provider successfully', (done) => { void request .get(api('video-conference.providers')) diff --git a/apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts b/apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts index 401a011cecea6..f04482afc747f 100644 --- a/apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts +++ b/apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts @@ -11,8 +11,9 @@ import { createRoom, deleteRoom } from '../../data/rooms.helper'; import { adminUsername, password } from '../../data/user'; import type { TestUser } from '../../data/users.helper'; import { createUser, deleteUser, login } from '../../data/users.helper'; +import { IS_EE } from '../../e2e/config/constants'; -describe('Apps - Send Messages As User', () => { +(IS_EE ? describe : describe.skip)('Apps - Send Messages As User', () => { let app: App; before((done) => getCredentials(done)); diff --git a/apps/meteor/tests/end-to-end/apps/send-messages.ts b/apps/meteor/tests/end-to-end/apps/send-messages.ts index 9cfb9f2688d1c..7e6db4cc4b588 100644 --- a/apps/meteor/tests/end-to-end/apps/send-messages.ts +++ b/apps/meteor/tests/end-to-end/apps/send-messages.ts @@ -7,8 +7,9 @@ import { apps } from '../../data/apps/apps-data'; import { cleanupApps, installTestApp } from '../../data/apps/helper'; import { getMessageById } from '../../data/chat.helper'; import { createRoom, deleteRoom } from '../../data/rooms.helper'; +import { IS_EE } from '../../e2e/config/constants'; -describe('Apps - Send Messages As APP User', () => { +(IS_EE ? describe : describe.skip)('Apps - Send Messages As APP User', () => { let app: App; before((done) => getCredentials(done)); diff --git a/apps/meteor/tests/end-to-end/apps/slash-command-test-simple.ts b/apps/meteor/tests/end-to-end/apps/slash-command-test-simple.ts index c74bcffaa4a2b..56031faf44869 100644 --- a/apps/meteor/tests/end-to-end/apps/slash-command-test-simple.ts +++ b/apps/meteor/tests/end-to-end/apps/slash-command-test-simple.ts @@ -4,8 +4,9 @@ import { after, before, describe, it } from 'mocha'; import { getCredentials, request, credentials, api } from '../../data/api-data'; import { cleanupApps, installTestApp } from '../../data/apps/helper'; +import { IS_EE } from '../../e2e/config/constants'; -describe('Apps - Slash Command "test-simple"', () => { +(IS_EE ? describe : describe.skip)('Apps - Slash Command "test-simple"', () => { before((done) => getCredentials(done)); before(async () => { await cleanupApps(); diff --git a/apps/meteor/tests/end-to-end/apps/slash-command-test-with-arguments.ts b/apps/meteor/tests/end-to-end/apps/slash-command-test-with-arguments.ts index 4701ed3c4c85e..e462536f23dd5 100644 --- a/apps/meteor/tests/end-to-end/apps/slash-command-test-with-arguments.ts +++ b/apps/meteor/tests/end-to-end/apps/slash-command-test-with-arguments.ts @@ -4,8 +4,9 @@ import { after, before, describe, it } from 'mocha'; import { getCredentials, request, credentials, api } from '../../data/api-data'; import { cleanupApps, installTestApp } from '../../data/apps/helper'; +import { IS_EE } from '../../e2e/config/constants'; -describe('Apps - Slash Command "test-with-arguments"', () => { +(IS_EE ? describe : describe.skip)('Apps - Slash Command "test-with-arguments"', () => { before((done) => getCredentials(done)); before(async () => { await cleanupApps(); diff --git a/apps/meteor/tests/end-to-end/apps/video-conferences.ts b/apps/meteor/tests/end-to-end/apps/video-conferences.ts index 65727168c47f3..80813603d3be4 100644 --- a/apps/meteor/tests/end-to-end/apps/video-conferences.ts +++ b/apps/meteor/tests/end-to-end/apps/video-conferences.ts @@ -7,6 +7,7 @@ import { cleanupApps, installTestApp } from '../../data/apps/helper'; import { updateSetting } from '../../data/permissions.helper'; import { createRoom, deleteRoom } from '../../data/rooms.helper'; import { adminUsername } from '../../data/user'; +import { IS_EE } from '../../e2e/config/constants'; describe('Apps - Video Conferences', () => { before((done) => getCredentials(done)); @@ -65,7 +66,7 @@ describe('Apps - Video Conferences', () => { }); }); - describe('[With Test App]', () => { + (IS_EE ? describe : describe.skip)('[With Test App]', () => { before(async () => { await cleanupApps(); await installTestApp(); diff --git a/ee/packages/license/__tests__/DefaultRestrictions.spec.ts b/ee/packages/license/__tests__/DefaultRestrictions.spec.ts index e085aa1656057..ea1f975823f1d 100644 --- a/ee/packages/license/__tests__/DefaultRestrictions.spec.ts +++ b/ee/packages/license/__tests__/DefaultRestrictions.spec.ts @@ -19,9 +19,9 @@ describe('Community Restrictions', () => { it('should respect the default if there is no license applied', async () => { const license = new LicenseImp(); - license.setLicenseLimitCounter('privateApps', () => 1); + license.setLicenseLimitCounter('privateApps', () => 0); - await expect(await license.shouldPreventAction('privateApps')).toBe(false); + await expect(await license.shouldPreventAction('privateApps')).toBe(true); license.setLicenseLimitCounter('privateApps', () => 10); diff --git a/ee/packages/license/src/getLicenseLimit.spec.ts b/ee/packages/license/src/getLicenseLimit.spec.ts index 0a95519d60835..84e9590861e83 100644 --- a/ee/packages/license/src/getLicenseLimit.spec.ts +++ b/ee/packages/license/src/getLicenseLimit.spec.ts @@ -7,7 +7,7 @@ describe('Marketplace Restrictions', () => { const LicenseManager = new LicenseImp(); expect(getAppsConfig.call(LicenseManager)).toEqual({ - maxPrivateApps: 3, + maxPrivateApps: 0, maxMarketplaceApps: 5, }); }); diff --git a/ee/packages/license/src/license.spec.ts b/ee/packages/license/src/license.spec.ts index 406dfd696e2e1..229b7e7097809 100644 --- a/ee/packages/license/src/license.spec.ts +++ b/ee/packages/license/src/license.spec.ts @@ -291,7 +291,7 @@ describe('License.getInfo', () => { }) ).limits, ).toMatchObject({ - privateApps: { max: 3 }, + privateApps: { max: 0 }, marketplaceApps: { max: 5 }, }); }); diff --git a/ee/packages/license/src/validation/validateDefaultLimits.ts b/ee/packages/license/src/validation/validateDefaultLimits.ts index 4f48d4e7ebe40..cc39635fa988f 100644 --- a/ee/packages/license/src/validation/validateDefaultLimits.ts +++ b/ee/packages/license/src/validation/validateDefaultLimits.ts @@ -11,7 +11,7 @@ export const defaultLimits: { privateApps: [ { behavior: 'prevent_action', - max: 3, + max: 0, }, ], marketplaceApps: [ From 7f2c829adfdeb7f0a3383d15627ede5c0f9a3d6f Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:26:24 -0300 Subject: [PATCH 47/85] fix!: Private apps are always auto enabled when updated (#33417) * fix: Private apps are auto-enabled when updated regardless of any condition --- .changeset/selfish-experts-develop.md | 6 ++ .../server/apps/communication/websockets.ts | 7 +- .../server/services/apps-engine/service.ts | 7 +- .../e2e/apps/private-apps-upload.spec.ts | 77 +++++++++++++++++++ .../tests/e2e/page-objects/marketplace.ts | 8 ++ packages/apps-engine/src/server/AppManager.ts | 18 ++++- 6 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 .changeset/selfish-experts-develop.md diff --git a/.changeset/selfish-experts-develop.md b/.changeset/selfish-experts-develop.md new file mode 100644 index 0000000000000..a48f7e90dba21 --- /dev/null +++ b/.changeset/selfish-experts-develop.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/apps-engine": patch +--- + +Fixes issue with previously disabled private apps being auto enabled on update diff --git a/apps/meteor/ee/server/apps/communication/websockets.ts b/apps/meteor/ee/server/apps/communication/websockets.ts index 83a1614271438..e6fc97464b8da 100644 --- a/apps/meteor/ee/server/apps/communication/websockets.ts +++ b/apps/meteor/ee/server/apps/communication/websockets.ts @@ -92,7 +92,12 @@ export class AppServerListener { const appPackage = await this.orch.getAppSourceStorage()!.fetch(storageItem); - await this.orch.getManager()!.updateLocal(storageItem, appPackage); + const isEnabled = AppStatusUtils.isEnabled(storageItem.status); + if (isEnabled) { + await this.orch.getManager()!.updateAndStartupLocal(storageItem, appPackage); + } else { + await this.orch.getManager()!.updateAndInitializeLocal(storageItem, appPackage); + } this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId); } diff --git a/apps/meteor/server/services/apps-engine/service.ts b/apps/meteor/server/services/apps-engine/service.ts index 19838fd8411d7..486a788563946 100644 --- a/apps/meteor/server/services/apps-engine/service.ts +++ b/apps/meteor/server/services/apps-engine/service.ts @@ -62,7 +62,12 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi return; } - await Apps.self?.getManager()?.updateLocal(storageItem, appPackage); + const isEnabled = AppStatusUtils.isEnabled(storageItem.status); + if (isEnabled) { + await Apps.self?.getManager()?.updateAndStartupLocal(storageItem, appPackage); + } else { + await Apps.self?.getManager()?.updateAndInitializeLocal(storageItem, appPackage); + } }); this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise => { diff --git a/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts b/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts index 05540dfc011fb..7350c500de6ac 100644 --- a/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts +++ b/apps/meteor/tests/e2e/apps/private-apps-upload.spec.ts @@ -32,6 +32,56 @@ test.describe.serial('Private apps upload', () => { await page.getByRole('button', { name: 'Agree' }).click(); await expect(poMarketplace.appStatusTag).toHaveText('Enabled'); }); + + test('expect to allow admin to update a enabled private app in EE, which should remain enabled', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + + await poMarketplace.btnUploadPrivateApp.click(); + await expect(poMarketplace.btnInstallPrivateApp).toBeDisabled(); + + await poMarketplace.btnUploadPrivateAppFile.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles('./tests/e2e/fixtures/files/test-app_0.0.1.zip'); + + await expect(poMarketplace.btnInstallPrivateApp).toBeEnabled(); + await poMarketplace.btnInstallPrivateApp.click(); + await poMarketplace.btnConfirmAppUpdate.click(); + await page.getByRole('button', { name: 'Agree' }).click(); + + await page.goto('/marketplace/private'); + await poMarketplace.lastAppRow.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Enabled'); + }); + + test('expect to allow disabling a recently installed private app in EE', async () => { + await poMarketplace.lastAppRow.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Enabled'); + await poMarketplace.appMenu.click(); + await expect(poMarketplace.btnDisableApp).toBeEnabled(); + await poMarketplace.btnDisableApp.click(); + await poMarketplace.btnConfirmAppUpdate.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Disabled'); + }); + + test('expect to allow admin to update a disabled private app in EE, which should remain disabled', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + + await poMarketplace.btnUploadPrivateApp.click(); + await expect(poMarketplace.btnInstallPrivateApp).toBeDisabled(); + + await poMarketplace.btnUploadPrivateAppFile.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles('./tests/e2e/fixtures/files/test-app_0.0.1.zip'); + + await expect(poMarketplace.btnInstallPrivateApp).toBeEnabled(); + await poMarketplace.btnInstallPrivateApp.click(); + await poMarketplace.btnConfirmAppUpdate.click(); + await page.getByRole('button', { name: 'Agree' }).click(); + + await page.goto('/marketplace/private'); + await poMarketplace.lastAppRow.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Disabled'); + }); }); test.describe('Community Edition', () => { @@ -66,5 +116,32 @@ test.describe.serial('Private apps upload', () => { await poMarketplace.appMenu.click(); await expect(poMarketplace.btnEnableApp).toBeDisabled(); }); + + test('expect updated private app in CE to be kept as disabled', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + + await poMarketplace.btnUploadPrivateApp.click(); + await expect(poMarketplace.btnConfirmAppUploadModal).toBeEnabled(); + await poMarketplace.btnConfirmAppUploadModal.click(); + + await expect(poMarketplace.btnInstallPrivateApp).toBeDisabled(); + await poMarketplace.btnUploadPrivateAppFile.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles('./tests/e2e/fixtures/files/test-app_0.0.1.zip'); + + await expect(poMarketplace.btnInstallPrivateApp).toBeEnabled(); + await poMarketplace.btnInstallPrivateApp.click(); + + await expect(poMarketplace.confirmAppUploadModalTitle).toHaveText('Private apps limit reached'); + await expect(poMarketplace.btnConfirmAppUploadModal).toBeEnabled(); + await poMarketplace.btnConfirmAppUploadModal.click(); + + await poMarketplace.btnConfirmAppUpdate.click(); + + await page.getByRole('button', { name: 'Agree' }).click(); + await page.goto('/marketplace/private'); + await poMarketplace.lastAppRow.click(); + await expect(poMarketplace.appStatusTag).toHaveText('Disabled'); + }); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/marketplace.ts b/apps/meteor/tests/e2e/page-objects/marketplace.ts index bd66e1bad203c..3132ccaf1a1a7 100644 --- a/apps/meteor/tests/e2e/page-objects/marketplace.ts +++ b/apps/meteor/tests/e2e/page-objects/marketplace.ts @@ -42,4 +42,12 @@ export class Marketplace { get btnEnableApp(): Locator { return this.page.getByRole('menuitem', { name: 'Enable' }); } + + get btnDisableApp(): Locator { + return this.page.getByRole('menuitem', { name: 'Disable' }); + } + + get btnConfirmAppUpdate(): Locator { + return this.page.locator('role=button[name="Yes"]'); + } } diff --git a/packages/apps-engine/src/server/AppManager.ts b/packages/apps-engine/src/server/AppManager.ts index 69a264f29c1d9..60a1f97fcc742 100644 --- a/packages/apps-engine/src/server/AppManager.ts +++ b/packages/apps-engine/src/server/AppManager.ts @@ -720,7 +720,12 @@ export class AppManager { aff.setApp(app); if (updateOptions.loadApp) { - await this.updateLocal(stored, app); + const shouldEnableApp = AppStatusUtils.isEnabled(old.status); + if (shouldEnableApp) { + await this.updateAndStartupLocal(stored, app); + } else { + await this.updateAndInitializeLocal(stored, app); + } await this.bridges .getAppActivationBridge() @@ -742,7 +747,7 @@ export class AppManager { * With an instance of a ProxiedApp, start it up and replace * the reference in the local app collection */ - public async updateLocal(stored: IAppStorageItem, appPackageOrInstance: ProxiedApp | Buffer) { + async updateLocal(stored: IAppStorageItem, appPackageOrInstance: ProxiedApp | Buffer): Promise { const app = await (async () => { if (appPackageOrInstance instanceof Buffer) { const parseResult = await this.getParser().unpackageApp(appPackageOrInstance); @@ -761,10 +766,19 @@ export class AppManager { await this.purgeAppConfig(app, { keepScheduledJobs: true }); this.apps.set(app.getID(), app); + return app; + } + public async updateAndStartupLocal(stored: IAppStorageItem, appPackageOrInstance: ProxiedApp | Buffer) { + const app = await this.updateLocal(stored, appPackageOrInstance); await this.runStartUpProcess(stored, app, false, true); } + public async updateAndInitializeLocal(stored: IAppStorageItem, appPackageOrInstance: ProxiedApp | Buffer) { + const app = await this.updateLocal(stored, appPackageOrInstance); + await this.initializeApp(stored, app, true, true); + } + public getLanguageContent(): { [key: string]: object } { const langs: { [key: string]: object } = {}; From 872143103082f0bdc0228be6345c18b2a0fcab8c Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:17:57 -0300 Subject: [PATCH 48/85] chore: tag apps as migrated when downgrading to CE (#33439) --- apps/meteor/ee/server/apps/orchestrator.js | 7 ++-- .../ee/server/startup/apps/trialExpiration.ts | 9 +++-- packages/apps-engine/src/server/AppManager.ts | 35 +++++++++++++++++++ .../apps-engine/src/server/IGetAppsFilter.ts | 3 ++ packages/apps-engine/src/server/ProxiedApp.ts | 6 +++- 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/apps/meteor/ee/server/apps/orchestrator.js b/apps/meteor/ee/server/apps/orchestrator.js index c252579138cb0..76b660e3fee31 100644 --- a/apps/meteor/ee/server/apps/orchestrator.js +++ b/apps/meteor/ee/server/apps/orchestrator.js @@ -197,10 +197,11 @@ export class AppServerOrchestrator { this._rocketchatLogger.info(`Loaded the Apps Framework and loaded a total of ${appCount} Apps!`); } - async disableApps() { - const apps = await this.getManager().get(); + async migratePrivateApps() { + const apps = await this.getManager().get({ installationSource: 'private' }); - await Promise.all(apps.map((app) => this.getManager().disable(app.getID()))); + await Promise.all(apps.map((app) => this.getManager().migrate(app.getID()))); + await Promise.all(apps.map((app) => this.getNotifier().appUpdated(app.getID()))); } async unload() { diff --git a/apps/meteor/ee/server/startup/apps/trialExpiration.ts b/apps/meteor/ee/server/startup/apps/trialExpiration.ts index e6bb9e47c7491..3c4d802916474 100644 --- a/apps/meteor/ee/server/startup/apps/trialExpiration.ts +++ b/apps/meteor/ee/server/startup/apps/trialExpiration.ts @@ -1,9 +1,12 @@ import { License } from '@rocket.chat/license'; import { Meteor } from 'meteor/meteor'; -Meteor.startup(() => { +Meteor.startup(async () => { + const { Apps } = await import('../../apps'); License.onInvalidateLicense(async () => { - const { Apps } = await import('../../apps'); - void Apps.disableApps(); + void Apps.migratePrivateApps(); + }); + License.onRemoveLicense(async () => { + void Apps.migratePrivateApps(); }); }); diff --git a/packages/apps-engine/src/server/AppManager.ts b/packages/apps-engine/src/server/AppManager.ts index 60a1f97fcc742..3fb6248743778 100644 --- a/packages/apps-engine/src/server/AppManager.ts +++ b/packages/apps-engine/src/server/AppManager.ts @@ -400,6 +400,10 @@ export class AppManager { rls = rls.filter((rl) => filter.ids.includes(rl.getID())); } + if (typeof filter.installationSource !== 'undefined') { + rls = rls.filter((rl) => rl.getInstallationSource() === filter.installationSource); + } + if (typeof filter.name === 'string') { rls = rls.filter((rl) => rl.getName() === filter.name); } else if (filter.name instanceof RegExp) { @@ -492,6 +496,37 @@ export class AppManager { return true; } + public async migrate(id: string): Promise { + const app = this.apps.get(id); + + if (!app) { + throw new Error(`No App by the id "${id}" exists.`); + } + + await app.call(AppMethod.ONUPDATE).catch((e) => console.warn('Error while migrating:', e)); + + await this.purgeAppConfig(app, { keepScheduledJobs: true }); + + const storageItem = await this.appMetadataStorage.retrieveOne(id); + + app.getStorageItem().marketplaceInfo = storageItem.marketplaceInfo; + await app.validateLicense().catch(); + + storageItem.migrated = true; + storageItem.signature = await this.getSignatureManager().signApp(storageItem); + // This is async, but we don't care since it only updates in the database + // and it should not mutate any properties we care about + const stored = await this.appMetadataStorage.update(storageItem).catch(); + + await this.updateLocal(stored, app); + await this.bridges + .getAppActivationBridge() + .doAppUpdated(app) + .catch(() => {}); + + return true; + } + public async addLocal(appId: string): Promise { const storageItem = await this.appMetadataStorage.retrieveOne(appId); diff --git a/packages/apps-engine/src/server/IGetAppsFilter.ts b/packages/apps-engine/src/server/IGetAppsFilter.ts index 4c2577ab0191e..dc33575485529 100644 --- a/packages/apps-engine/src/server/IGetAppsFilter.ts +++ b/packages/apps-engine/src/server/IGetAppsFilter.ts @@ -1,6 +1,9 @@ +import type { AppInstallationSource } from './storage'; + export interface IGetAppsFilter { ids?: Array; name?: string | RegExp; enabled?: boolean; disabled?: boolean; + installationSource?: AppInstallationSource; } diff --git a/packages/apps-engine/src/server/ProxiedApp.ts b/packages/apps-engine/src/server/ProxiedApp.ts index 86e54e802b2ea..e2605c8375ded 100644 --- a/packages/apps-engine/src/server/ProxiedApp.ts +++ b/packages/apps-engine/src/server/ProxiedApp.ts @@ -8,7 +8,7 @@ import { AppConsole } from './logging'; import { AppLicenseValidationResult } from './marketplace/license'; import type { AppsEngineRuntime } from './runtime/AppsEngineRuntime'; import { JSONRPC_METHOD_NOT_FOUND, type DenoRuntimeSubprocessController } from './runtime/deno/AppsEngineDenoRuntime'; -import type { IAppStorageItem } from './storage'; +import type { AppInstallationSource, IAppStorageItem } from './storage'; export class ProxiedApp { private previousStatus: AppStatus; @@ -108,6 +108,10 @@ export class ProxiedApp { return this.storageItem.id; } + public getInstallationSource(): AppInstallationSource { + return this.storageItem.installationSource; + } + public getVersion(): string { return this.storageItem.info.version; } From 3d62bafd23529482a6ce4161edcec2e74d2c4991 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:34:34 -0300 Subject: [PATCH 49/85] chore!: Tag private apps as migrated (exempt from CE limitations) when moving to the next major (#33420) * feat: Add migration to grandfather enabled private apps --- .../ee/server/apps/storage/AppRealStorage.ts | 9 +++++ apps/meteor/server/models/raw/AppLogsModel.ts | 4 +- .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v307.ts | 40 +++++++++++++++++++ .../src/server/storage/AppMetadataStorage.ts | 2 + .../tests/test-data/storage/storage.ts | 16 ++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 apps/meteor/server/startup/migrations/v307.ts diff --git a/apps/meteor/ee/server/apps/storage/AppRealStorage.ts b/apps/meteor/ee/server/apps/storage/AppRealStorage.ts index cceb70da9e960..45e380e02f460 100644 --- a/apps/meteor/ee/server/apps/storage/AppRealStorage.ts +++ b/apps/meteor/ee/server/apps/storage/AppRealStorage.ts @@ -36,6 +36,15 @@ export class AppRealStorage extends AppMetadataStorage { return items; } + public async retrieveAllPrivate(): Promise> { + const docs = await this.db.find({ installationSource: 'private' }).toArray(); + const items = new Map(); + + docs.forEach((i) => items.set(i.id, i)); + + return items; + } + public async update(item: IAppStorageItem): Promise { await this.db.updateOne({ id: item.id }, { $set: item }); return this.retrieveOne(item.id); diff --git a/apps/meteor/server/models/raw/AppLogsModel.ts b/apps/meteor/server/models/raw/AppLogsModel.ts index 513a473576ee3..f9f7db79d86c5 100644 --- a/apps/meteor/server/models/raw/AppLogsModel.ts +++ b/apps/meteor/server/models/raw/AppLogsModel.ts @@ -13,7 +13,9 @@ export class AppsLogsModel extends BaseRaw implements IAppLogsModel { } async resetTTLIndex(expireAfterSeconds: number): Promise { - await this.col.dropIndex('_updatedAt_1'); + if (await this.col.indexExists('_updatedAt_1')) { + await this.col.dropIndex('_updatedAt_1'); + } await this.col.createIndex({ _updatedAt: 1 }, { expireAfterSeconds }); } } diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index e7efdc89ebde1..ca749c3c2086f 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -39,5 +39,6 @@ import './v303'; import './v304'; import './v305'; import './v306'; +import './v307'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v307.ts b/apps/meteor/server/startup/migrations/v307.ts new file mode 100644 index 0000000000000..240842229b15c --- /dev/null +++ b/apps/meteor/server/startup/migrations/v307.ts @@ -0,0 +1,40 @@ +import { Apps } from '@rocket.chat/apps'; +import type { AppSignatureManager } from '@rocket.chat/apps-engine/server/managers/AppSignatureManager'; +import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; +import { License } from '@rocket.chat/license'; + +import type { AppRealStorage } from '../../../ee/server/apps/storage'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 307, + name: "Mark all installed private apps as 'migrated'", + async up() { + const isEE = License.hasValidLicense(); + if (isEE) { + return; + } + + if (!Apps.self) { + throw new Error('Apps Orchestrator not registered.'); + } + + Apps.initialize(); + + const sigMan = Apps.getManager()?.getSignatureManager() as AppSignatureManager; + const appsStorage = Apps.getStorage() as AppRealStorage; + const apps = await appsStorage.retrieveAllPrivate(); + + for await (const app of apps.values()) { + const updatedApp = { + ...app, + migrated: true, + } as IAppStorageItem; + + await appsStorage.update({ + ...updatedApp, + signature: await sigMan.signApp(updatedApp), + }); + } + }, +}); diff --git a/packages/apps-engine/src/server/storage/AppMetadataStorage.ts b/packages/apps-engine/src/server/storage/AppMetadataStorage.ts index 51d84e19b390a..0448fb535755e 100644 --- a/packages/apps-engine/src/server/storage/AppMetadataStorage.ts +++ b/packages/apps-engine/src/server/storage/AppMetadataStorage.ts @@ -13,6 +13,8 @@ export abstract class AppMetadataStorage { public abstract retrieveAll(): Promise>; + public abstract retrieveAllPrivate(): Promise>; + public abstract update(item: IAppStorageItem): Promise; public abstract remove(id: string): Promise<{ success: boolean }>; diff --git a/packages/apps-engine/tests/test-data/storage/storage.ts b/packages/apps-engine/tests/test-data/storage/storage.ts index bbb49c590812e..b87a4a2abe7e0 100644 --- a/packages/apps-engine/tests/test-data/storage/storage.ts +++ b/packages/apps-engine/tests/test-data/storage/storage.ts @@ -65,6 +65,22 @@ export class TestsAppStorage extends AppMetadataStorage { }); } + public retrieveAllPrivate(): Promise> { + return new Promise((resolve, reject) => { + this.db.find({ installationSource: 'private' }, (err: Error, docs: Array) => { + if (err) { + reject(err); + } else { + const items = new Map(); + + docs.forEach((i) => items.set(i.id, i)); + + resolve(items); + } + }); + }); + } + public update(item: IAppStorageItem): Promise { return new Promise((resolve, reject) => { this.db.update({ id: item.id }, item, {}, (err: Error, numOfUpdated: number) => { From fa904e043855d295ffe458b82a29ce8fa1bc81df Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 7 Oct 2024 15:12:09 -0300 Subject: [PATCH 50/85] chore!: Grandfathered private apps texts (#33468) * chore: updates grandfathered private app texts * chore: removes outdated translations * fix: applies code review requested changes Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --------- Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- packages/i18n/src/locales/en.i18n.json | 6 +++--- packages/i18n/src/locales/fi.i18n.json | 1 - packages/i18n/src/locales/hi-IN.i18n.json | 1 - packages/i18n/src/locales/sv.i18n.json | 2 -- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index eae11406ed5e1..6288757202b1d 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2578,7 +2578,7 @@ "GoogleTagManager_id": "Google Tag Manager Id", "Got_it": "Got it", "Government": "Government", - "Grandfathered_app": "Grandfathered app – this app is exempt from the app limit policy", + "Grandfathered_app": "App exempt from app limit policy", "Graphql_CORS": "GraphQL CORS", "Graphql_Enabled": "GraphQL Enabled", "Graphql_Subscription_Port": "GraphQL Subscription Port", @@ -6459,8 +6459,8 @@ "cloud.RegisterWorkspace_Setup_Terms_Privacy": "I agree with <1>Terms and Conditions and <3>Privacy Policy", "Larger_amounts_of_active_connections": "For larger amounts of active connections you can consider our <1>multiple instance solutions.", "Uninstall_grandfathered_app": "Uninstall {{appName}}?", - "App_will_lose_grandfathered_status": "**This app will lose its grandfathered status.** \n \nWorkspaces on Community can have up to {{limit}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.", - "App_will_lose_grandfathered_status_private": "**This app will lose its grandfathered status.** \n \nBecause Community workspaces cannot enable private apps, this workspace will require a premium plan in order to enable this app again in future.", + "App_will_lose_grandfathered_status": "**This app will lose its app limit policy exemption.** \n \nWorkspaces on Community can have up to {{limit}} apps enabled. Uninstalling this app will cause it to lose its exemption policy.", + "App_will_lose_grandfathered_status_private": "**This app will lose its app limit policy exemption.** \n \nBecause Community workspaces cannot enable private apps, this workspace will require a premium plan in order to enable this app again in future.", "All_rooms": "All rooms", "All_visible": "All visible", "all": "all", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index 40387d02e4b14..be26b145f766f 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -5740,7 +5740,6 @@ "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Hyväksyn <1>käyttöehdot ja <3>tietosuojakäytännön", "Larger_amounts_of_active_connections": "Jos tarvitset enemmän aktiivisia yhteyksiä, ota harkintaan", "Uninstall_grandfathered_app": "Poistetaanko {{appName}}?", - "App_will_lose_grandfathered_status": "**Tämä {{context}}sovellus menettää aikaisemmin käytetössä olleen sovelluksen tilansa.** \n \nYhteisöversion työtiloissa voi olla käytössä enintään {{limit}} {{context}} sovellusta. aikaisemmin Aikaisemmin käytössä olleet sovellukset lasketaan mukaan rajoitukseen, mutta rajoitusta ei sovelleta niihin.", "Theme_Appearence": "Teeman ulkoasu", "Enterprise": "Yritys", "UpgradeToGetMore_engagement-dashboard_Title": "Analytics" diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index 2f8c20ae63521..ce5010c4a2c6e 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -6046,7 +6046,6 @@ "cloud.RegisterWorkspace_Setup_Terms_Privacy": "मैं <1>नियम एवं शर्तें और <3>गोपनीयता नीति से सहमत हूं", "Larger_amounts_of_active_connections": "बड़ी मात्रा में सक्रिय कनेक्शन के लिए आप हमारे <1>मल्टीपल इंस्टेंस समाधान पर विचार कर सकते हैं।", "Uninstall_grandfathered_app": "{{appName}} अनइंस्टॉल करें?", - "App_will_lose_grandfathered_status": "**यह {{context}} ऐप अपना दादा दर्जा खो देगा।**\n \nसमुदाय पर कार्यस्थानों में अधिकतम {{limit}} {{context}} ऐप्स सक्षम हो सकते हैं। दादाजी ऐप्स को सीमा में गिना जाता है लेकिन सीमा उन पर लागू नहीं होती है।", "All_rooms": "सभी कमरे", "All_visible": "सब दिख रहा है", "Filter_by_room": "कमरे के प्रकार के अनुसार फ़िल्टर करें", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index 774dde2b4f17c..2606c483a3c60 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -5745,8 +5745,6 @@ "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Jag godkänner <1>villkoren och <3>integritetspolicyn", "Larger_amounts_of_active_connections": "För större mängder aktiva anslutningar kan du överväga vår", "Uninstall_grandfathered_app": "Avinstallera {{appName}}?", - "App_will_lose_grandfathered_status": "**Denna {{context}}-app kommer att förlora sin status som gammal app.** \n \nArbetsytorna i Community Edition kan ha upp till {{limit}} {{context}}-appar aktiverade. Gamla appar inkluderas i gränsen, men gränsen tillämpas inte på dem.", - "Theme_Appearence": "Utseende för tema", "Enterprise": "Enterprise", "UpgradeToGetMore_engagement-dashboard_Title": "Analytics" } \ No newline at end of file From 4f6afdafdd3248638f7e8f63fcbe6be2e6cb7a22 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:27:03 -0300 Subject: [PATCH 51/85] fix!: LDAP sync triggers multiple cron jobs in case an invalid sync interval is provided (#32285) * fix: use settings' packageValue as a fallback to LDAP sync intervals * fix: add migration to update the packageValue of LDAP sync interval settings --------- Co-authored-by: Marcos Spessatto Defendi --- .changeset/sixty-vans-grab.md | 5 +++ apps/meteor/ee/server/configuration/ldap.ts | 6 +++- apps/meteor/package.json | 1 + .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v308.ts | 31 +++++++++++++++++++ yarn.lock | 8 +++++ 6 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .changeset/sixty-vans-grab.md create mode 100644 apps/meteor/server/startup/migrations/v308.ts diff --git a/.changeset/sixty-vans-grab.md b/.changeset/sixty-vans-grab.md new file mode 100644 index 0000000000000..97b33b207a7f4 --- /dev/null +++ b/.changeset/sixty-vans-grab.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Fixed issue with LDAP sync triggering multiple cron jobs in case an invalid sync interval is provided diff --git a/apps/meteor/ee/server/configuration/ldap.ts b/apps/meteor/ee/server/configuration/ldap.ts index 27c47fdec408a..055401f40b053 100644 --- a/apps/meteor/ee/server/configuration/ldap.ts +++ b/apps/meteor/ee/server/configuration/ldap.ts @@ -1,6 +1,8 @@ import type { IImportUser, ILDAPEntry, IUser } from '@rocket.chat/core-typings'; import { cronJobs } from '@rocket.chat/cron'; import { License } from '@rocket.chat/license'; +import { Settings } from '@rocket.chat/models'; +import { isValidCron } from 'cron-validator'; import { Meteor } from 'meteor/meteor'; import { settings } from '../../../app/settings/server'; @@ -28,7 +30,9 @@ Meteor.startup(async () => { } const settingValue = settings.get(intervalSetting); - const schedule = ldapIntervalValuesToCronMap[settingValue] ?? settingValue; + const schedule = + ldapIntervalValuesToCronMap[settingValue] ?? + (isValidCron(settingValue) ? settingValue : ((await Settings.findOneById(intervalSetting))?.packageValue as string)); if (schedule) { if (schedule !== lastSchedule && (await cronJobs.has(jobName))) { await cronJobs.remove(jobName); diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 1581d0e1d15e1..46cca9cceb2f1 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -323,6 +323,7 @@ "cookie-parser": "^1.4.7", "cors": "^2.8.5", "cron": "~1.8.2", + "cron-validator": "^1.3.1", "css-vars-ponyfill": "^2.4.9", "csv-parse": "^5.2.0", "date-fns": "^2.28.0", diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index ca749c3c2086f..71ef63582e5b2 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -40,5 +40,6 @@ import './v304'; import './v305'; import './v306'; import './v307'; +import './v308'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v308.ts b/apps/meteor/server/startup/migrations/v308.ts new file mode 100644 index 0000000000000..fc27fdd31868c --- /dev/null +++ b/apps/meteor/server/startup/migrations/v308.ts @@ -0,0 +1,31 @@ +import type { ISetting } from '@rocket.chat/core-typings'; +import { Settings } from '@rocket.chat/models'; +import { isValidCron } from 'cron-validator'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 308, + name: 'Update packageValue from LDAP interval settings', + async up() { + const newAvatarSyncPackageValue = '0 0 * * *'; + const newAutoLogoutPackageValue = '*/5 * * * *'; + const ldapAvatarSyncInterval = await Settings.findOneById>('LDAP_Background_Sync_Avatars_Interval', { + projection: { value: 1 }, + }); + const ldapAutoLogoutInterval = await Settings.findOneById>('LDAP_Sync_AutoLogout_Interval', { + projection: { value: 1 }, + }); + const isValidAvatarSyncInterval = ldapAvatarSyncInterval && isValidCron(ldapAvatarSyncInterval.value as string); + const isValidAutoLogoutInterval = ldapAutoLogoutInterval && isValidCron(ldapAutoLogoutInterval.value as string); + + await Settings.updateOne( + { _id: 'LDAP_Background_Sync_Avatars_Interval' }, + { $set: { packageValue: newAvatarSyncPackageValue, ...(!isValidAvatarSyncInterval && { value: newAvatarSyncPackageValue }) } }, + ); + await Settings.updateOne( + { _id: 'LDAP_Sync_AutoLogout_Interval' }, + { $set: { packageValue: newAutoLogoutPackageValue, ...(!isValidAutoLogoutInterval && { value: newAutoLogoutPackageValue }) } }, + ); + }, +}); diff --git a/yarn.lock b/yarn.lock index d6055fb1c90bc..707d8d18f4c5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9837,6 +9837,7 @@ __metadata: cookie-parser: ^1.4.7 cors: ^2.8.5 cron: ~1.8.2 + cron-validator: ^1.3.1 cross-env: ^7.0.3 css-vars-ponyfill: ^2.4.9 csv-parse: ^5.2.0 @@ -20687,6 +20688,13 @@ __metadata: languageName: node linkType: hard +"cron-validator@npm:^1.3.1": + version: 1.3.1 + resolution: "cron-validator@npm:1.3.1" + checksum: 82895b417bc35a96c8ad8501d2f236492403ba6e35c1112762e9573939eb15cd80bd0693d5ae95e3a15e15fc6442b1a6e6cc3dd0f740f0a3320b202082aeabec + languageName: node + linkType: hard + "cron@npm:~1.8.2": version: 1.8.2 resolution: "cron@npm:1.8.2" From c9d4439aadea891b3b6828fd45cc479dc88ef7a3 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:11:25 +0530 Subject: [PATCH 52/85] chore!: remove deprecated livechat:saveSurveyFeedback method (#33443) --- .changeset/rude-dodos-agree.md | 5 ++ apps/meteor/app/livechat/server/index.ts | 1 - .../server/methods/saveSurveyFeedback.ts | 57 ------------------- 3 files changed, 5 insertions(+), 58 deletions(-) create mode 100644 .changeset/rude-dodos-agree.md delete mode 100644 apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts diff --git a/.changeset/rude-dodos-agree.md b/.changeset/rude-dodos-agree.md new file mode 100644 index 0000000000000..cd8d737e71134 --- /dev/null +++ b/.changeset/rude-dodos-agree.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removed deprecated method `livechat:saveSurveyFeedback`. Moving forward use the endpoint `livechat/room.survey`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 38462ef56c844..e9ba895cbb929 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -42,7 +42,6 @@ import './methods/saveDepartment'; import './methods/saveDepartmentAgents'; import './methods/saveInfo'; import './methods/saveIntegration'; -import './methods/saveSurveyFeedback'; import './methods/saveTrigger'; import './methods/sendMessageLivechat'; import './methods/sendFileLivechatMessage'; diff --git a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts b/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts deleted file mode 100644 index 36fc3f775b9f2..0000000000000 --- a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatRooms, LivechatVisitors } from '@rocket.chat/models'; -import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; -import type { UpdateResult } from 'mongodb'; -import _ from 'underscore'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:saveSurveyFeedback'( - visitorToken: string, - visitorRoom: string, - formData: { - name: 'satisfaction' | 'agentKnowledge' | 'agentResposiveness' | 'agentFriendliness' | 'additionalFeedback'; - value: '1' | '2' | '3' | '4' | '5'; - }[], - ): UpdateResult | undefined; - } -} - -Meteor.methods({ - async 'livechat:saveSurveyFeedback'(visitorToken, visitorRoom, formData) { - methodDeprecationLogger.method('livechat:saveSurveyFeedback', '7.0.0'); - - check(visitorToken, String); - check(visitorRoom, String); - check(formData, [Match.ObjectIncluding({ name: String, value: String })]); - - const visitor = (await LivechatVisitors.getVisitorByToken(visitorToken)) ?? undefined; - const room = (await LivechatRooms.findOneById(visitorRoom)) ?? undefined; - - if (visitor !== undefined && room !== undefined && room.v !== undefined && room.v.token === visitor.token) { - const updateData: Partial< - Record< - 'satisfaction' | 'agentKnowledge' | 'agentResposiveness' | 'agentFriendliness' | 'additionalFeedback', - '1' | '2' | '3' | '4' | '5' - > - > = {}; - for (const item of formData) { - if ( - ['satisfaction', 'agentKnowledge', 'agentResposiveness', 'agentFriendliness'].includes(item.name) && - ['1', '2', '3', '4', '5'].includes(item.value) - ) { - updateData[item.name] = item.value; - } else if (item.name === 'additionalFeedback') { - updateData[item.name] = item.value; - } - } - if (!_.isEmpty(updateData)) { - return LivechatRooms.updateSurveyFeedbackById(room._id, updateData); - } - } - }, -}); From 052aa2e888d775176cde09fd88e2b08ec4600ca8 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:13:11 +0530 Subject: [PATCH 53/85] chore!: remove deprecated livechat:pageVisited method (#33427) --- .changeset/tricky-horses-swim.md | 5 +++++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/pageVisited.ts | 19 ------------------- 3 files changed, 5 insertions(+), 20 deletions(-) create mode 100644 .changeset/tricky-horses-swim.md delete mode 100644 apps/meteor/app/livechat/server/methods/pageVisited.ts diff --git a/.changeset/tricky-horses-swim.md b/.changeset/tricky-horses-swim.md new file mode 100644 index 0000000000000..570e10a32480d --- /dev/null +++ b/.changeset/tricky-horses-swim.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removed deprecated method `livechat:pageVisited`. Moving forward, use the `livechat/page.visited` endpoint. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index e9ba895cbb929..55b69c552f02f 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -26,7 +26,6 @@ import './methods/getAnalyticsChartData'; import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; import './methods/getRoutingConfig'; -import './methods/pageVisited'; import './methods/registerGuest'; import './methods/removeAgent'; import './methods/removeAllClosedRooms'; diff --git a/apps/meteor/app/livechat/server/methods/pageVisited.ts b/apps/meteor/app/livechat/server/methods/pageVisited.ts deleted file mode 100644 index 7c0864f27b74a..0000000000000 --- a/apps/meteor/app/livechat/server/methods/pageVisited.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:pageVisited'(token: string, room: string, pageInfo: { title: string; location: { href: string }; change: string }): void; - } -} - -Meteor.methods({ - async 'livechat:pageVisited'(token, room, pageInfo) { - methodDeprecationLogger.method('livechat:pageVisited', '7.0.0'); - await Livechat.savePageHistory(token, room, pageInfo); - }, -}); From 62610cd2abd4b38ca3facbbd94ce95ae006c8815 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:16:38 +0530 Subject: [PATCH 54/85] chore!: remove deprecated method livechat:saveInfo (#33447) --- .changeset/kind-eels-brush.md | 5 + apps/meteor/app/livechat/server/index.ts | 1 - .../app/livechat/server/methods/saveInfo.ts | 95 ------------------- 3 files changed, 5 insertions(+), 96 deletions(-) create mode 100644 .changeset/kind-eels-brush.md delete mode 100644 apps/meteor/app/livechat/server/methods/saveInfo.ts diff --git a/.changeset/kind-eels-brush.md b/.changeset/kind-eels-brush.md new file mode 100644 index 0000000000000..1b82e431a7b17 --- /dev/null +++ b/.changeset/kind-eels-brush.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:saveInfo`. Moving forward use the enpoint `livechat/room/save.info`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 55b69c552f02f..ef8fb5067e220 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -39,7 +39,6 @@ import './methods/saveAppearance'; import './methods/saveCustomField'; import './methods/saveDepartment'; import './methods/saveDepartmentAgents'; -import './methods/saveInfo'; import './methods/saveIntegration'; import './methods/saveTrigger'; import './methods/sendMessageLivechat'; diff --git a/apps/meteor/app/livechat/server/methods/saveInfo.ts b/apps/meteor/app/livechat/server/methods/saveInfo.ts deleted file mode 100644 index bb22c127effa5..0000000000000 --- a/apps/meteor/app/livechat/server/methods/saveInfo.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatRooms, Users } from '@rocket.chat/models'; -import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { callbacks } from '../../../../lib/callbacks'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat as LivechatTyped } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:saveInfo'( - guestData: { - _id: string; - name?: string; - email?: string; - phone?: string; - livechatData?: Record; - }, - roomData: { - _id: string; - topic?: string; - tags?: string[]; - livechatData?: Record; - priorityId?: string; - slaId?: string; - }, - ): boolean; - } -} - -Meteor.methods({ - async 'livechat:saveInfo'(guestData, roomData) { - methodDeprecationLogger.method('livechat:saveInfo', '7.0.0', 'Use "livechat/room.saveInfo" endpoint instead.'); - const userId = Meteor.userId(); - - if (!userId || !(await hasPermissionAsync(userId, 'view-l-room'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveInfo' }); - } - - check( - guestData, - Match.ObjectIncluding({ - _id: String, - name: Match.Optional(String), - email: Match.Optional(String), - phone: Match.Optional(String), - livechatData: Match.Optional(Object), - }), - ); - - check( - roomData, - Match.ObjectIncluding({ - _id: String, - topic: Match.Optional(String), - tags: Match.Optional([String]), - livechatData: Match.Optional(Object), - priorityId: Match.Optional(String), - slaId: Match.Optional(String), - }), - ); - - const room = await LivechatRooms.findOneById(roomData._id); - if (!room || !isOmnichannelRoom(room)) { - throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'livechat:saveInfo' }); - } - - if ((!room.servedBy || room.servedBy._id !== userId) && !(await hasPermissionAsync(userId, 'save-others-livechat-room-info'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveInfo' }); - } - - if (room.sms) { - delete guestData.phone; - } - - // Since the endpoint is going to be deprecated, we can live with this for a while - // @ts-expect-error - type is right, but `check` converts it to something else - await Promise.allSettled([LivechatTyped.saveGuest(guestData as any, userId), LivechatTyped.saveRoomInfo(roomData)]); - - const user = await Users.findOne({ _id: userId }, { projection: { _id: 1, username: 1 } }); - - setImmediate(async () => { - void callbacks.run('livechat.saveInfo', await LivechatRooms.findOneById(roomData._id), { - user, - oldRoom: room, - }); - }); - - return true; - }, -}); From 3fc67cf09b7ab2e712678683b1f7d1797669f56b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:18:02 +0530 Subject: [PATCH 55/85] chore!: remove deprecated endpoint pw.getPolicyReset (#33474) --- .changeset/fair-bees-wash.md | 6 +++ apps/meteor/app/api/server/v1/misc.ts | 31 ------------- .../tests/end-to-end/api/miscellaneous.ts | 43 ------------------- packages/rest-typings/src/v1/misc.ts | 22 ---------- 4 files changed, 6 insertions(+), 96 deletions(-) create mode 100644 .changeset/fair-bees-wash.md diff --git a/.changeset/fair-bees-wash.md b/.changeset/fair-bees-wash.md new file mode 100644 index 0000000000000..fb35ae55eb2bb --- /dev/null +++ b/.changeset/fair-bees-wash.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': major +'@rocket.chat/meteor': major +--- + +Removes deprecated endpoint `pw.getPolicyReset`. Moving forward, use the `pw.getPolicy` endpoint. diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index d0c8ebe5f1b11..5d7505074a018 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -10,7 +10,6 @@ import { isMethodCallAnonProps, isFingerprintProps, isMeteorCall, - validateParamsPwGetPolicyRest, } from '@rocket.chat/rest-typings'; import { escapeHTML } from '@rocket.chat/string-helpers'; import EJSON from 'ejson'; @@ -408,36 +407,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'pw.getPolicyReset', - { - authRequired: false, - validateParams: validateParamsPwGetPolicyRest, - deprecation: { - version: '7.0.0', - alternatives: ['pw.getPolicy'], - }, - }, - { - async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - token: String, - }), - ); - const { token } = this.queryParams; - - const user = await Users.findOneByResetToken(token, { projection: { _id: 1 } }); - if (!user) { - return API.v1.unauthorized(); - } - - return API.v1.success(passwordPolicy.getPasswordPolicy()); - }, - }, -); - /** * @openapi * /api/v1/stdout.queue: diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index b68f3635560b0..f664882cb3859 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -662,49 +662,6 @@ describe('miscellaneous', () => { }); }); - describe('/pw.getPolicyReset', () => { - it('should fail if no token provided', (done) => { - void request - .get(api('pw.getPolicyReset')) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'invalid-params'); - }) - .end(done); - }); - - it('should fail if no token is invalid format', (done) => { - void request - .get(api('pw.getPolicyReset')) - .query({ token: '123' }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - - // not sure we have a way to get the reset token, looks like it is only sent via email by Meteor - it.skip('should return policies if correct token is provided', (done) => { - void request - .get(api('pw.getPolicyReset')) - .query({ token: '' }) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('enabled'); - expect(res.body).to.have.property('policy').and.to.be.an('array'); - }) - .end(done); - }); - }); - describe('[/stdout.queue]', () => { let testUser: TestUser; let testUsername: string; diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index cc06e1cc330a0..de327085df8b4 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -180,21 +180,6 @@ const FingerprintSchema = { export const isFingerprintProps = ajv.compile(FingerprintSchema); -type PwGetPolicyReset = { token: string }; - -const PwGetPolicyResetSchema = { - type: 'object', - properties: { - token: { - type: 'string', - }, - }, - required: ['token'], - additionalProperties: false, -}; - -export const validateParamsPwGetPolicyRest = ajv.compile(PwGetPolicyResetSchema); - export type MiscEndpoints = { '/v1/stdout.queue': { GET: () => { @@ -226,13 +211,6 @@ export type MiscEndpoints = { }; }; - '/v1/pw.getPolicyReset': { - GET: (params: PwGetPolicyReset) => { - enabled: boolean; - policy: [name: string, options?: Record][]; - }; - }; - '/v1/method.call/:method': { POST: (params: { message: string }) => { message: string; From 8141267d4acc02f63314b8ddde6b62980c6fbc97 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:19:44 +0530 Subject: [PATCH 56/85] chore!: remove deprecated method getPasswordPolicy (#33473) --- .changeset/heavy-apricots-wash.md | 5 +++ .../server/methods/getPasswordPolicy.ts | 37 ------------------- apps/meteor/server/methods/index.ts | 1 - 3 files changed, 5 insertions(+), 38 deletions(-) create mode 100644 .changeset/heavy-apricots-wash.md delete mode 100644 apps/meteor/server/methods/getPasswordPolicy.ts diff --git a/.changeset/heavy-apricots-wash.md b/.changeset/heavy-apricots-wash.md new file mode 100644 index 0000000000000..fe0e221cfa433 --- /dev/null +++ b/.changeset/heavy-apricots-wash.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `getPasswordPolicy`. Moving forward, use the endpoint `pw.getPolicy`. diff --git a/apps/meteor/server/methods/getPasswordPolicy.ts b/apps/meteor/server/methods/getPasswordPolicy.ts deleted file mode 100644 index 483938716093d..0000000000000 --- a/apps/meteor/server/methods/getPasswordPolicy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Users } from '@rocket.chat/models'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { passwordPolicy } from '../../app/lib/server'; -import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - getPasswordPolicy(params: { token: string }): { - enabled: boolean; - policy: [name: TranslationKey, options?: Record][]; - }; - } -} - -Meteor.methods({ - async getPasswordPolicy(params) { - methodDeprecationLogger.method('getPasswordPolicy', '7.0.0'); - - check(params, { token: String }); - - const user = await Users.findOne({ 'services.password.reset.token': params.token }); - if (!user && !Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'getPasswordPolicy', - }); - } - return passwordPolicy.getPasswordPolicy() as { - enabled: boolean; - policy: [name: TranslationKey, options?: Record][]; - }; - }, -}); diff --git a/apps/meteor/server/methods/index.ts b/apps/meteor/server/methods/index.ts index 6ae87421c11af..dd738080e05d6 100644 --- a/apps/meteor/server/methods/index.ts +++ b/apps/meteor/server/methods/index.ts @@ -13,7 +13,6 @@ import './createDirectMessage'; import './deleteFileMessage'; import './deleteUser'; import './getAvatarSuggestion'; -import './getPasswordPolicy'; import './getRoomById'; import './getRoomIdByNameOrId'; import './getRoomNameById'; From b7fae68521754390d0153d3d36a2904819d13ab0 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:20:32 +0530 Subject: [PATCH 57/85] chore!: remove deprecated method deleteMessage (#33472) --- .changeset/kind-clocks-smash.md | 5 + apps/meteor/app/lib/server/index.ts | 1 - .../app/lib/server/methods/deleteMessage.ts | 35 --- apps/meteor/client/lib/chats/ChatAPI.ts | 2 +- apps/meteor/client/lib/chats/data.ts | 18 +- .../lib/chats/flows/requestMessageDeletion.ts | 2 +- apps/meteor/tests/end-to-end/api/methods.ts | 278 +----------------- 7 files changed, 24 insertions(+), 317 deletions(-) create mode 100644 .changeset/kind-clocks-smash.md delete mode 100644 apps/meteor/app/lib/server/methods/deleteMessage.ts diff --git a/.changeset/kind-clocks-smash.md b/.changeset/kind-clocks-smash.md new file mode 100644 index 0000000000000..c108c9560a736 --- /dev/null +++ b/.changeset/kind-clocks-smash.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated `deleteMessage` method. Moving forward, use the `chat.delete` endpoint. diff --git a/apps/meteor/app/lib/server/index.ts b/apps/meteor/app/lib/server/index.ts index ecfdb2ed81cd1..f56827baeb8ac 100644 --- a/apps/meteor/app/lib/server/index.ts +++ b/apps/meteor/app/lib/server/index.ts @@ -17,7 +17,6 @@ import './methods/cleanRoomHistory'; import './methods/createChannel'; import './methods/createPrivateGroup'; import './methods/createToken'; -import './methods/deleteMessage'; import './methods/deleteUserOwnAccount'; import './methods/executeSlashCommandPreview'; import './startup/mentionUserNotInChannel'; diff --git a/apps/meteor/app/lib/server/methods/deleteMessage.ts b/apps/meteor/app/lib/server/methods/deleteMessage.ts deleted file mode 100644 index b0be64c245ef6..0000000000000 --- a/apps/meteor/app/lib/server/methods/deleteMessage.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { IMessage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { deleteMessageValidatingPermission } from '../functions/deleteMessage'; -import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - deleteMessage({ _id }: Pick): void; - } -} - -Meteor.methods({ - async deleteMessage(message) { - methodDeprecationLogger.method('deleteMessage', '7.0.0'); - - check( - message, - Match.ObjectIncluding({ - _id: String, - }), - ); - - const uid = Meteor.userId(); - - if (!uid) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'deleteMessage', - }); - } - return deleteMessageValidatingPermission(message, uid); - }, -}); diff --git a/apps/meteor/client/lib/chats/ChatAPI.ts b/apps/meteor/client/lib/chats/ChatAPI.ts index 30dd5909ee03b..325073d438373 100644 --- a/apps/meteor/client/lib/chats/ChatAPI.ts +++ b/apps/meteor/client/lib/chats/ChatAPI.ts @@ -79,7 +79,7 @@ export type DataAPI = { canUpdateMessage(message: IMessage): Promise; updateMessage(message: Pick & Partial>, previewUrls?: string[]): Promise; canDeleteMessage(message: IMessage): Promise; - deleteMessage(mid: IMessage['_id']): Promise; + deleteMessage(msgIdOrMsg: IMessage | IMessage['_id']): Promise; getDraft(mid: IMessage['_id'] | undefined): Promise; discardDraft(mid: IMessage['_id'] | undefined): Promise; saveDraft(mid: IMessage['_id'] | undefined, text: string): Promise; diff --git a/apps/meteor/client/lib/chats/data.ts b/apps/meteor/client/lib/chats/data.ts index 445f61f27226e..4f6263147ba6f 100644 --- a/apps/meteor/client/lib/chats/data.ts +++ b/apps/meteor/client/lib/chats/data.ts @@ -214,8 +214,22 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage return deleteAllowed && onTimeForDelete; }; - const deleteMessage = async (mid: IMessage['_id']): Promise => { - await sdk.call('deleteMessage', { _id: mid }); + const deleteMessage = async (msgIdOrMsg: IMessage | IMessage['_id']): Promise => { + let msgId: string; + let roomId: string; + if (typeof msgIdOrMsg === 'string') { + msgId = msgIdOrMsg; + const msg = await findMessageByID(msgId); + if (!msg) { + throw new Error('Message not found'); + } + roomId = msg.rid; + } else { + msgId = msgIdOrMsg._id; + roomId = msgIdOrMsg.rid; + } + + await sdk.rest.post('/v1/chat.delete', { msgId, roomId }); }; const drafts = new Map(); diff --git a/apps/meteor/client/lib/chats/flows/requestMessageDeletion.ts b/apps/meteor/client/lib/chats/flows/requestMessageDeletion.ts index d10086840529f..130266284b484 100644 --- a/apps/meteor/client/lib/chats/flows/requestMessageDeletion.ts +++ b/apps/meteor/client/lib/chats/flows/requestMessageDeletion.ts @@ -21,7 +21,7 @@ export const requestMessageDeletion = async (chat: ChatAPI, message: IMessage): dispatchToastMessage({ type: 'error', message: t('Message_deleting_blocked') }); return; } - await chat.data.deleteMessage(message._id); + await chat.data.deleteMessage(message); imperativeModal.close(); diff --git a/apps/meteor/tests/end-to-end/api/methods.ts b/apps/meteor/tests/end-to-end/api/methods.ts index e3c42389e506f..9e05fba88b4cf 100644 --- a/apps/meteor/tests/end-to-end/api/methods.ts +++ b/apps/meteor/tests/end-to-end/api/methods.ts @@ -2,7 +2,7 @@ import type { Credentials } from '@rocket.chat/api-client'; import type { IMessage, IRoom, IThreadMessage, IUser } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; import { expect } from 'chai'; -import { after, before, beforeEach, describe, it } from 'mocha'; +import { after, before, describe, it } from 'mocha'; import { api, credentials, getCredentials, methodCall, request } from '../../data/api-data'; import { sendSimpleMessage } from '../../data/chat.helper'; @@ -2519,282 +2519,6 @@ describe('Meteor.methods', () => { }); }); - describe('[@deleteMessage]', () => { - let rid: IRoom['_id']; - let messageId: IMessage['_id']; - - before('create room', (done) => { - const channelName = `methods-test-channel-${Date.now()}`; - void request - .post(api('groups.create')) - .set(credentials) - .send({ - name: channelName, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('group._id'); - expect(res.body).to.have.nested.property('group.name', channelName); - expect(res.body).to.have.nested.property('group.t', 'p'); - expect(res.body).to.have.nested.property('group.msgs', 0); - rid = res.body.group._id; - }) - .end(done); - }); - - beforeEach('send message with URL', (done) => { - void request - .post(methodCall('sendMessage')) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'sendMessage', - params: [ - { - _id: `${Date.now() + Math.random()}`, - rid, - msg: 'test message with https://github.com', - }, - ], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('result').that.is.an('object'); - expect(data.result).to.have.a.property('urls').that.is.an('array'); - expect(data.result.urls[0].url).to.equal('https://github.com'); - messageId = data.result._id; - }) - .end(done); - }); - - after(() => - Promise.all([ - deleteRoom({ type: 'p', roomId: rid }), - updatePermission('bypass-time-limit-edit-and-delete', ['bot', 'app']), - updateSetting('Message_AllowEditing_BlockEditInMinutes', 0), - ]), - ); - - it('should delete a message', (done) => { - void request - .post(methodCall('deleteMessage')) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'deleteMessage', - params: [{ _id: messageId, rid }], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('msg', 'result'); - expect(data).to.have.a.property('id', 'id'); - }) - .end(done); - }); - - it('should delete a message when bypass time limits permission is enabled', async () => { - await Promise.all([ - updatePermission('bypass-time-limit-edit-and-delete', ['admin']), - updateSetting('Message_AllowEditing_BlockEditInMinutes', 0.01), - ]); - - await request - .post(methodCall('deleteMessage')) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'deleteMessage', - params: [{ _id: messageId, rid }], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('msg', 'result'); - expect(data).to.have.a.property('id', 'id'); - }); - - await Promise.all([ - updatePermission('bypass-time-limit-edit-and-delete', ['bot', 'app']), - updateSetting('Message_AllowEditing_BlockEditInMinutes', 0), - ]); - }); - - describe('message deletion when user is not part of the room', () => { - let ridTestRoom: IRoom['_id']; - let messageIdTestRoom: IMessage['_id']; - let testUser: TestUser; - let testUserCredentials: Credentials; - - before('create room, add new owner, and leave room', async () => { - testUser = await createUser(); - testUserCredentials = await login(testUser.username, password); - const channelName = `methods-test-channel-${Date.now()}`; - - await request - .post(api('groups.create')) - .set(testUserCredentials) - .send({ - name: channelName, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('group._id'); - expect(res.body).to.have.nested.property('group.name', channelName); - expect(res.body).to.have.nested.property('group.t', 'p'); - expect(res.body).to.have.nested.property('group.msgs', 0); - ridTestRoom = res.body.group._id; - }); - - await request - .post(methodCall('sendMessage')) - .set(testUserCredentials) - .send({ - message: JSON.stringify({ - method: 'sendMessage', - params: [ - { - _id: `${Date.now() + Math.random()}`, - rid: ridTestRoom, - msg: 'just a random test message', - }, - ], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('result').that.is.an('object'); - messageIdTestRoom = data.result._id; - }); - - await request - .post(methodCall('addUsersToRoom')) - .set(testUserCredentials) - .send({ - message: JSON.stringify({ - method: 'addUsersToRoom', - params: [ - { - rid: ridTestRoom, - users: ['rocket.cat'], - }, - ], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - }); - - await request - .post(api('groups.addOwner')) - .set(testUserCredentials) - .send({ - roomId: ridTestRoom, - userId: 'rocket.cat', - }) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - }); - - await request - .post(api('groups.leave')) - .set(testUserCredentials) - .send({ - roomId: ridTestRoom, - }) - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - }); - }); - - it('should not delete a message if the user is no longer member of the room', async () => { - await request - .post(methodCall('deleteMessage')) - .set(testUserCredentials) - .send({ - message: JSON.stringify({ - method: 'deleteMessage', - params: [{ _id: messageIdTestRoom, rid: ridTestRoom }], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('msg', 'result'); - expect(data).to.have.a.property('id', 'id'); - expect(data.error).to.have.a.property('error', 'error-action-not-allowed'); - }); - }); - - it('should not delete a message if the user was never part of the room', async () => { - await request - .post(methodCall('deleteMessage')) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'deleteMessage', - params: [{ _id: messageIdTestRoom, rid: ridTestRoom }], - id: 'id', - msg: 'method', - }), - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.a.property('success', true); - expect(res.body).to.have.a.property('message').that.is.a('string'); - const data = JSON.parse(res.body.message); - expect(data).to.have.a.property('msg', 'result'); - expect(data).to.have.a.property('id', 'id'); - expect(data.error).to.have.a.property('error', 'error-action-not-allowed'); - }); - }); - - after(() => Promise.all([deleteRoom({ type: 'p', roomId: ridTestRoom }), deleteUser(testUser)])); - }); - }); - describe('[@setUserActiveStatus]', () => { let testUser: TestUser; let testUser2: TestUser; From 5d8a3589ffd3e8f7a2962185917a434bd90c6a6c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:21:19 +0530 Subject: [PATCH 58/85] chore!: remove deprecated endpoint channels.images (#33471) --- .changeset/olive-dogs-jam.md | 6 +++ apps/meteor/app/api/server/v1/channels.ts | 52 +------------------ .../rest-typings/src/v1/channels/channels.ts | 8 +-- 3 files changed, 8 insertions(+), 58 deletions(-) create mode 100644 .changeset/olive-dogs-jam.md diff --git a/.changeset/olive-dogs-jam.md b/.changeset/olive-dogs-jam.md new file mode 100644 index 0000000000000..4205feeed4021 --- /dev/null +++ b/.changeset/olive-dogs-jam.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': major +'@rocket.chat/meteor': major +--- + +Removes deprecated endpoint `channels.images`. Moving forward, use `rooms.images` endpoint. diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index b69487bdd22bb..f288053773661 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -1,5 +1,5 @@ import { Team, Room } from '@rocket.chat/core-services'; -import type { IRoom, ISubscription, IUser, RoomType, IUpload } from '@rocket.chat/core-typings'; +import type { IRoom, ISubscription, IUser, RoomType } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; import { isChannelsAddAllProps, @@ -18,7 +18,6 @@ import { isChannelsConvertToTeamProps, isChannelsSetReadOnlyProps, isChannelsDeleteProps, - isRoomsImagesProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -800,55 +799,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.images', - { - authRequired: true, - validateParams: isRoomsImagesProps, - deprecation: { - version: '7.0.0', - alternatives: ['rooms.images'], - }, - }, - { - async get() { - const room = await Rooms.findOneById>(this.queryParams.roomId, { - projection: { t: 1, teamId: 1, prid: 1 }, - }); - - if (!room || !(await canAccessRoomAsync(room, { _id: this.userId }))) { - return API.v1.unauthorized(); - } - - let initialImage: IUpload | null = null; - if (this.queryParams.startingFromId) { - initialImage = await Uploads.findOneById(this.queryParams.startingFromId); - } - - const { offset, count } = await getPaginationItems(this.queryParams); - - const { cursor, totalCount } = Uploads.findImagesByRoomId(room._id, initialImage?.uploadedAt, { - skip: offset, - limit: count, - }); - - const [files, total] = await Promise.all([cursor.toArray(), totalCount]); - - // If the initial image was not returned in the query, insert it as the first element of the list - if (initialImage && !files.find(({ _id }) => _id === (initialImage as IUpload)._id)) { - files.splice(0, 0, initialImage); - } - - return API.v1.success({ - files, - count, - offset, - total, - }); - }, - }, -); - API.v1.addRoute( 'channels.getIntegrations', { diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index 6bda71004c5db..04bda08bd186a 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -1,8 +1,7 @@ -import type { IUpload, IUploadWithUser, IMessage, IRoom, ITeam, IGetRoomRoles, IUser, IIntegration } from '@rocket.chat/core-typings'; +import type { IUploadWithUser, IMessage, IRoom, ITeam, IGetRoomRoles, IUser, IIntegration } from '@rocket.chat/core-typings'; import type { PaginatedRequest } from '../../helpers/PaginatedRequest'; import type { PaginatedResult } from '../../helpers/PaginatedResult'; -import type { RoomsImagesProps } from '../rooms'; import type { ChannelsAddAllProps } from './ChannelsAddAllProps'; import type { ChannelsArchiveProps } from './ChannelsArchiveProps'; import type { ChannelsConvertToTeamProps } from './ChannelsConvertToTeamProps'; @@ -39,11 +38,6 @@ export type ChannelsEndpoints = { files: IUploadWithUser[]; }>; }; - '/v1/channels.images': { - GET: (params: RoomsImagesProps) => PaginatedResult<{ - files: IUpload[]; - }>; - }; '/v1/channels.members': { GET: ( params: PaginatedRequest< From f7caf155cc46ccc762ed9dd6f110f15d0f36280c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:22:52 +0530 Subject: [PATCH 59/85] chore!: remove deprecated endpoints licenses.isEnterprise and licenses.get (#33470) --- .changeset/ten-houses-repair.md | 6 ++ apps/meteor/client/hooks/useIsEnterprise.ts | 3 +- apps/meteor/ee/server/api/licenses.ts | 28 ------- apps/meteor/tests/end-to-end/api/licenses.ts | 78 ------------------- .../src/MockedServerContext.tsx | 10 --- packages/rest-typings/src/v1/licenses.ts | 8 +- 6 files changed, 8 insertions(+), 125 deletions(-) create mode 100644 .changeset/ten-houses-repair.md diff --git a/.changeset/ten-houses-repair.md b/.changeset/ten-houses-repair.md new file mode 100644 index 0000000000000..7d3cd67ce15fd --- /dev/null +++ b/.changeset/ten-houses-repair.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': major +'@rocket.chat/meteor': major +--- + +Removes deprecated endpoints `licenses.isEnterprise` and `licenses.get`. Moving forward use the endpoint `licenses.info.` diff --git a/apps/meteor/client/hooks/useIsEnterprise.ts b/apps/meteor/client/hooks/useIsEnterprise.ts index e2622d8b695df..dffaf8b368775 100644 --- a/apps/meteor/client/hooks/useIsEnterprise.ts +++ b/apps/meteor/client/hooks/useIsEnterprise.ts @@ -1,8 +1,7 @@ -import type { OperationResult } from '@rocket.chat/rest-typings'; import type { UseQueryResult } from '@tanstack/react-query'; import { useLicenseBase } from './useLicense'; -export const useIsEnterprise = (): UseQueryResult> => { +export const useIsEnterprise = (): UseQueryResult<{ isEnterprise: boolean }> => { return useLicenseBase({ select: (data) => ({ isEnterprise: Boolean(data?.license.license) }) }); }; diff --git a/apps/meteor/ee/server/api/licenses.ts b/apps/meteor/ee/server/api/licenses.ts index 22ddbda9e31e5..9d8d60c0be516 100644 --- a/apps/meteor/ee/server/api/licenses.ts +++ b/apps/meteor/ee/server/api/licenses.ts @@ -7,23 +7,6 @@ import { API } from '../../../app/api/server/api'; import { hasPermissionAsync } from '../../../app/authorization/server/functions/hasPermission'; import { notifyOnSettingChangedById } from '../../../app/lib/server/lib/notifyListener'; -API.v1.addRoute( - 'licenses.get', - { authRequired: true, deprecation: { version: '7.0.0', alternatives: ['licenses.info'] } }, - { - async get() { - if (!(await hasPermissionAsync(this.userId, 'view-privileged-setting'))) { - return API.v1.unauthorized(); - } - - const license = License.getUnmodifiedLicenseAndModules(); - const licenses = license ? [license] : []; - - return API.v1.success({ licenses }); - }, - }, -); - API.v1.addRoute( 'licenses.info', { authRequired: true, validateParams: isLicensesInfoProps }, @@ -73,14 +56,3 @@ API.v1.addRoute( }, }, ); - -API.v1.addRoute( - 'licenses.isEnterprise', - { authOrAnonRequired: true, deprecation: { version: '7.0.0', alternatives: ['licenses.info'] } }, - { - get() { - const isEnterpriseEdition = License.hasValidLicense(); - return API.v1.success({ isEnterprise: isEnterpriseEdition }); - }, - }, -); diff --git a/apps/meteor/tests/end-to-end/api/licenses.ts b/apps/meteor/tests/end-to-end/api/licenses.ts index 10dce4177aec6..931f9958846b0 100644 --- a/apps/meteor/tests/end-to-end/api/licenses.ts +++ b/apps/meteor/tests/end-to-end/api/licenses.ts @@ -70,46 +70,6 @@ describe('licenses', () => { }); }); - describe('[/licenses.get]', () => { - it('should fail if not logged in', (done) => { - void request - .get(api('licenses.get')) - .expect('Content-Type', 'application/json') - .expect(401) - .expect((res) => { - expect(res.body).to.have.property('status', 'error'); - expect(res.body).to.have.property('message'); - }) - .end(done); - }); - - it('should fail if user is unauthorized', (done) => { - void request - .get(api('licenses.get')) - .set(unauthorizedUserCredentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - - it('should return licenses if user is logged in and is authorized', (done) => { - void request - .get(api('licenses.get')) - .set(credentials) - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('licenses').and.to.be.an('array'); - }) - - .end(done); - }); - }); - describe('[/licenses.info]', () => { it('should fail if not logged in', (done) => { void request @@ -155,42 +115,4 @@ describe('licenses', () => { .end(done); }); }); - - describe('[/licenses.isEnterprise]', () => { - it('should fail if not logged in', (done) => { - void request - .get(api('licenses.isEnterprise')) - .expect('Content-Type', 'application/json') - .expect(401) - .expect((res) => { - expect(res.body).to.have.property('status', 'error'); - expect(res.body).to.have.property('message'); - }) - .end(done); - }); - - it('should pass if user has user role', (done) => { - void request - .get(api('licenses.isEnterprise')) - .set(unauthorizedUserCredentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('isEnterprise', Boolean(process.env.IS_EE)); - }) - .end(done); - }); - - it('should pass if user has admin role', (done) => { - void request - .get(api('licenses.isEnterprise')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('isEnterprise', Boolean(process.env.IS_EE)); - }) - .end(done); - }); - }); }); diff --git a/packages/mock-providers/src/MockedServerContext.tsx b/packages/mock-providers/src/MockedServerContext.tsx index 1f30cd6f1c2f5..84bca6c1d69d1 100644 --- a/packages/mock-providers/src/MockedServerContext.tsx +++ b/packages/mock-providers/src/MockedServerContext.tsx @@ -9,8 +9,6 @@ export const MockedServerContext = ({ handleRequest, handleMethod, children, - - isEnterprise, }: { handleRequest?: (args: { method: TMethod; @@ -41,14 +39,6 @@ export const MockedServerContext = ({ keys: UrlParams; params: OperationParams; }) => { - if (isEnterprise !== undefined) { - if (args.method === 'GET' && args.pathPattern === '/v1/licenses.isEnterprise') { - return { - isEnterprise, - } as any; - } - } - return handleRequest?.(args); }, getStream: () => () => undefined, diff --git a/packages/rest-typings/src/v1/licenses.ts b/packages/rest-typings/src/v1/licenses.ts index 4a18bc113a6c0..99ba936e34920 100644 --- a/packages/rest-typings/src/v1/licenses.ts +++ b/packages/rest-typings/src/v1/licenses.ts @@ -1,4 +1,4 @@ -import type { ILicenseV2, ILicenseV3, LicenseInfo } from '@rocket.chat/core-typings'; +import type { LicenseInfo } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; const ajv = new Ajv({ @@ -40,9 +40,6 @@ const licensesInfoPropsSchema = { export const isLicensesInfoProps = ajv.compile(licensesInfoPropsSchema); export type LicensesEndpoints = { - '/v1/licenses.get': { - GET: () => { licenses: Array }; - }; '/v1/licenses.info': { GET: (params: licensesInfoProps) => { license: LicenseInfo; @@ -57,7 +54,4 @@ export type LicensesEndpoints = { '/v1/licenses.requestSeatsLink': { GET: () => { url: string }; }; - '/v1/licenses.isEnterprise': { - GET: () => { isEnterprise: boolean }; - }; }; From a08d613df0153ecef8e2f64cfffc77710e47d10c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:25:15 +0530 Subject: [PATCH 60/85] chore!: remove deprecated addOAuthApp method (#33465) --- .changeset/hungry-icons-try.md | 5 ++++ .../server/admin/methods/addOAuthApp.ts | 24 ------------------- .../app/oauth2-server-config/server/index.ts | 1 - 3 files changed, 5 insertions(+), 25 deletions(-) create mode 100644 .changeset/hungry-icons-try.md delete mode 100644 apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts diff --git a/.changeset/hungry-icons-try.md b/.changeset/hungry-icons-try.md new file mode 100644 index 0000000000000..971873093a552 --- /dev/null +++ b/.changeset/hungry-icons-try.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `addOAuthApp`. Moving forward, use the endpoint `oauth-apps.create` instead. diff --git a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts b/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts deleted file mode 100644 index 3f41b1b01bea4..0000000000000 --- a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { IOAuthApps } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import type { OauthAppsAddParams } from '@rocket.chat/rest-typings'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../../lib/server/lib/deprecationWarningLogger'; -import { addOAuthApp } from '../functions/addOAuthApp'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - addOAuthApp(application: OauthAppsAddParams): IOAuthApps; - } -} - -Meteor.methods({ - async addOAuthApp(application) { - methodDeprecationLogger.warn( - 'addOAuthApp is deprecated and will be removed in future versions of Rocket.Chat. Use the REST endpoint /v1/oauth-apps.create instead.', - ); - - return addOAuthApp(application, this.userId ?? undefined); - }, -}); diff --git a/apps/meteor/app/oauth2-server-config/server/index.ts b/apps/meteor/app/oauth2-server-config/server/index.ts index 2dd48fe73a2c1..be26bdb2facb1 100644 --- a/apps/meteor/app/oauth2-server-config/server/index.ts +++ b/apps/meteor/app/oauth2-server-config/server/index.ts @@ -1,6 +1,5 @@ import './oauth/oauth2-server'; import './oauth/default-services'; import './admin/functions/addOAuthApp'; -import './admin/methods/addOAuthApp'; import './admin/methods/updateOAuthApp'; import './admin/methods/deleteOAuthApp'; From 3ec7793cb3388226248a6e1f3f7ec6c239d15322 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:27:55 +0530 Subject: [PATCH 61/85] chore!: remove deprecated endpoint livechat/inquiries.queued (#33453) --- .changeset/sixty-moons-walk.md | 6 ++++ .../livechat/imports/server/rest/inquiries.ts | 34 ------------------- .../end-to-end/api/livechat/05-inquiries.ts | 24 +------------ packages/rest-typings/src/v1/omnichannel.ts | 30 ---------------- 4 files changed, 7 insertions(+), 87 deletions(-) create mode 100644 .changeset/sixty-moons-walk.md diff --git a/.changeset/sixty-moons-walk.md b/.changeset/sixty-moons-walk.md new file mode 100644 index 0000000000000..7d7e96b7c16cf --- /dev/null +++ b/.changeset/sixty-moons-walk.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': major +'@rocket.chat/meteor': major +--- + +Removes deprecated `livechat/inquiries.queued` endpoint. Moving forward use the `livechat/inquiries.queuedForUser` endpoint. diff --git a/apps/meteor/app/livechat/imports/server/rest/inquiries.ts b/apps/meteor/app/livechat/imports/server/rest/inquiries.ts index 07c69a22d08fb..2432e55054f52 100644 --- a/apps/meteor/app/livechat/imports/server/rest/inquiries.ts +++ b/apps/meteor/app/livechat/imports/server/rest/inquiries.ts @@ -3,7 +3,6 @@ import { LivechatInquiry, LivechatDepartment, Users } from '@rocket.chat/models' import { isGETLivechatInquiriesListParams, isPOSTLivechatInquiriesTakeParams, - isGETLivechatInquiriesQueuedParams, isGETLivechatInquiriesQueuedForUserParams, isGETLivechatInquiriesGetOneParams, } from '@rocket.chat/rest-typings'; @@ -69,39 +68,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'livechat/inquiries.queued', - { - authRequired: true, - permissionsRequired: ['view-l-room'], - validateParams: isGETLivechatInquiriesQueuedParams, - deprecation: { - version: '7.0.0', - alternatives: ['livechat/inquiries.queuedForUser'], - }, - }, - { - async get() { - const { offset, count } = await getPaginationItems(this.queryParams); - const { sort } = await this.parseJsonQuery(); - const { department } = this.queryParams; - - return API.v1.success( - await findInquiries({ - userId: this.userId, - department, - status: LivechatInquiryStatus.QUEUED, - pagination: { - offset, - count, - sort, - }, - }), - ); - }, - }, -); - API.v1.addRoute( 'livechat/inquiries.queuedForUser', { authRequired: true, permissionsRequired: ['view-l-room'], validateParams: isGETLivechatInquiriesQueuedForUserParams }, diff --git a/apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts b/apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts index 0039a6c245b03..45f20686f80ee 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts @@ -52,28 +52,6 @@ describe('LIVECHAT - inquiries', () => { }); }); - describe('livechat/inquiries.queued', () => { - it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-l-room', []); - await request.get(api('livechat/inquiries.queued')).set(credentials).expect('Content-Type', 'application/json').expect(403); - }); - it('should return an array of inquiries', async () => { - await updatePermission('view-l-room', ['admin']); - await request - .get(api('livechat/inquiries.queued')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res: Response) => { - expect(res.body).to.have.property('success', true); - expect(res.body.inquiries).to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('count'); - }); - }); - }); - describe('livechat/inquiries.getOne', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { await updatePermission('view-l-room', []); @@ -235,7 +213,7 @@ describe('LIVECHAT - inquiries', () => { }); it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { await updatePermission('view-l-room', []); - await request.get(api('livechat/inquiries.queued')).set(credentials).expect('Content-Type', 'application/json').expect(403); + await request.get(api('livechat/inquiries.queuedForUser')).set(credentials).expect('Content-Type', 'application/json').expect(403); }); it('should return an array of inquiries', async () => { await restorePermissionToRoles('view-l-room'); diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 09c021a4e7d92..82df994de1ba0 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2947,33 +2947,6 @@ const POSTLivechatInquiriesTakeParamsSchema = { export const isPOSTLivechatInquiriesTakeParams = ajv.compile(POSTLivechatInquiriesTakeParamsSchema); -type GETLivechatInquiriesQueuedParams = PaginatedRequest<{ department?: string }>; - -const GETLivechatInquiriesQueuedParamsSchema = { - type: 'object', - properties: { - count: { - type: 'number', - nullable: true, - }, - offset: { - type: 'number', - nullable: true, - }, - sort: { - type: 'string', - nullable: true, - }, - department: { - type: 'string', - nullable: true, - }, - }, - additionalProperties: false, -}; - -export const isGETLivechatInquiriesQueuedParams = ajv.compile(GETLivechatInquiriesQueuedParamsSchema); - type GETLivechatInquiriesQueuedForUserParams = PaginatedRequest<{ department?: string }>; const GETLivechatInquiriesQueuedForUserParamsSchema = { @@ -3937,9 +3910,6 @@ export type OmnichannelEndpoints = { '/v1/livechat/inquiries.take': { POST: (params: POSTLivechatInquiriesTakeParams) => { inquiry: ILivechatInquiryRecord }; }; - '/v1/livechat/inquiries.queued': { - GET: (params: GETLivechatInquiriesQueuedParams) => PaginatedResult<{ inquiries: ILivechatInquiryRecord[] }>; - }; '/v1/livechat/inquiries.queuedForUser': { GET: (params: GETLivechatInquiriesQueuedForUserParams) => PaginatedResult<{ inquiries: ILivechatInquiryRecord[] }>; }; From 5e4509537a295fc8b7c12375d71c1a7ba644a2e6 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:28:57 +0530 Subject: [PATCH 62/85] chore!: remove deprecated method livechat:sendOfflineMessage (#33452) --- .changeset/unlucky-ducks-arrive.md | 5 +++ .../livechat/server/api/v1/offlineMessage.ts | 5 ++- apps/meteor/app/livechat/server/index.ts | 1 - .../server/methods/sendOfflineMessage.ts | 40 ------------------- 4 files changed, 9 insertions(+), 42 deletions(-) create mode 100644 .changeset/unlucky-ducks-arrive.md delete mode 100644 apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts diff --git a/.changeset/unlucky-ducks-arrive.md b/.changeset/unlucky-ducks-arrive.md new file mode 100644 index 0000000000000..3500d95e44be6 --- /dev/null +++ b/.changeset/unlucky-ducks-arrive.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:sendOfflineMessage`. Moving forward, use the endpoint `livechat/offline.message`. diff --git a/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts b/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts index 6acd6ab98ea13..a367df7a04adc 100644 --- a/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts +++ b/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts @@ -6,7 +6,10 @@ import { Livechat } from '../../lib/LivechatTyped'; API.v1.addRoute( 'livechat/offline.message', - { validateParams: isPOSTLivechatOfflineMessageParams }, + { + validateParams: isPOSTLivechatOfflineMessageParams, + rateLimiterOptions: { numRequestsAllowed: 1, intervalTimeInMS: 5000 }, + }, { async post() { const { name, email, message, department, host } = this.bodyParams; diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index ef8fb5067e220..d365b8af5a1bd 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -43,7 +43,6 @@ import './methods/saveIntegration'; import './methods/saveTrigger'; import './methods/sendMessageLivechat'; import './methods/sendFileLivechatMessage'; -import './methods/sendOfflineMessage'; import './methods/setCustomField'; import './methods/setDepartmentForVisitor'; import './methods/transfer'; diff --git a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts b/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts deleted file mode 100644 index b620aa4341008..0000000000000 --- a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { check } from 'meteor/check'; -import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:sendOfflineMessage'(data: { name: string; email: string; message: string }): Promise; - } -} - -Meteor.methods({ - async 'livechat:sendOfflineMessage'(data) { - methodDeprecationLogger.method('livechat:sendOfflineMessage', '7.0.0'); - - check(data, { - name: String, - email: String, - message: String, - }); - - await Livechat.sendOfflineMessage(data); - }, -}); - -DDPRateLimiter.addRule( - { - type: 'method', - name: 'livechat:sendOfflineMessage', - connectionId() { - return true; - }, - }, - 1, - 5000, -); From 099e44af50f5302b7a7fdee0b18898974fbdf6d5 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:31:48 +0530 Subject: [PATCH 63/85] chore!: removed deprecated method livechat:getAgentData (#33450) --- .changeset/yellow-jobs-serve.md | 5 +++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/getAgentData.ts | 42 ------------------- 3 files changed, 5 insertions(+), 43 deletions(-) create mode 100644 .changeset/yellow-jobs-serve.md delete mode 100644 apps/meteor/app/livechat/server/methods/getAgentData.ts diff --git a/.changeset/yellow-jobs-serve.md b/.changeset/yellow-jobs-serve.md new file mode 100644 index 0000000000000..add05b7704b73 --- /dev/null +++ b/.changeset/yellow-jobs-serve.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:getAgentData`. Moving forward use the endpoint `livechat/agent.info/:rid/:token`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index d365b8af5a1bd..70d756cbc6ef8 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -20,7 +20,6 @@ import './hooks/afterSaveOmnichannelMessage'; import './methods/changeLivechatStatus'; import './methods/closeRoom'; import './methods/discardTranscript'; -import './methods/getAgentData'; import './methods/getAgentOverviewData'; import './methods/getAnalyticsChartData'; import './methods/getAnalyticsOverviewData'; diff --git a/apps/meteor/app/livechat/server/methods/getAgentData.ts b/apps/meteor/app/livechat/server/methods/getAgentData.ts deleted file mode 100644 index 5fe58560806e5..0000000000000 --- a/apps/meteor/app/livechat/server/methods/getAgentData.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { ILivechatAgent } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { settings } from '../../../settings/server'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:getAgentData'(params: { - roomId: string; - token: string; - }): Promise | null | undefined>; - } -} - -Meteor.methods({ - async 'livechat:getAgentData'({ roomId, token }) { - check(roomId, String); - check(token, String); - - methodDeprecationLogger.warn( - 'The method "livechat:getAgentData" is deprecated and will be removed after version v7.0.0. Use "livechat/agent.info/:rid/:token" instead.', - ); - - const room = await LivechatRooms.findOneById(roomId); - const visitor = await LivechatVisitors.getVisitorByToken(token); - - if (!room || room.t !== 'l' || !room.v || room.v.token !== visitor?.token) { - throw new Meteor.Error('error-invalid-room', 'Invalid room'); - } - - if (!room.servedBy) { - return; - } - - return Users.getAgentInfo(room.servedBy._id, settings.get('Livechat_show_agent_email')); - }, -}); From e162458f079e85ade7e27ea806498d43cde1c5f0 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:32:51 +0530 Subject: [PATCH 64/85] chore!: remove deprecated method livechat:webhookTest (#33449) --- .changeset/forty-actors-care.md | 5 + apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/webhookTest.ts | 93 ------------------- 3 files changed, 5 insertions(+), 94 deletions(-) create mode 100644 .changeset/forty-actors-care.md delete mode 100644 apps/meteor/app/livechat/server/methods/webhookTest.ts diff --git a/.changeset/forty-actors-care.md b/.changeset/forty-actors-care.md new file mode 100644 index 0000000000000..2420d1603114c --- /dev/null +++ b/.changeset/forty-actors-care.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:webhookTest`. Moving forward use the endpoint `livechat/webhook.test`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 70d756cbc6ef8..1f943523a3f5d 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -45,7 +45,6 @@ import './methods/sendFileLivechatMessage'; import './methods/setCustomField'; import './methods/setDepartmentForVisitor'; import './methods/transfer'; -import './methods/webhookTest'; import './methods/setUpConnection'; import './methods/takeInquiry'; import './methods/requestTranscript'; diff --git a/apps/meteor/app/livechat/server/methods/webhookTest.ts b/apps/meteor/app/livechat/server/methods/webhookTest.ts deleted file mode 100644 index 68800e1a0616d..0000000000000 --- a/apps/meteor/app/livechat/server/methods/webhookTest.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { serverFetch as fetch } from '@rocket.chat/server-fetch'; -import { Meteor } from 'meteor/meteor'; - -import { SystemLogger } from '../../../../server/lib/logger/system'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { settings } from '../../../settings/server'; - -const postCatchError = async function (url: string, options?: Record | undefined) { - try { - return fetch(url, { ...options, method: 'POST' }); - } catch (e) { - return undefined; // TODO: should we return the error? - } -}; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:webhookTest'(): Promise; - } -} - -Meteor.methods({ - async 'livechat:webhookTest'() { - methodDeprecationLogger.method('livechat:webhookTest', '7.0.0'); - - const sampleData = { - type: 'LivechatSession', - _id: 'fasd6f5a4sd6f8a4sdf', - label: 'title', - topic: 'asiodojf', - createdAt: new Date(), - lastMessageAt: new Date(), - tags: ['tag1', 'tag2', 'tag3'], - customFields: { - productId: '123456', - }, - visitor: { - _id: '', - name: 'visitor name', - username: 'visitor-username', - department: 'department', - email: 'email@address.com', - phone: '192873192873', - ip: '123.456.7.89', - browser: 'Chrome', - os: 'Linux', - customFields: { - customerId: '123456', - }, - }, - agent: { - _id: 'asdf89as6df8', - username: 'agent.username', - name: 'Agent Name', - email: 'agent@email.com', - }, - messages: [ - { - username: 'visitor-username', - msg: 'message content', - ts: new Date(), - }, - { - username: 'agent.username', - agentId: 'asdf89as6df8', - msg: 'message content from agent', - ts: new Date(), - }, - ], - }; - - const options = { - method: 'POST', - headers: { - 'X-RocketChat-Livechat-Token': settings.get('Livechat_secret_token'), - 'Accept': 'application/json', - }, - body: sampleData, - }; - - const response = await postCatchError(settings.get('Livechat_webhookUrl'), options); - - SystemLogger.debug({ response: await response?.text() }); - - if (response?.ok) { - return true; - } - - throw new Meteor.Error('error-invalid-webhook-response'); - }, -}); From 5cb21dc9872f71263d8c2b3699abc2c890db2347 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:34:09 +0530 Subject: [PATCH 65/85] chore!: remove deprecated method livechat:saveAppearance (#33448) --- .changeset/many-carrots-care.md | 5 ++ apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/saveAppearance.ts | 63 ------------------- 3 files changed, 5 insertions(+), 64 deletions(-) create mode 100644 .changeset/many-carrots-care.md delete mode 100644 apps/meteor/app/livechat/server/methods/saveAppearance.ts diff --git a/.changeset/many-carrots-care.md b/.changeset/many-carrots-care.md new file mode 100644 index 0000000000000..cfc61b1645d79 --- /dev/null +++ b/.changeset/many-carrots-care.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes the deprecated method `livechat:saveAppearance`. Moving forward use the endpoint `livechat/appearance`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 1f943523a3f5d..52082e8b71434 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -34,7 +34,6 @@ import './methods/removeManager'; import './methods/removeTrigger'; import './methods/removeRoom'; import './methods/saveAgentInfo'; -import './methods/saveAppearance'; import './methods/saveCustomField'; import './methods/saveDepartment'; import './methods/saveDepartmentAgents'; diff --git a/apps/meteor/app/livechat/server/methods/saveAppearance.ts b/apps/meteor/app/livechat/server/methods/saveAppearance.ts deleted file mode 100644 index 619dae1477087..0000000000000 --- a/apps/meteor/app/livechat/server/methods/saveAppearance.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Settings } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:saveAppearance'(settings: { _id: string; value: any }[]): Promise; - } -} - -Meteor.methods({ - async 'livechat:saveAppearance'(settings) { - methodDeprecationLogger.method('livechat:saveAppearance', '7.0.0'); - - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:saveAppearance', - }); - } - - const validSettings = [ - 'Livechat_title', - 'Livechat_title_color', - 'Livechat_enable_message_character_limit', - 'Livechat_message_character_limit', - 'Livechat_show_agent_info', - 'Livechat_show_agent_email', - 'Livechat_display_offline_form', - 'Livechat_offline_form_unavailable', - 'Livechat_offline_message', - 'Livechat_offline_success_message', - 'Livechat_offline_title', - 'Livechat_offline_title_color', - 'Livechat_offline_email', - 'Livechat_conversation_finished_message', - 'Livechat_conversation_finished_text', - 'Livechat_registration_form', - 'Livechat_name_field_registration_form', - 'Livechat_email_field_registration_form', - 'Livechat_registration_form_message', - ]; - - const valid = settings.every((setting) => validSettings.indexOf(setting._id) !== -1); - - if (!valid) { - throw new Meteor.Error('invalid-setting'); - } - - const promises = settings.map((setting) => Settings.updateValueById(setting._id, setting.value)); - - (await Promise.all(promises)).forEach((value, index) => { - if (value?.modifiedCount) { - void notifyOnSettingChangedById(settings[index]._id); - } - }); - }, -}); From c2fb6e0e46339822a2713b9ce039cd6263772a1f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:40:02 +0530 Subject: [PATCH 66/85] chore!: remove deprecated livechat:getAgentOverviewData method (#33445) --- .changeset/mean-readers-join.md | 5 +++ apps/meteor/app/livechat/server/index.ts | 1 - .../server/methods/getAgentOverviewData.ts | 35 ------------------- 3 files changed, 5 insertions(+), 36 deletions(-) create mode 100644 .changeset/mean-readers-join.md delete mode 100644 apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts diff --git a/.changeset/mean-readers-join.md b/.changeset/mean-readers-join.md new file mode 100644 index 0000000000000..f78af9a3c5665 --- /dev/null +++ b/.changeset/mean-readers-join.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated `livechat:getAgentOverviewData` method. Moving forward use `livechat/analytics/agent-overview` endpoint. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 52082e8b71434..651fbdbcfa69b 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -20,7 +20,6 @@ import './hooks/afterSaveOmnichannelMessage'; import './methods/changeLivechatStatus'; import './methods/closeRoom'; import './methods/discardTranscript'; -import './methods/getAgentOverviewData'; import './methods/getAnalyticsChartData'; import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; diff --git a/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts b/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts deleted file mode 100644 index 819bac03c5b41..0000000000000 --- a/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ConversationData } from '@rocket.chat/core-services'; -import { OmnichannelAnalytics } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Users } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:getAgentOverviewData'(options: { chartOptions: { name: string } }): ConversationData | void; - } -} - -Meteor.methods({ - async 'livechat:getAgentOverviewData'(options) { - methodDeprecationLogger.method('livechat:getAgentOverviewData', '7.0.0', ' Use "livechat/analytics/agent-overview" instead.'); - - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:getAgentOverviewData', - }); - } - - if (!options.chartOptions?.name) { - return; - } - - const user = await Users.findOneById(uid, { projection: { _id: 1, utcOffset: 1 } }); - return OmnichannelAnalytics.getAgentOverviewData({ ...options, utcOffset: user?.utcOffset || 0 }); - }, -}); From 4422e88ea8250aaa038828bcea5ab9e43d428bf4 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:40:44 +0530 Subject: [PATCH 67/85] chore!: removed deprecated methods livechat:removeAgent, livechat:removeManager, and livechat:removeDepartment (#33423) --- .changeset/purple-tools-heal.md | 5 +++ apps/meteor/app/livechat/server/index.ts | 3 -- .../livechat/server/methods/removeAgent.ts | 27 --------------- .../server/methods/removeDepartment.ts | 33 ------------------- .../livechat/server/methods/removeManager.ts | 29 ---------------- 5 files changed, 5 insertions(+), 92 deletions(-) create mode 100644 .changeset/purple-tools-heal.md delete mode 100644 apps/meteor/app/livechat/server/methods/removeAgent.ts delete mode 100644 apps/meteor/app/livechat/server/methods/removeDepartment.ts delete mode 100644 apps/meteor/app/livechat/server/methods/removeManager.ts diff --git a/.changeset/purple-tools-heal.md b/.changeset/purple-tools-heal.md new file mode 100644 index 0000000000000..069703865c4d1 --- /dev/null +++ b/.changeset/purple-tools-heal.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removed deprecated methods `livechat:removeAgent`, `livechat:removeManager` and `livechat:removeDepartment`. Moving forward, use `livechat/users/agent/:_id`, and `livechat/users/manager/:_id` and `livechat/department/:_id` respectively.` diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 651fbdbcfa69b..4e78811505d77 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -25,11 +25,8 @@ import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; import './methods/getRoutingConfig'; import './methods/registerGuest'; -import './methods/removeAgent'; import './methods/removeAllClosedRooms'; import './methods/removeCustomField'; -import './methods/removeDepartment'; -import './methods/removeManager'; import './methods/removeTrigger'; import './methods/removeRoom'; import './methods/saveAgentInfo'; diff --git a/apps/meteor/app/livechat/server/methods/removeAgent.ts b/apps/meteor/app/livechat/server/methods/removeAgent.ts deleted file mode 100644 index ebb57383784f0..0000000000000 --- a/apps/meteor/app/livechat/server/methods/removeAgent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:removeAgent'(username: string): boolean; - } -} - -Meteor.methods({ - async 'livechat:removeAgent'(username) { - methodDeprecationLogger.method('livechat:removeAgent', '7.0.0'); - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-agents'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:removeAgent', - }); - } - - return Livechat.removeAgent(username); - }, -}); diff --git a/apps/meteor/app/livechat/server/methods/removeDepartment.ts b/apps/meteor/app/livechat/server/methods/removeDepartment.ts deleted file mode 100644 index 6e4b11c836f67..0000000000000 --- a/apps/meteor/app/livechat/server/methods/removeDepartment.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; -import type { DeleteResult } from 'mongodb'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { DepartmentHelper } from '../lib/Departments'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:removeDepartment'(_id: string): DeleteResult; - } -} - -Meteor.methods({ - async 'livechat:removeDepartment'(_id) { - methodDeprecationLogger.method('livechat:removeDepartment', '7.0.0'); - - check(_id, String); - - const uid = Meteor.userId(); - - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-departments'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:removeDepartment', - }); - } - - return DepartmentHelper.removeDepartment(_id); - }, -}); diff --git a/apps/meteor/app/livechat/server/methods/removeManager.ts b/apps/meteor/app/livechat/server/methods/removeManager.ts deleted file mode 100644 index 85a5f3076c8c3..0000000000000 --- a/apps/meteor/app/livechat/server/methods/removeManager.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/LivechatTyped'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:removeManager'(username: string): boolean; - } -} - -Meteor.methods({ - async 'livechat:removeManager'(username) { - methodDeprecationLogger.method('livechat:removeManager', '7.0.0'); - - const uid = Meteor.userId(); - - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-managers'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:removeManager', - }); - } - - return Livechat.removeManager(username); - }, -}); From a8cce1ff36b304195e72b2d17581239659ef4b03 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:48:34 +0530 Subject: [PATCH 68/85] chore!: remove deprecated livechat:setCustomField method (#33444) --- .changeset/nice-vans-design.md | 5 ++++ .../app/livechat/imports/server/rest/sms.ts | 11 +++---- .../livechat/server/api/lib/customFields.ts | 20 ++++++++++++- apps/meteor/app/livechat/server/index.ts | 1 - .../livechat/server/methods/setCustomField.ts | 30 ------------------- 5 files changed, 30 insertions(+), 37 deletions(-) create mode 100644 .changeset/nice-vans-design.md delete mode 100644 apps/meteor/app/livechat/server/methods/setCustomField.ts diff --git a/.changeset/nice-vans-design.md b/.changeset/nice-vans-design.md new file mode 100644 index 0000000000000..42070f39cc841 --- /dev/null +++ b/.changeset/nice-vans-design.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:setCustomField`. The custom fields can be directly set via the `livechat/visitor` endpoint. diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 6b2411cf8e3dd..18ac13f4b65c8 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -19,6 +19,7 @@ import { API } from '../../../../api/server'; import { FileUpload } from '../../../../file-upload/server'; import { checkUrlForSsrf } from '../../../../lib/server/functions/checkUrlForSsrf'; import { settings } from '../../../../settings/server'; +import { setCustomField } from '../../../server/api/lib/customFields'; import type { ILivechatMessage } from '../../../server/lib/LivechatTyped'; import { Livechat as LivechatTyped } from '../../../server/lib/LivechatTyped'; @@ -263,16 +264,16 @@ API.v1.addRoute('livechat/sms-incoming/:service', { setImmediate(async () => { if (sms.extra) { if (sms.extra.fromCountry) { - await Meteor.callAsync('livechat:setCustomField', sendMessage.message.token, 'country', sms.extra.fromCountry); + await setCustomField(sendMessage.message.token, 'country', sms.extra.fromCountry); } if (sms.extra.fromState) { - await Meteor.callAsync('livechat:setCustomField', sendMessage.message.token, 'state', sms.extra.fromState); + await setCustomField(sendMessage.message.token, 'state', sms.extra.fromState); } if (sms.extra.fromCity) { - await Meteor.callAsync('livechat:setCustomField', sendMessage.message.token, 'city', sms.extra.fromCity); + await setCustomField(sendMessage.message.token, 'city', sms.extra.fromCity); } - if (sms.extra.toPhone) { - await Meteor.callAsync('livechat:setCustomField', sendMessage.message.token, 'phoneNumber', sms.extra.toPhone); + if (sms.extra.fromZip) { + await setCustomField(sendMessage.message.token, 'zip', sms.extra.fromZip); } } }); diff --git a/apps/meteor/app/livechat/server/api/lib/customFields.ts b/apps/meteor/app/livechat/server/api/lib/customFields.ts index 0a9213f99f996..8b1d459b2d8e5 100644 --- a/apps/meteor/app/livechat/server/api/lib/customFields.ts +++ b/apps/meteor/app/livechat/server/api/lib/customFields.ts @@ -1,7 +1,8 @@ import type { ILivechatCustomField } from '@rocket.chat/core-typings'; -import { LivechatCustomField } from '@rocket.chat/models'; +import { LivechatCustomField, LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; import type { PaginatedResult } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; +import type { UpdateResult, Document } from 'mongodb'; export async function findLivechatCustomFields({ text, @@ -41,3 +42,20 @@ export async function findCustomFieldById({ customField: await LivechatCustomField.findOneById(customFieldId), }; } + +export async function setCustomField( + token: string, + key: string, + value: string, + overwrite = true, +): Promise { + const customField = await LivechatCustomField.findOneById(key); + if (customField) { + if (customField.scope === 'room') { + return LivechatRooms.updateDataByToken(token, key, value, overwrite); + } + return LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite); + } + + return true; +} diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 4e78811505d77..35d27dbae1238 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -37,7 +37,6 @@ import './methods/saveIntegration'; import './methods/saveTrigger'; import './methods/sendMessageLivechat'; import './methods/sendFileLivechatMessage'; -import './methods/setCustomField'; import './methods/setDepartmentForVisitor'; import './methods/transfer'; import './methods/setUpConnection'; diff --git a/apps/meteor/app/livechat/server/methods/setCustomField.ts b/apps/meteor/app/livechat/server/methods/setCustomField.ts deleted file mode 100644 index 6d3a2384cdd7c..0000000000000 --- a/apps/meteor/app/livechat/server/methods/setCustomField.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { LivechatVisitors, LivechatCustomField, LivechatRooms } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; -import type { UpdateResult, Document } from 'mongodb'; - -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:setCustomField'(token: string, key: string, value: string, overwrite?: boolean): Promise; - } -} - -Meteor.methods({ - async 'livechat:setCustomField'(token, key, value, overwrite = true) { - methodDeprecationLogger.method('livechat:setCustomField', '7.0.0'); - - const customField = await LivechatCustomField.findOneById(key); - if (customField) { - if (customField.scope === 'room') { - return LivechatRooms.updateDataByToken(token, key, value, overwrite); - } - // Save in user - return LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite); - } - - return true; - }, -}); From a341380dbbf41c0542d1c1cea8bf74f936bbe823 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:50:27 +0530 Subject: [PATCH 69/85] chore!: remove deprecated method livechat:saveIntegration (#33451) --- .changeset/real-avocados-sneeze.md | 5 ++ apps/meteor/app/livechat/server/index.ts | 1 - .../server/methods/saveIntegration.ts | 74 ------------------- 3 files changed, 5 insertions(+), 75 deletions(-) create mode 100644 .changeset/real-avocados-sneeze.md delete mode 100644 apps/meteor/app/livechat/server/methods/saveIntegration.ts diff --git a/.changeset/real-avocados-sneeze.md b/.changeset/real-avocados-sneeze.md new file mode 100644 index 0000000000000..af9091481d330 --- /dev/null +++ b/.changeset/real-avocados-sneeze.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated method `livechat:saveIntegration`. Moving forward, use the endpoint `omnichannel/integrations (POST)`. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 35d27dbae1238..90064827e2c64 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -33,7 +33,6 @@ import './methods/saveAgentInfo'; import './methods/saveCustomField'; import './methods/saveDepartment'; import './methods/saveDepartmentAgents'; -import './methods/saveIntegration'; import './methods/saveTrigger'; import './methods/sendMessageLivechat'; import './methods/sendFileLivechatMessage'; diff --git a/apps/meteor/app/livechat/server/methods/saveIntegration.ts b/apps/meteor/app/livechat/server/methods/saveIntegration.ts deleted file mode 100644 index 0a4d82cfbcc14..0000000000000 --- a/apps/meteor/app/livechat/server/methods/saveIntegration.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Settings } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { trim } from '../../../../lib/utils/stringUtils'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:saveIntegration'(values: Record): void; - } -} - -Meteor.methods({ - async 'livechat:saveIntegration'(values) { - methodDeprecationLogger.method('livechat:saveIntegration', '7.0.0'); - - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:saveIntegration', - }); - } - - const settingsIds = [ - typeof values.Livechat_webhookUrl !== 'undefined' && { _id: 'Livechat_webhookUrl', value: trim(values.Livechat_webhookUrl) }, - typeof values.Livechat_secret_token !== 'undefined' && { _id: 'Livechat_secret_token', value: trim(values.Livechat_secret_token) }, - typeof values.Livechat_http_timeout !== 'undefined' && { _id: 'Livechat_http_timeout', value: values.Livechat_http_timeout }, - typeof values.Livechat_webhook_on_start !== 'undefined' && { - _id: 'Livechat_webhook_on_start', - value: !!values.Livechat_webhook_on_start, - }, - typeof values.Livechat_webhook_on_close !== 'undefined' && { - _id: 'Livechat_webhook_on_close', - value: !!values.Livechat_webhook_on_close, - }, - typeof values.Livechat_webhook_on_forward !== 'undefined' && { - _id: 'Livechat_webhook_on_forward', - value: !!values.Livechat_webhook_on_forward, - }, - typeof values.Livechat_webhook_on_chat_taken !== 'undefined' && { - _id: 'Livechat_webhook_on_chat_taken', - value: !!values.Livechat_webhook_on_chat_taken, - }, - typeof values.Livechat_webhook_on_chat_queued !== 'undefined' && { - _id: 'Livechat_webhook_on_chat_queued', - value: !!values.Livechat_webhook_on_chat_queued, - }, - typeof values.Livechat_webhook_on_offline_msg !== 'undefined' && { - _id: 'Livechat_webhook_on_offline_msg', - value: !!values.Livechat_webhook_on_offline_msg, - }, - typeof values.Livechat_webhook_on_visitor_message !== 'undefined' && { - _id: 'Livechat_webhook_on_visitor_message', - value: !!values.Livechat_webhook_on_visitor_message, - }, - typeof values.Livechat_webhook_on_agent_message !== 'undefined' && { - _id: 'Livechat_webhook_on_agent_message', - value: !!values.Livechat_webhook_on_agent_message, - }, - ].filter(Boolean) as unknown as { _id: string; value: any }[]; - - const promises = settingsIds.map((setting) => Settings.updateValueById(setting._id, setting.value)); - - (await Promise.all(promises)).forEach((value, index) => { - if (value?.modifiedCount) { - void notifyOnSettingChangedById(settingsIds[index]._id); - } - }); - }, -}); From 229567045f7a59f0a63b544868ae4950eec4a682 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 10 Oct 2024 01:54:37 +0530 Subject: [PATCH 70/85] chore!: removed deprecated method livechat:getAnalyticsOverviewData (#33442) --- .changeset/forty-pants-roll.md | 5 +++ apps/meteor/app/livechat/server/index.ts | 1 - .../methods/getAnalyticsOverviewData.ts | 41 ------------------- 3 files changed, 5 insertions(+), 42 deletions(-) create mode 100644 .changeset/forty-pants-roll.md delete mode 100644 apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts diff --git a/.changeset/forty-pants-roll.md b/.changeset/forty-pants-roll.md new file mode 100644 index 0000000000000..b8eb5424fab65 --- /dev/null +++ b/.changeset/forty-pants-roll.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': major +--- + +Removes deprecated `livechat:getAnalyticsOverviewData` method. Moving forward use the `livechat/analytics/overview` endpoint. diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 90064827e2c64..b21bc048c254f 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -21,7 +21,6 @@ import './methods/changeLivechatStatus'; import './methods/closeRoom'; import './methods/discardTranscript'; import './methods/getAnalyticsChartData'; -import './methods/getAnalyticsOverviewData'; import './methods/getNextAgent'; import './methods/getRoutingConfig'; import './methods/registerGuest'; diff --git a/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts b/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts deleted file mode 100644 index 9f56cfa570693..0000000000000 --- a/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { AnalyticsOverviewDataResult } from '@rocket.chat/core-services'; -import { OmnichannelAnalytics } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Users } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; - -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { settings } from '../../../settings/server'; - -declare module '@rocket.chat/ddp-client' { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface ServerMethods { - 'livechat:getAnalyticsOverviewData'(options: { analyticsOptions: { name: string } }): AnalyticsOverviewDataResult[] | void; - } -} - -Meteor.methods({ - async 'livechat:getAnalyticsOverviewData'(options) { - methodDeprecationLogger.method('livechat:getAnalyticsOverviewData', '7.0.0', ' Use "livechat/analytics/overview" instead.'); - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'livechat:getAnalyticsOverviewData', - }); - } - - if (!options.analyticsOptions?.name) { - return; - } - - const user = await Users.findOneById(uid, { projection: { _id: 1, utcOffset: 1, language: 1 } }); - const language = user?.language || settings.get('Language') || 'en'; - - return OmnichannelAnalytics.getAnalyticsOverviewData({ - ...options, - utcOffset: user?.utcOffset || 0, - language, - }); - }, -}); From dab2afb5d74ced50c719e26b7a87f676d9fae6f1 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 10 Oct 2024 13:04:30 -0300 Subject: [PATCH 71/85] feat!: Meteor 3.0 - Node.js 20 (#31438) Co-authored-by: Douglas Gubert --- .changeset/large-bikes-brake.md | 14 ++ .changeset/quick-moles-jump.md | 6 + .changeset/sixty-owls-arrive.md | 5 + .../workflows/ci-deploy-gh-pages-preview.yml | 2 +- .github/workflows/ci-deploy-gh-pages.yml | 2 +- .github/workflows/new-release.yml | 2 +- .github/workflows/pr-update-description.yml | 2 +- .github/workflows/publish-release.yml | 2 +- _templates/service/new/package.json.ejs.t | 1 - apps/meteor/.docker-mongo/Dockerfile | 2 +- apps/meteor/.docker/Dockerfile | 2 +- apps/meteor/.docker/Dockerfile.alpine | 68 +------ apps/meteor/.meteor/packages | 83 ++++----- apps/meteor/.meteor/release | 2 +- apps/meteor/.meteor/versions | 175 +++++++++--------- .../authentication/server/startup/index.js | 14 +- .../server/custom_oauth_server.js | 7 +- .../app/file-upload/server/lib/proxy.ts | 17 +- apps/meteor/app/lib/server/lib/RateLimiter.js | 2 +- .../server/lib/interceptDirectReplyEmails.js | 2 +- .../meteor-accounts-saml/server/lib/SAML.ts | 2 +- .../InfoPanel/RetentionPolicyCallout.spec.tsx | 2 +- .../hooks/usePruneWarningMessage.spec.ts | 31 ++-- apps/meteor/client/meteorOverrides/index.ts | 1 - .../client/meteorOverrides/login/linkedin.ts | 48 ----- .../room/body/RetentionPolicyWarning.spec.tsx | 2 +- .../meteor/pauli-linkedin-oauth.d.ts | 3 - apps/meteor/ee/app/license/server/startup.ts | 81 ++++---- apps/meteor/ee/server/services/Dockerfile | 6 +- apps/meteor/ee/server/services/package.json | 1 - .../ee/server/startup/apps/trialExpiration.ts | 15 +- apps/meteor/ee/server/startup/index.ts | 1 - apps/meteor/ee/server/startup/services.ts | 26 +-- apps/meteor/package.json | 6 +- .../meteor/packages/accounts-linkedin/LICENSE | 20 -- .../packages/accounts-linkedin/README.md | 83 --------- .../packages/accounts-linkedin/linkedin.js | 24 --- .../packages/accounts-linkedin/notice.js | 10 - .../packages/accounts-linkedin/package.js | 24 --- .../packages/autoupdate/autoupdate_client.js | 2 +- .../packages/autoupdate/autoupdate_server.js | 59 +++--- apps/meteor/packages/autoupdate/package.js | 3 +- .../.npm/package/npm-shrinkwrap.json | 31 ++-- .../meteor/packages/linkedin-oauth/package.js | 1 - .../packages/meteor-restivus/package.js | 3 - .../packages/meteor-user-presence/package.js | 2 - .../server/configuration/accounts_meld.js | 8 +- apps/meteor/server/lib/cas/createNewUser.ts | 2 +- apps/meteor/server/main.ts | 32 ++-- apps/meteor/server/methods/registerUser.ts | 6 +- apps/meteor/server/services/meteor/service.ts | 19 +- ee/apps/account-service/Dockerfile | 4 +- ee/apps/account-service/package.json | 1 - ee/apps/authorization-service/Dockerfile | 4 +- ee/apps/authorization-service/package.json | 1 - ee/apps/ddp-streamer/Dockerfile | 4 +- ee/apps/ddp-streamer/package.json | 1 - ee/apps/omnichannel-transcript/Dockerfile | 4 +- ee/apps/omnichannel-transcript/package.json | 1 - ee/apps/presence-service/Dockerfile | 4 +- ee/apps/presence-service/package.json | 1 - ee/apps/queue-worker/Dockerfile | 4 +- ee/apps/queue-worker/package.json | 1 - ee/apps/stream-hub-service/Dockerfile | 4 +- ee/apps/stream-hub-service/package.json | 1 - ee/packages/omnichannel-services/package.json | 1 - package.json | 4 +- packages/core-services/package.json | 4 +- .../core-services/src/lib/ContextStore.ts | 22 --- .../src/lib/asyncLocalStorage.ts | 5 +- packages/fuselage-ui-kit/package.json | 2 +- packages/gazzodown/package.json | 6 +- packages/i18n/src/locales/ca.i18n.json | 3 + packages/ui-composer/package.json | 102 +++++----- yarn.lock | 35 +--- 75 files changed, 431 insertions(+), 752 deletions(-) create mode 100644 .changeset/large-bikes-brake.md create mode 100644 .changeset/quick-moles-jump.md create mode 100644 .changeset/sixty-owls-arrive.md delete mode 100644 apps/meteor/client/meteorOverrides/login/linkedin.ts delete mode 100644 apps/meteor/definition/externals/meteor/pauli-linkedin-oauth.d.ts delete mode 100644 apps/meteor/packages/accounts-linkedin/LICENSE delete mode 100644 apps/meteor/packages/accounts-linkedin/README.md delete mode 100644 apps/meteor/packages/accounts-linkedin/linkedin.js delete mode 100644 apps/meteor/packages/accounts-linkedin/notice.js delete mode 100644 apps/meteor/packages/accounts-linkedin/package.js diff --git a/.changeset/large-bikes-brake.md b/.changeset/large-bikes-brake.md new file mode 100644 index 0000000000000..6dfa769cc7a46 --- /dev/null +++ b/.changeset/large-bikes-brake.md @@ -0,0 +1,14 @@ +--- +"@rocket.chat/meteor": major +"rocketchat-services": major +--- + +Upgrades the version of the Meteor framework to 3.0 + +The main reason behind this is the upgrade of the Node.js version, where version 14 will be removed and version 20 will be used instead. + +Internally, significant changes have been made, mostly due to the removal of fibers. + +As a result, it was necessary to adapt our code to work with the new version. + +No functionality should have been affected by this, but if you are running Rocket.Chat in unconventional ways, please note that you need to upgrade your Node.js version. diff --git a/.changeset/quick-moles-jump.md b/.changeset/quick-moles-jump.md new file mode 100644 index 0000000000000..d9912e012acb7 --- /dev/null +++ b/.changeset/quick-moles-jump.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": major +"rocketchat-services": major +--- + +Node.js 20.x support diff --git a/.changeset/sixty-owls-arrive.md b/.changeset/sixty-owls-arrive.md new file mode 100644 index 0000000000000..c756e260635e1 --- /dev/null +++ b/.changeset/sixty-owls-arrive.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Remove linkedin oauth package, now linkedin oauth must to me configured as custom oauth diff --git a/.github/workflows/ci-deploy-gh-pages-preview.yml b/.github/workflows/ci-deploy-gh-pages-preview.yml index 8a0905a174bb5..736117610f8ee 100644 --- a/.github/workflows/ci-deploy-gh-pages-preview.yml +++ b/.github/workflows/ci-deploy-gh-pages-preview.yml @@ -22,7 +22,7 @@ jobs: uses: ./.github/actions/setup-node if: github.event.action != 'closed' with: - node-version: 14.21.3 + node-version: 20.15.1 deno-version: 1.37.1 cache-modules: true install: true diff --git a/.github/workflows/ci-deploy-gh-pages.yml b/.github/workflows/ci-deploy-gh-pages.yml index 0aab8022c7e61..986fe50d72d9b 100644 --- a/.github/workflows/ci-deploy-gh-pages.yml +++ b/.github/workflows/ci-deploy-gh-pages.yml @@ -17,7 +17,7 @@ jobs: - name: Setup NodeJS uses: ./.github/actions/setup-node with: - node-version: 14.21.3 + node-version: 20.15.1 deno-version: 1.37.1 cache-modules: true install: true diff --git a/.github/workflows/new-release.yml b/.github/workflows/new-release.yml index 70e9eb354a064..0483eac311b82 100644 --- a/.github/workflows/new-release.yml +++ b/.github/workflows/new-release.yml @@ -34,7 +34,7 @@ jobs: - name: Setup NodeJS uses: ./.github/actions/setup-node with: - node-version: 14.21.3 + node-version: 20.15.1 deno-version: 1.37.1 cache-modules: true install: true diff --git a/.github/workflows/pr-update-description.yml b/.github/workflows/pr-update-description.yml index 26ffffc6c86f3..1e6985a0cff8c 100644 --- a/.github/workflows/pr-update-description.yml +++ b/.github/workflows/pr-update-description.yml @@ -21,7 +21,7 @@ jobs: - name: Setup NodeJS uses: ./.github/actions/setup-node with: - node-version: 14.21.3 + node-version: 20.15.1 deno-version: 1.37.1 cache-modules: true install: true diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index fe049f6a8369b..b52cac7dd40e6 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -24,7 +24,7 @@ jobs: - name: Setup NodeJS uses: ./.github/actions/setup-node with: - node-version: 14.21.3 + node-version: 20.15.1 deno-version: 1.37.1 cache-modules: true install: true diff --git a/_templates/service/new/package.json.ejs.t b/_templates/service/new/package.json.ejs.t index 0aa1bd69e9950..21b7348b44212 100644 --- a/_templates/service/new/package.json.ejs.t +++ b/_templates/service/new/package.json.ejs.t @@ -29,7 +29,6 @@ to: ee/apps/<%= name %>/package.json "@types/node": "^14.18.51", "ejson": "^2.2.3", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "mem": "^8.1.1", "moleculer": "^0.14.29", "mongodb": "^4.12.1", diff --git a/apps/meteor/.docker-mongo/Dockerfile b/apps/meteor/.docker-mongo/Dockerfile index 037aadf4917fb..074eb00ad761c 100644 --- a/apps/meteor/.docker-mongo/Dockerfile +++ b/apps/meteor/.docker-mongo/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-bullseye-slim +FROM node:20.15.1-bullseye-slim LABEL maintainer="buildmaster@rocket.chat" diff --git a/apps/meteor/.docker/Dockerfile b/apps/meteor/.docker/Dockerfile index 75df2cb90678b..f8acfc80f03ef 100644 --- a/apps/meteor/.docker/Dockerfile +++ b/apps/meteor/.docker/Dockerfile @@ -2,7 +2,7 @@ ARG DENO_VERSION="1.37.1" FROM denoland/deno:bin-${DENO_VERSION} as deno -FROM node:14.21.3-bullseye-slim +FROM node:20.15.1-bullseye-slim LABEL maintainer="buildmaster@rocket.chat" diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index aaa2d2552ab3b..43f24e47b2096 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -1,62 +1,10 @@ -ARG DENO_VERSION="1.37.1" - -FROM denoland/deno:bin-${DENO_VERSION} as deno - -FROM node:14.21.3-alpine3.16 +FROM node:20.15.1-alpine3.20 LABEL maintainer="buildmaster@rocket.chat" ENV LANG=C.UTF-8 -## Alpine 3.16 does not have a deno package, but newer versions have it -## So as soon as we can update the Alpine version, we can replace the following -## GLIBC installation part by an `apk add deno` - -# Installing glibc deps required by Deno -# This replaces libc6-compat -# Copied from https://github.com/Docker-Hub-frolvlad/docker-alpine-glibc, which denoland/deno:alpine-1.37.1 uses -# NOTE: Glibc 2.35 package is broken: https://github.com/sgerrand/alpine-pkg-glibc/issues/176, so we stick to 2.34 for now -RUN ALPINE_GLIBC_BASE_URL="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" && \ - ALPINE_GLIBC_PACKAGE_VERSION="2.34-r0" && \ - ALPINE_GLIBC_BASE_PACKAGE_FILENAME="glibc-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ - ALPINE_GLIBC_BIN_PACKAGE_FILENAME="glibc-bin-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ - ALPINE_GLIBC_I18N_PACKAGE_FILENAME="glibc-i18n-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ - apk add --no-cache --virtual=.build-dependencies wget ca-certificates ttf-dejavu && \ - echo \ - "-----BEGIN PUBLIC KEY-----\ - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApZ2u1KJKUu/fW4A25y9m\ - y70AGEa/J3Wi5ibNVGNn1gT1r0VfgeWd0pUybS4UmcHdiNzxJPgoWQhV2SSW1JYu\ - tOqKZF5QSN6X937PTUpNBjUvLtTQ1ve1fp39uf/lEXPpFpOPL88LKnDBgbh7wkCp\ - m2KzLVGChf83MS0ShL6G9EQIAUxLm99VpgRjwqTQ/KfzGtpke1wqws4au0Ab4qPY\ - KXvMLSPLUp7cfulWvhmZSegr5AdhNw5KNizPqCJT8ZrGvgHypXyiFvvAH5YRtSsc\ - Zvo9GI2e2MaZyo9/lvb+LbLEJZKEQckqRj4P26gmASrZEPStwc+yqy1ShHLA0j6m\ - 1QIDAQAB\ - -----END PUBLIC KEY-----" | sed 's/ */\n/g' > "/etc/apk/keys/sgerrand.rsa.pub" && \ - wget \ - "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \ - mv /etc/nsswitch.conf /etc/nsswitch.conf.bak && \ - apk add --no-cache --force-overwrite \ - "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \ - \ - mv /etc/nsswitch.conf.bak /etc/nsswitch.conf && \ - rm "/etc/apk/keys/sgerrand.rsa.pub" && \ - (/usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true) && \ - echo "export LANG=$LANG" > /etc/profile.d/locale.sh && \ - \ - apk del glibc-i18n && \ - \ - rm "/root/.wget-hsts" && \ - apk del .build-dependencies && \ - rm \ - "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \ - "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" - -COPY --from=deno /deno /bin/deno +RUN apk add --no-cache deno ttf-dejavu ADD . /app @@ -70,7 +18,7 @@ ENV DEPLOY_METHOD=docker \ Accounts_AvatarStorePath=/app/uploads RUN set -x \ - && apk add --no-cache --virtual .fetch-deps python3 make g++ \ + && apk add --no-cache --virtual .fetch-deps python3 make g++ py3-setuptools libc6-compat \ && cd /app/bundle/programs/server \ && npm install --omit=dev --unsafe-perm \ # Start hack for sharp... @@ -78,11 +26,11 @@ RUN set -x \ && npm install sharp@0.32.6 \ && mv node_modules/sharp npm/node_modules/sharp \ # End hack for sharp - # Start hack for isolated-vm... - && rm -rf npm/node_modules/isolated-vm \ - && npm install isolated-vm@4.4.2 \ - && mv node_modules/isolated-vm npm/node_modules/isolated-vm \ - # End hack for isolated-vm + # # Start hack for isolated-vm... + # && rm -rf npm/node_modules/isolated-vm \ + # && npm install isolated-vm@4.6.0 \ + # && mv node_modules/isolated-vm npm/node_modules/isolated-vm \ + # # End hack for isolated-vm && cd /app/bundle/programs/server/npm \ && npm rebuild bcrypt --build-from-source \ && npm cache clear --force \ diff --git a/apps/meteor/.meteor/packages b/apps/meteor/.meteor/packages index 307b0d89eb0dc..70518d252df9d 100644 --- a/apps/meteor/.meteor/packages +++ b/apps/meteor/.meteor/packages @@ -8,43 +8,40 @@ rocketchat:ddp rocketchat:mongo-config -rocketchat:oauth2-server rocketchat:restivus rocketchat:livechat rocketchat:streamer rocketchat:version rocketchat:user-presence -accounts-base@2.2.11 -accounts-facebook@1.3.3 -accounts-github@1.5.0 -accounts-google@1.4.0 -accounts-meteor-developer@1.5.0 -accounts-oauth@1.4.4 -accounts-password@2.4.0 -accounts-twitter@1.5.0 - -pauli:accounts-linkedin -google-oauth@1.4.4 -oauth@2.2.1 -oauth2@1.3.2 - -check@1.4.1 -ddp-rate-limiter@1.2.1 -rate-limit@1.1.1 -email@2.2.6 -http@2.0.0 - -meteor-base@1.5.1 -ddp-common@1.4.1 -webapp@1.13.8 - -mongo@1.16.10 - -reload@1.3.1 -service-configuration@1.3.4 -session@1.2.1 -shell-server@0.5.0 +accounts-base@3.0.2 +accounts-facebook@1.3.4 +accounts-github@1.5.1 +accounts-google@1.4.1 +accounts-meteor-developer@1.5.1 +accounts-oauth@1.4.5 +accounts-password@3.0.2 +accounts-twitter@1.5.2 + +google-oauth@1.4.5 +oauth@3.0.0 +oauth2@1.3.3 + +check@1.4.2 +ddp-rate-limiter@1.2.2 +rate-limit@1.1.2 +email@3.1.0 + +meteor-base@1.5.2 +ddp-common@1.4.4 +webapp@2.0.1 + +mongo@2.0.2 + +reload@1.3.2 +service-configuration@1.3.5 +session@1.2.2 +shell-server@0.6.0 dispatch:run-as-user ostrio:cookies @@ -53,22 +50,22 @@ kadira:flow-router meteorhacks:inject-initial -routepolicy@1.1.1 +routepolicy@1.1.2 -webapp-hashing@1.1.1 -facts-base@1.0.1 +webapp-hashing@1.1.2 +facts-base@1.0.2 -tracker@1.3.3 -reactive-dict@1.3.1 -reactive-var@1.0.12 +tracker@1.3.4 +reactive-dict@1.3.2 +reactive-var@1.0.13 -babel-compiler@7.10.5 -standard-minifier-css@1.9.2 -dynamic-import@0.7.3 -ecmascript@0.16.8 -typescript@4.9.5 +babel-compiler@7.11.0 +standard-minifier-css@1.9.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +typescript@5.4.3 -autoupdate@1.8.0 +autoupdate@2.0.0 # photoswipe diff --git a/apps/meteor/.meteor/release b/apps/meteor/.meteor/release index 5152abe9d5821..c41cba9c61a21 100644 --- a/apps/meteor/.meteor/release +++ b/apps/meteor/.meteor/release @@ -1 +1 @@ -METEOR@2.16 +METEOR@3.0.3 diff --git a/apps/meteor/.meteor/versions b/apps/meteor/.meteor/versions index 416ae456f05bd..c41710f5aa16e 100644 --- a/apps/meteor/.meteor/versions +++ b/apps/meteor/.meteor/versions @@ -1,102 +1,97 @@ -accounts-base@2.2.11 -accounts-facebook@1.3.3 -accounts-github@1.5.0 -accounts-google@1.4.0 -accounts-meteor-developer@1.5.0 -accounts-oauth@1.4.4 -accounts-password@2.4.0 -accounts-twitter@1.5.0 -allow-deny@1.1.1 -autoupdate@1.8.0 -babel-compiler@7.10.5 -babel-runtime@1.5.1 -base64@1.0.12 -binary-heap@1.0.11 -boilerplate-generator@1.7.2 -caching-compiler@1.2.2 -callback-hook@1.5.1 -check@1.4.1 -coffeescript@2.7.0 -coffeescript-compiler@2.4.1 -ddp@1.4.1 -ddp-client@2.6.2 -ddp-common@1.4.1 -ddp-rate-limiter@1.2.1 -ddp-server@2.7.1 -diff-sequence@1.1.2 +accounts-base@3.0.2 +accounts-facebook@1.3.4 +accounts-github@1.5.1 +accounts-google@1.4.1 +accounts-meteor-developer@1.5.1 +accounts-oauth@1.4.5 +accounts-password@3.0.2 +accounts-twitter@1.5.2 +allow-deny@2.0.0 +autoupdate@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.1 +ddp-common@1.4.4 +ddp-rate-limiter@1.2.2 +ddp-server@3.0.1 +diff-sequence@1.1.3 dispatch:run-as-user@1.1.1 -dynamic-import@0.7.3 -ecmascript@0.16.8 -ecmascript-runtime@0.8.1 -ecmascript-runtime-client@0.12.1 -ecmascript-runtime-server@0.11.0 -ejson@1.1.3 -email@2.2.6 -es5-shim@4.8.0 -facebook-oauth@1.11.3 -facts-base@1.0.1 -fetch@0.1.4 -geojson-utils@1.0.11 -github-oauth@1.4.1 -google-oauth@1.4.4 -hot-code-push@1.0.4 -http@2.0.0 -id-map@1.1.1 -inter-process-messaging@0.1.1 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +email@3.1.0 +es5-shim@4.8.1 +facebook-oauth@1.11.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +github-oauth@1.4.2 +google-oauth@1.4.5 +hot-code-push@1.0.5 +http@1.4.4 +id-map@1.2.0 +inter-process-messaging@0.1.2 kadira:flow-router@2.12.1 -localstorage@1.2.0 -logging@1.3.4 -meteor@1.11.5 -meteor-base@1.5.1 -meteor-developer-oauth@1.3.2 +localstorage@1.2.1 +logging@1.3.5 +meteor@2.0.1 +meteor-base@1.5.2 +meteor-developer-oauth@1.3.3 meteorhacks:inject-initial@1.0.5 -minifier-css@1.6.4 -minimongo@1.9.4 -modern-browsers@0.1.10 -modules@0.20.0 -modules-runtime@0.13.1 -mongo@1.16.10 -mongo-decimal@0.1.3 -mongo-dev-server@1.1.0 -mongo-id@1.0.8 -npm-mongo@4.17.2 -oauth@2.2.1 -oauth1@1.5.1 -oauth2@1.3.2 -ordered-dict@1.1.0 +minifier-css@2.0.0 +minimongo@2.0.1 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.2 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.4 +oauth@3.0.0 +oauth1@1.5.2 +oauth2@1.3.3 +ordered-dict@1.2.0 ostrio:cookies@2.7.2 -pauli:accounts-linkedin@6.0.0 -pauli:linkedin-oauth@6.0.0 -promise@0.12.2 -random@1.2.1 -rate-limit@1.1.1 -react-fast-refresh@0.2.8 -reactive-dict@1.3.1 -reactive-var@1.0.12 -reload@1.3.1 -retry@1.1.0 +promise@1.0.0 +random@1.2.2 +rate-limit@1.1.2 +react-fast-refresh@0.2.9 +reactive-dict@1.3.2 +reactive-var@1.0.13 +reload@1.3.2 +retry@1.1.1 rocketchat:ddp@0.0.1 rocketchat:livechat@0.0.1 rocketchat:mongo-config@0.0.1 -rocketchat:oauth2-server@3.0.0 rocketchat:restivus@1.1.0 rocketchat:streamer@1.1.0 rocketchat:user-presence@2.6.3 rocketchat:version@1.0.0 -routepolicy@1.1.1 -service-configuration@1.3.4 -session@1.2.1 -sha@1.0.9 -shell-server@0.5.0 -socket-stream-client@0.5.2 -standard-minifier-css@1.9.2 -tracker@1.3.3 -twitter-oauth@1.3.3 -typescript@4.9.5 -underscore@1.6.1 -url@1.3.2 -webapp@1.13.8 -webapp-hashing@1.1.1 +routepolicy@1.1.2 +service-configuration@1.3.5 +session@1.2.2 +sha@1.0.10 +shell-server@0.6.0 +socket-stream-client@0.5.3 +standard-minifier-css@1.9.3 +tracker@1.3.4 +twitter-oauth@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +url@1.3.3 +webapp@2.0.1 +webapp-hashing@1.1.2 zodern:caching-minifier@0.5.0 -zodern:standard-minifier-js@5.3.1 -zodern:types@1.0.13 +zodern:standard-minifier-js@5.2.0 +zodern:types@1.0.10 diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index 2e4c599ce558c..10ba3fd44154b 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -262,11 +262,12 @@ const onCreateUserAsync = async function (options, user = {}) { Accounts.onCreateUser(function (...args) { // Depends on meteor support for Async - return Promise.await(onCreateUserAsync.call(this, ...args)); + return onCreateUserAsync.call(this, ...args); }); const { insertUserDoc } = Accounts; -const insertUserDocAsync = async function (options, user) { + +Accounts.insertUserDoc = async function (options, user) { const globalRoles = new Set(); if (Match.test(options.globalRoles, [String]) && options.globalRoles.length > 0) { @@ -307,7 +308,7 @@ const insertUserDocAsync = async function (options, user) { user.roles = []; } - const _id = insertUserDoc.call(Accounts, options, user); + const _id = await insertUserDoc.call(Accounts, options, user); user = await Users.findOne({ _id, @@ -368,11 +369,6 @@ const insertUserDocAsync = async function (options, user) { return _id; }; -Accounts.insertUserDoc = function (...args) { - // Depends on meteor support for Async - return Promise.await(insertUserDocAsync.call(this, ...args)); -}; - const validateLoginAttemptAsync = async function (login) { login = await callbacks.run('beforeValidateLogin', login); @@ -442,7 +438,7 @@ const validateLoginAttemptAsync = async function (login) { Accounts.validateLoginAttempt(function (...args) { // Depends on meteor support for Async - return Promise.await(validateLoginAttemptAsync.call(this, ...args)); + return validateLoginAttemptAsync.call(this, ...args); }); Accounts.validateNewUser((user) => { diff --git a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js index 16407bf134de4..a546a7c527eb0 100644 --- a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js +++ b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js @@ -437,7 +437,8 @@ export class CustomOAuth { } const { updateOrCreateUserFromExternalService } = Accounts; -const updateOrCreateUserFromExternalServiceAsync = async function (...args /* serviceName, serviceData, options*/) { + +Accounts.updateOrCreateUserFromExternalService = async function (...args /* serviceName, serviceData, options*/) { for await (const hook of BeforeUpdateOrCreateUserFromExternalService) { await hook.apply(this, args); } @@ -458,7 +459,3 @@ const updateOrCreateUserFromExternalServiceAsync = async function (...args /* se return user; }; - -Accounts.updateOrCreateUserFromExternalService = function (...args /* serviceName, serviceData, options*/) { - return Promise.await(updateOrCreateUserFromExternalServiceAsync.call(this, ...args)); -}; diff --git a/apps/meteor/app/file-upload/server/lib/proxy.ts b/apps/meteor/app/file-upload/server/lib/proxy.ts index 048424af4bd9a..dd9e692f0f98d 100644 --- a/apps/meteor/app/file-upload/server/lib/proxy.ts +++ b/apps/meteor/app/file-upload/server/lib/proxy.ts @@ -99,8 +99,15 @@ async function handle(req: createServer.IncomingMessage, res: http.ServerRespons }); } -WebApp.connectHandlers.stack.unshift({ - route: '', - // eslint-disable-next-line @typescript-eslint/no-misused-promises - handle, -}); +// @ts-expect-error - l +const dummyRouter = WebApp.connectHandlers._router; + +// Create a dummy route +dummyRouter.route('*'); +// fetch the newly created "layer" +const stackedRoute = dummyRouter.stack.pop(); +stackedRoute.handle = handle; + +// Move the layer to the top :) +// @ts-expect-error - l +WebApp.connectHandlers._router.stack.unshift(stackedRoute); diff --git a/apps/meteor/app/lib/server/lib/RateLimiter.js b/apps/meteor/app/lib/server/lib/RateLimiter.js index 126b236426dad..e1986e49e81eb 100644 --- a/apps/meteor/app/lib/server/lib/RateLimiter.js +++ b/apps/meteor/app/lib/server/lib/RateLimiter.js @@ -94,7 +94,7 @@ export const RateLimiterClass = new (class { name: methodName, }; Object.entries(matchers).forEach(([key, matcher]) => { - match[key] = (...args) => Promise.await(matcher(...args)); + match[key] = (...args) => matcher(...args); }); return DDPRateLimiter.addRule(match, numRequests, timeInterval); } diff --git a/apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.js b/apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.js index 41a704d5afb6c..f9af0911bcc68 100644 --- a/apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.js +++ b/apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.js @@ -21,7 +21,7 @@ export class DirectReplyIMAPInterceptor extends IMAPInterceptor { super(imapConfig, options); - this.on('email', (email) => Promise.await(processDirectEmail(email))); + this.on('email', (email) => processDirectEmail(email)); } } diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts index 6e68518ef31c4..be9bef0f2e7a5 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts @@ -163,7 +163,7 @@ export class SAML { } } - const userId = Accounts.insertUserDoc({}, newUser); + const userId = await Accounts.insertUserDoc({}, newUser); user = await Users.findOneById(userId); if (user && userObject.channels && channelsAttributeUpdate !== true) { diff --git a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx index 8db42e8c649d9..a9305b5f1f15c 100644 --- a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx +++ b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx @@ -18,7 +18,7 @@ describe('RetentionPolicyCallout', () => { legacyRoot: true, wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }), }); - expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024, 12:30 AM'); + expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024 at 12:30 AM'); }); it('Should not render callout if settings are invalid', () => { diff --git a/apps/meteor/client/hooks/usePruneWarningMessage.spec.ts b/apps/meteor/client/hooks/usePruneWarningMessage.spec.ts index 4f51bd2040de6..2bf4b813be12b 100644 --- a/apps/meteor/client/hooks/usePruneWarningMessage.spec.ts +++ b/apps/meteor/client/hooks/usePruneWarningMessage.spec.ts @@ -20,6 +20,9 @@ const getRetentionRoomProps = (props: Partial { jest.setSystemTime(new Date(2024, 5, 1, 0, 0, 0)); @@ -36,9 +39,9 @@ describe('usePruneWarningMessage hook', () => { TTLChannels: 60000, }), }); - expect(result.current).toEqual('a minute June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('a minute June 1, 2024 at 12:30 AM'); jest.advanceTimersByTime(31 * 60 * 1000); - expect(result.current).toEqual('a minute June 1, 2024, 1:00 AM'); + expect(result.current).toEqual('a minute June 1, 2024 at 1:00 AM'); }); it('Should return the default warning with precision set to every_hour', () => { @@ -51,7 +54,7 @@ describe('usePruneWarningMessage hook', () => { precision: '1', }), }); - expect(result.current).toEqual('a minute June 1, 2024, 1:00 AM'); + expect(result.current).toEqual('a minute June 1, 2024 at 1:00 AM'); }); it('Should return the default warning with precision set to every_six_hours', () => { @@ -64,7 +67,7 @@ describe('usePruneWarningMessage hook', () => { precision: '2', }), }); - expect(result.current).toEqual('a minute June 1, 2024, 6:00 AM'); + expect(result.current).toEqual('a minute June 1, 2024 at 6:00 AM'); }); it('Should return the default warning with precision set to every_day', () => { @@ -77,7 +80,7 @@ describe('usePruneWarningMessage hook', () => { precision: '3', }), }); - expect(result.current).toEqual('a minute June 2, 2024, 12:00 AM'); + expect(result.current).toEqual('a minute June 2, 2024 at 12:00 AM'); }); it('Should return the default warning with advanced precision', () => { @@ -91,7 +94,7 @@ describe('usePruneWarningMessage hook', () => { advancedPrecisionCron: '0 0 1 */1 *', }), }); - expect(result.current).toEqual('a minute July 1, 2024, 12:00 AM'); + expect(result.current).toEqual('a minute July 1, 2024 at 12:00 AM'); }); }); @@ -105,7 +108,7 @@ describe('usePruneWarningMessage hook', () => { TTLChannels: 60000, }), }); - expect(result.current).toEqual('a minute June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('a minute June 1, 2024 at 12:30 AM'); }); it('Should return the unpinned messages warning', () => { @@ -118,7 +121,7 @@ describe('usePruneWarningMessage hook', () => { doNotPrunePinned: true, }), }); - expect(result.current).toEqual('Unpinned a minute June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('Unpinned a minute June 1, 2024 at 12:30 AM'); }); it('Should return the files only warning', () => { @@ -132,7 +135,7 @@ describe('usePruneWarningMessage hook', () => { filesOnly: true, }), }); - expect(result.current).toEqual('FilesOnly a minute June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('FilesOnly a minute June 1, 2024 at 12:30 AM'); }); it('Should return the unpinned files only warning', () => { @@ -147,7 +150,7 @@ describe('usePruneWarningMessage hook', () => { doNotPrunePinned: true, }), }); - expect(result.current).toEqual('UnpinnedFilesOnly a minute June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('UnpinnedFilesOnly a minute June 1, 2024 at 12:30 AM'); }); }); @@ -158,7 +161,7 @@ describe('usePruneWarningMessage hook', () => { legacyRoot: true, wrapper: createMock(), }); - expect(result.current).toEqual('30 days June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('30 days June 1, 2024 at 12:30 AM'); }); it('Should return the unpinned messages warning', () => { @@ -167,7 +170,7 @@ describe('usePruneWarningMessage hook', () => { legacyRoot: true, wrapper: createMock(), }); - expect(result.current).toEqual('Unpinned 30 days June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('Unpinned 30 days June 1, 2024 at 12:30 AM'); }); it('Should return the files only warning', () => { @@ -177,7 +180,7 @@ describe('usePruneWarningMessage hook', () => { legacyRoot: true, wrapper: createMock(), }); - expect(result.current).toEqual('FilesOnly 30 days June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('FilesOnly 30 days June 1, 2024 at 12:30 AM'); }); it('Should return the unpinned files only warning', () => { @@ -187,7 +190,7 @@ describe('usePruneWarningMessage hook', () => { legacyRoot: true, wrapper: createMock(), }); - expect(result.current).toEqual('UnpinnedFilesOnly 30 days June 1, 2024, 12:30 AM'); + expect(result.current).toEqual('UnpinnedFilesOnly 30 days June 1, 2024 at 12:30 AM'); }); }); }); diff --git a/apps/meteor/client/meteorOverrides/index.ts b/apps/meteor/client/meteorOverrides/index.ts index 9a1b0eb1f7be0..b8e14d4138bd0 100644 --- a/apps/meteor/client/meteorOverrides/index.ts +++ b/apps/meteor/client/meteorOverrides/index.ts @@ -8,7 +8,6 @@ import './login/facebook'; import './login/github'; import './login/google'; import './login/ldap'; -import './login/linkedin'; import './login/meteorDeveloperAccount'; import './login/oauth'; import './login/password'; diff --git a/apps/meteor/client/meteorOverrides/login/linkedin.ts b/apps/meteor/client/meteorOverrides/login/linkedin.ts deleted file mode 100644 index a10b8182feec3..0000000000000 --- a/apps/meteor/client/meteorOverrides/login/linkedin.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { LinkedinOAuthConfiguration } from '@rocket.chat/core-typings'; -import { Random } from '@rocket.chat/random'; -import { Meteor } from 'meteor/meteor'; -import { OAuth } from 'meteor/oauth'; -import { Linkedin } from 'meteor/pauli:linkedin-oauth'; - -import type { LoginCallback } from '../../lib/2fa/overrideLoginMethod'; -import { overrideLoginMethod } from '../../lib/2fa/overrideLoginMethod'; -import { wrapRequestCredentialFn } from '../../lib/wrapRequestCredentialFn'; -import { createOAuthTotpLoginMethod } from './oauth'; - -declare module 'meteor/meteor' { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Meteor { - function loginWithLinkedin(options?: Meteor.LoginWithExternalServiceOptions, callback?: LoginCallback): void; - } -} -const { loginWithLinkedin } = Meteor; -const loginWithLinkedinAndTOTP = createOAuthTotpLoginMethod(Linkedin); -Meteor.loginWithLinkedin = (options, callback) => { - overrideLoginMethod(loginWithLinkedin, [options], callback, loginWithLinkedinAndTOTP); -}; - -Linkedin.requestCredential = wrapRequestCredentialFn( - 'linkedin', - ({ options, credentialRequestCompleteCallback, config, loginStyle }) => { - const credentialToken = Random.secret(); - - const { requestPermissions } = options; - const scope = (requestPermissions || ['openid', 'email', 'profile']).join('+'); - - const loginUrl = `https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=${ - config.clientId - }&redirect_uri=${OAuth._redirectUri('linkedin', config)}&state=${OAuth._stateParam(loginStyle, credentialToken)}&scope=${scope}`; - - OAuth.launchLogin({ - credentialRequestCompleteCallback, - credentialToken, - loginService: 'linkedin', - loginStyle, - loginUrl, - popupOptions: { - width: 390, - height: 628, - }, - }); - }, -); diff --git a/apps/meteor/client/views/room/body/RetentionPolicyWarning.spec.tsx b/apps/meteor/client/views/room/body/RetentionPolicyWarning.spec.tsx index 712a2b9850cc0..5e4e13fa3eab9 100644 --- a/apps/meteor/client/views/room/body/RetentionPolicyWarning.spec.tsx +++ b/apps/meteor/client/views/room/body/RetentionPolicyWarning.spec.tsx @@ -18,7 +18,7 @@ describe('RetentionPolicyWarning', () => { legacyRoot: true, wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }), }); - expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024, 12:30 AM'); + expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024 at 12:30 AM'); }); it('Should not render callout if settings are invalid', () => { diff --git a/apps/meteor/definition/externals/meteor/pauli-linkedin-oauth.d.ts b/apps/meteor/definition/externals/meteor/pauli-linkedin-oauth.d.ts deleted file mode 100644 index 14c3130597102..0000000000000 --- a/apps/meteor/definition/externals/meteor/pauli-linkedin-oauth.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module 'meteor/pauli:linkedin-oauth' { - export const Linkedin: any; -} diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index 3c47c87386134..14f88bc27d613 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -105,57 +105,60 @@ export const startLicense = async () => { } }; - // When settings are loaded, apply the current license if there is one. - settings.onReady(async () => { - if (!(await applyLicense(settings.get('Enterprise_License') ?? '', false))) { - // License from the envvar is always treated as new, because it would have been saved on the setting if it was already in use. - if (process.env.ROCKETCHAT_LICENSE && !License.hasValidLicense()) { - await applyLicense(process.env.ROCKETCHAT_LICENSE, true); + License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount()); + License.setLicenseLimitCounter('guestUsers', () => Users.getActiveLocalGuestCount()); + License.setLicenseLimitCounter('roomsPerGuest', async (context) => (context?.userId ? Subscriptions.countByUserId(context.userId) : 0)); + License.setLicenseLimitCounter('privateApps', () => getAppCount('private')); + License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace')); + License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatVisitors.countVisitorsOnPeriod(moment.utc().format('YYYY-MM'))); + + return new Promise((resolve) => { + // When settings are loaded, apply the current license if there is one. + settings.onReady(async () => { + if (!(await applyLicense(settings.get('Enterprise_License') ?? '', false))) { + // License from the envvar is always treated as new, because it would have been saved on the setting if it was already in use. + if (process.env.ROCKETCHAT_LICENSE && !License.hasValidLicense()) { + await applyLicense(process.env.ROCKETCHAT_LICENSE, true); + } } - } - // After the current license is already loaded, watch the setting value to react to new licenses being applied. - settings.change('Enterprise_License', (license) => applyLicenseOrRemove(license, true)); + // After the current license is already loaded, watch the setting value to react to new licenses being applied. + settings.change('Enterprise_License', (license) => applyLicenseOrRemove(license, true)); - callbacks.add('workspaceLicenseRemoved', () => License.remove()); + callbacks.add('workspaceLicenseRemoved', () => License.remove()); - callbacks.add('workspaceLicenseChanged', (updatedLicense) => applyLicense(updatedLicense, true)); + callbacks.add('workspaceLicenseChanged', (updatedLicense) => applyLicense(updatedLicense, true)); - License.onInstall(async () => void api.broadcast('license.actions', {} as Record, boolean>)); + License.onInstall(async () => void api.broadcast('license.actions', {} as Record, boolean>)); - License.onInvalidate(async () => void api.broadcast('license.actions', {} as Record, boolean>)); + License.onInvalidate(async () => void api.broadcast('license.actions', {} as Record, boolean>)); - License.onBehaviorTriggered('prevent_action', (context) => syncByTriggerDebounced(`prevent_action_${context.limit}`)); + License.onBehaviorTriggered('prevent_action', (context) => syncByTriggerDebounced(`prevent_action_${context.limit}`)); - License.onBehaviorTriggered('start_fair_policy', async (context) => syncByTriggerDebounced(`start_fair_policy_${context.limit}`)); + License.onBehaviorTriggered('start_fair_policy', async (context) => syncByTriggerDebounced(`start_fair_policy_${context.limit}`)); - License.onBehaviorTriggered('disable_modules', async (context) => syncByTriggerDebounced(`disable_modules_${context.limit}`)); + License.onBehaviorTriggered('disable_modules', async (context) => syncByTriggerDebounced(`disable_modules_${context.limit}`)); - License.onChange(() => api.broadcast('license.sync')); + License.onChange(() => api.broadcast('license.sync')); - License.onBehaviorToggled('prevent_action', (context) => { - if (!context.limit) { - return; - } - void api.broadcast('license.actions', { - [context.limit]: true, - } as Record, boolean>); - }); + License.onBehaviorToggled('prevent_action', (context) => { + if (!context.limit) { + return; + } + void api.broadcast('license.actions', { + [context.limit]: true, + } as Record, boolean>); + }); - License.onBehaviorToggled('allow_action', (context) => { - if (!context.limit) { - return; - } - void api.broadcast('license.actions', { - [context.limit]: false, - } as Record, boolean>); + License.onBehaviorToggled('allow_action', (context) => { + if (!context.limit) { + return; + } + void api.broadcast('license.actions', { + [context.limit]: false, + } as Record, boolean>); + }); + resolve(); }); }); - - License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount()); - License.setLicenseLimitCounter('guestUsers', () => Users.getActiveLocalGuestCount()); - License.setLicenseLimitCounter('roomsPerGuest', async (context) => (context?.userId ? Subscriptions.countByUserId(context.userId) : 0)); - License.setLicenseLimitCounter('privateApps', () => getAppCount('private')); - License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace')); - License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatVisitors.countVisitorsOnPeriod(moment.utc().format('YYYY-MM'))); }; diff --git a/apps/meteor/ee/server/services/Dockerfile b/apps/meteor/ee/server/services/Dockerfile index fa2abf88946a8..eb8a7de0a4c1c 100644 --- a/apps/meteor/ee/server/services/Dockerfile +++ b/apps/meteor/ee/server/services/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3 as build +FROM node:20.15.1 as build WORKDIR /app @@ -28,7 +28,7 @@ COPY ./tsconfig.base.json . RUN yarn workspace @rocket.chat/core-typings run build \ && yarn workspace @rocket.chat/rest-typings run build -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -69,7 +69,7 @@ ENV NODE_ENV=production \ WORKDIR /app/apps/meteor/ee/server/services RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/apps/meteor/ee/server/services/package.json b/apps/meteor/ee/server/services/package.json index 1ea056a086064..f16819ffac268 100644 --- a/apps/meteor/ee/server/services/package.json +++ b/apps/meteor/ee/server/services/package.json @@ -36,7 +36,6 @@ "ejson": "^2.2.3", "eventemitter3": "^4.0.7", "express": "^4.17.3", - "fibers": "^5.0.3", "jaeger-client": "^3.19.0", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/apps/meteor/ee/server/startup/apps/trialExpiration.ts b/apps/meteor/ee/server/startup/apps/trialExpiration.ts index 3c4d802916474..f9a6800951b08 100644 --- a/apps/meteor/ee/server/startup/apps/trialExpiration.ts +++ b/apps/meteor/ee/server/startup/apps/trialExpiration.ts @@ -1,12 +1,15 @@ import { License } from '@rocket.chat/license'; import { Meteor } from 'meteor/meteor'; +import { Apps } from '../../apps'; + Meteor.startup(async () => { - const { Apps } = await import('../../apps'); - License.onInvalidateLicense(async () => { - void Apps.migratePrivateApps(); - }); - License.onRemoveLicense(async () => { + const migratePrivateAppsCallback = async () => { + if (!Apps.isInitialized) return; + void Apps.migratePrivateApps(); - }); + }; + + License.onInvalidateLicense(migratePrivateAppsCallback); + License.onRemoveLicense(migratePrivateAppsCallback); }); diff --git a/apps/meteor/ee/server/startup/index.ts b/apps/meteor/ee/server/startup/index.ts index eb09ca6ed30f4..51bb011828ba2 100644 --- a/apps/meteor/ee/server/startup/index.ts +++ b/apps/meteor/ee/server/startup/index.ts @@ -4,7 +4,6 @@ import './audit'; import './deviceManagement'; import './engagementDashboard'; import './maxRoomsPerGuest'; -import './services'; import './upsell'; import { api } from '@rocket.chat/core-services'; diff --git a/apps/meteor/ee/server/startup/services.ts b/apps/meteor/ee/server/startup/services.ts index dc072fb1d9b07..c7cd0491bda9a 100644 --- a/apps/meteor/ee/server/startup/services.ts +++ b/apps/meteor/ee/server/startup/services.ts @@ -26,17 +26,19 @@ if (!isRunningMs()) { api.registerService(new InstanceService()); } -let federationService: FederationService; +export const startFederationService = async (): Promise => { + let federationService: FederationService; -if (!License.hasValidLicense()) { - federationService = await FederationService.createFederationService(); - api.registerService(federationService); -} - -void License.onLicense('federation', async () => { - const federationServiceEE = await FederationServiceEE.createFederationService(); - if (federationService) { - await api.destroyService(federationService); + if (!License.hasValidLicense()) { + federationService = await FederationService.createFederationService(); + api.registerService(federationService); } - api.registerService(federationServiceEE); -}); + + void License.onLicense('federation', async () => { + const federationServiceEE = await FederationServiceEE.createFederationService(); + if (federationService) { + await api.destroyService(federationService); + } + api.registerService(federationServiceEE); + }); +}; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 46cca9cceb2f1..9bee550462737 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -25,7 +25,7 @@ "debug": "meteor run --inspect", "debug-brk": "meteor run --inspect-brk", "lint": "yarn stylelint && yarn eslint", - "eslint": "NODE_OPTIONS=\"--max-old-space-size=6144\" eslint --ext .js,.jsx,.ts,.tsx . --cache", + "eslint": "NODE_OPTIONS=\"--max-old-space-size=8192\" eslint --ext .js,.jsx,.ts,.tsx . --cache", "eslint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix --cache", "obj:dev": "TEST_MODE=true yarn dev", "stylelint": "stylelint \"app/**/*.css\" \"client/**/*.css\" \"app/**/*.less\" \"client/**/*.less\" \"ee/**/*.less\"", @@ -107,7 +107,6 @@ "@types/ejson": "^2.2.2", "@types/express": "^4.17.21", "@types/express-rate-limit": "^5.1.3", - "@types/fibers": "^3.1.4", "@types/google-libphonenumber": "^7.4.30", "@types/gravatar": "^1.8.6", "@types/he": "^1.1.2", @@ -341,7 +340,6 @@ "express-rate-limit": "^5.5.1", "fastq": "^1.13.0", "fflate": "^0.7.4", - "fibers": "^5.0.3", "file-type": "^16.5.4", "filenamify": "^4.3.0", "filesize": "9.0.11", @@ -360,7 +358,7 @@ "imap": "^0.8.19", "ip-range-check": "^0.2.0", "is-svg": "^4.3.2", - "isolated-vm": "4.4.2", + "isolated-vm": "4.7.2", "jschardet": "^3.0.0", "jsdom": "^16.7.0", "jsrsasign": "^10.5.24", diff --git a/apps/meteor/packages/accounts-linkedin/LICENSE b/apps/meteor/packages/accounts-linkedin/LICENSE deleted file mode 100644 index 5f9ee7d18b2bf..0000000000000 --- a/apps/meteor/packages/accounts-linkedin/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/apps/meteor/packages/accounts-linkedin/README.md b/apps/meteor/packages/accounts-linkedin/README.md deleted file mode 100644 index fcbb24f50ad08..0000000000000 --- a/apps/meteor/packages/accounts-linkedin/README.md +++ /dev/null @@ -1,83 +0,0 @@ -meteor-accounts-linkedin -============================ - -A meteor package for LinkedIn's login service. - -### Important -BREAKING CHANGE LinkedIn -> Linkedin inside code from v5.0.0 - -v4.0.0 works with Meteor@1.6.1 & up - -From March 1, 2019 Linkedin will be using only V2 API [Docs](https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/migration-faq?context=linkedin/consumer/context) - -Install ------------ -``` -meteor add pauli:accounts-linkedin -``` - -Usage ------------ - -Core principles are the same as native Meteor external services login, all available params are in [meteor documentation](https://docs.meteor.com/api/accounts.html#Meteor-loginWith). So if you using `{{> loginButtons}}`, it will just appear in the selection. - -For custom usage, available functions in **Client**: - - login: ``Meteor.loginWithLinkedin([options], [callback])`` - credential request:``Meteor.requestCredential([options], [callback])`` - -If you are not using any other external login service, you need to `metoer add service-configuration`, otherwise just config it in server folder: - -```js -ServiceConfiguration.configurations.upsert( - { service: 'linkedin' }, - { - $set: { - clientId: "XXXXXXX", // Client ID - secret: "XXXXXXX" // Client Secret - } - } -); -``` - -As basic Linkedin permission now only allows `r_emailaddress` and `r_liteprofile`, this package by default will return this fields to `user.profile` - -```js - linkedinId, // Specific user id for your application only - firstName:{ - localized:{ - en_US:'Tina' - }, - preferredLocale:{ - country:'US', - language:'en' - } - }, - lastName:{ - localized:{ - en_US:'Belcher' - }, - preferredLocale:{ - country:'US', - language:'en' - } - }, - profilePicture: { - displayImage, // URN media handle - identifiersUrl, // Array of links to all available identifiers - }, - email, // First email from received emails during authentication - emails, // Array of all received emails during authentication -``` - -If you want during login process to ask for more fields, you need to pass requestPermissions to options. -To change popup options: -```js -popupOptions = { width: XXX, height: XXX } -``` - -More info [Linkedin API](https://docs.microsoft.com/en-us/linkedin/consumer/) - -License ------------ -[MIT](https://github.com/PauliBuccini/meteor-accounts-linkedin/blob/master/LICENSE) diff --git a/apps/meteor/packages/accounts-linkedin/linkedin.js b/apps/meteor/packages/accounts-linkedin/linkedin.js deleted file mode 100644 index c4430c2e79c9b..0000000000000 --- a/apps/meteor/packages/accounts-linkedin/linkedin.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Accounts } from 'meteor/accounts-base'; -import { Meteor } from 'meteor/meteor'; -import { Linkedin } from 'meteor/pauli:linkedin-oauth'; - -Accounts.oauth.registerService('linkedin'); - -if (Meteor.isClient) { - const loginWithLinkedin = function (options, callback) { - // support a callback without options - if (!callback && typeof options === 'function') { - callback = options; - options = null; - } - const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - void Linkedin.requestCredential(options, credentialRequestCompleteCallback).catch(credentialRequestCompleteCallback); - }; - Accounts.registerClientLoginFunction('linkedin', loginWithLinkedin); - - Meteor.loginWithLinkedin = (...args) => Accounts.applyLoginFunction('linkedin', args); -} else { - Accounts.addAutopublishFields({ - forLoggedInUser: ['services.linkedin'], - }); -} diff --git a/apps/meteor/packages/accounts-linkedin/notice.js b/apps/meteor/packages/accounts-linkedin/notice.js deleted file mode 100644 index 4150dd68c6792..0000000000000 --- a/apps/meteor/packages/accounts-linkedin/notice.js +++ /dev/null @@ -1,10 +0,0 @@ -if (Package['accounts-ui'] && !Package['service-configuration'] && !Package.hasOwnProperty('pauli:linkedin-config-ui')) { - console.warn( - "Note: You're using accounts-ui and pauli:accounts-linkedin,\n" + - "but didn't install the configuration UI for the Linkedin\n" + - 'OAuth. You can install it with:\n' + - '\n' + - ' meteor add pauli:linkedin-config-ui' + - '\n', - ); -} diff --git a/apps/meteor/packages/accounts-linkedin/package.js b/apps/meteor/packages/accounts-linkedin/package.js deleted file mode 100644 index a3078932a0b28..0000000000000 --- a/apps/meteor/packages/accounts-linkedin/package.js +++ /dev/null @@ -1,24 +0,0 @@ -Package.describe({ - name: 'pauli:accounts-linkedin', - summary: 'Login service for LinkedIn accounts, use with Meteor 1.6.1 & up', - version: '6.0.0', - git: 'https://github.com/PoBuchi/meteor-accounts-linkedin.git', -}); - -Package.onUse((api) => { - api.versionsFrom('2.5'); - api.use('ecmascript'); - api.use('accounts-base', ['client', 'server']); - api.imply('accounts-base', ['client', 'server']); - api.use('accounts-oauth', ['client', 'server']); - api.use('pauli:linkedin-oauth@6.0.0', ['client', 'server']); - api.imply('pauli:linkedin-oauth'); - - api.use('http', ['client', 'server']); - - // If users use accounts-ui but not linkedin-config-ui, give them a tip. - api.use(['accounts-ui', 'pauli:linkedin-config-ui@5.0.0'], ['client', 'server'], { weak: true }); - api.addFiles('notice.js'); - - api.addFiles('linkedin.js'); -}); diff --git a/apps/meteor/packages/autoupdate/autoupdate_client.js b/apps/meteor/packages/autoupdate/autoupdate_client.js index f9613935dbba5..dfd924c7f1cf1 100644 --- a/apps/meteor/packages/autoupdate/autoupdate_client.js +++ b/apps/meteor/packages/autoupdate/autoupdate_client.js @@ -42,7 +42,7 @@ export const Autoupdate = {}; const clientVersions = (Autoupdate._clientVersions = // Used by a self-test and hot-module-replacement new ClientVersions()); -Meteor.connection.registerStore('meteor_autoupdate_clientVersions', clientVersions.createStore()); +Meteor.connection.registerStoreClient('meteor_autoupdate_clientVersions', clientVersions.createStore()); Autoupdate.newClientAvailable = function () { return clientVersions.newClientAvailable(clientArch, ['versionRefreshable', 'versionNonRefreshable'], autoupdateVersions); diff --git a/apps/meteor/packages/autoupdate/autoupdate_server.js b/apps/meteor/packages/autoupdate/autoupdate_server.js index 5ffbf92e82a68..27a02c43428b2 100644 --- a/apps/meteor/packages/autoupdate/autoupdate_server.js +++ b/apps/meteor/packages/autoupdate/autoupdate_server.js @@ -26,7 +26,6 @@ // the document are the versions described above. import { ClientVersions } from './client_versions.js'; -var Future = Npm.require('fibers/future'); export const Autoupdate = (__meteor_runtime_config__.autoupdate = { // Map from client architectures (web.browser, web.browser.legacy, @@ -53,12 +52,12 @@ Autoupdate.autoupdateVersionRefreshable = null; Autoupdate.autoupdateVersionCordova = null; Autoupdate.appId = __meteor_runtime_config__.appId = process.env.APP_ID; -var syncQueue = new Meteor._SynchronousQueue(); +var syncQueue = new Meteor._AsynchronousQueue(); -function updateVersions(shouldReloadClientProgram) { +async function updateVersions(shouldReloadClientProgram) { // Step 1: load the current client program on the server if (shouldReloadClientProgram) { - WebAppInternals.reloadClientPrograms(); + await WebAppInternals.reloadClientPrograms(); } const { @@ -83,7 +82,7 @@ function updateVersions(shouldReloadClientProgram) { // Step 3: form the new client boilerplate which contains the updated // assets and __meteor_runtime_config__. if (shouldReloadClientProgram) { - WebAppInternals.generateBoilerplate(); + await WebAppInternals.generateBoilerplate(); } // Step 4: update the ClientVersions collection. @@ -129,8 +128,8 @@ Meteor.publish( { is_auto: true }, ); -Meteor.startup(function () { - updateVersions(false); +Meteor.startup(async function () { + await updateVersions(false); // Force any connected clients that are still looking for these older // document IDs to reload. @@ -141,36 +140,26 @@ Meteor.startup(function () { }); }); -var fut = new Future(); - -// We only want 'refresh' to trigger 'updateVersions' AFTER onListen, -// so we add a queued task that waits for onListen before 'refresh' can queue -// tasks. Note that the `onListening` callbacks do not fire until after -// Meteor.startup, so there is no concern that the 'updateVersions' calls from -// 'refresh' will overlap with the `updateVersions` call from Meteor.startup. - -syncQueue.queueTask(function () { - fut.wait(); -}); - -WebApp.onListening(function () { - fut.return(); -}); - function enqueueVersionsRefresh() { - syncQueue.queueTask(function () { - updateVersions(true); + syncQueue.queueTask(async function () { + await updateVersions(true); }); } -// Listen for messages pertaining to the client-refresh topic. -import { onMessage } from 'meteor/inter-process-messaging'; -onMessage('client-refresh', enqueueVersionsRefresh); +const setupListeners = () => { + // Listen for messages pertaining to the client-refresh topic. + import { onMessage } from 'meteor/inter-process-messaging'; + onMessage('client-refresh', enqueueVersionsRefresh); -// Another way to tell the process to refresh: send SIGHUP signal -process.on( - 'SIGHUP', - Meteor.bindEnvironment(function () { - enqueueVersionsRefresh(); - }, 'handling SIGHUP signal for refresh'), -); + // Another way to tell the process to refresh: send SIGHUP signal + process.on( + 'SIGHUP', + Meteor.bindEnvironment(function () { + enqueueVersionsRefresh(); + }, 'handling SIGHUP signal for refresh'), + ); +}; + +WebApp.onListening(function () { + Promise.resolve(setupListeners()); +}); diff --git a/apps/meteor/packages/autoupdate/package.js b/apps/meteor/packages/autoupdate/package.js index 52941121a9b68..2ac8f4947815f 100644 --- a/apps/meteor/packages/autoupdate/package.js +++ b/apps/meteor/packages/autoupdate/package.js @@ -1,6 +1,7 @@ Package.describe({ + name: 'autoupdate', summary: 'Update the client when new client code is available', - version: '1.8.0', + version: '2.0.0', }); Package.onUse(function (api) { diff --git a/apps/meteor/packages/flow-router/.npm/package/npm-shrinkwrap.json b/apps/meteor/packages/flow-router/.npm/package/npm-shrinkwrap.json index 8110d45c9f30c..51321bcb0a554 100644 --- a/apps/meteor/packages/flow-router/.npm/package/npm-shrinkwrap.json +++ b/apps/meteor/packages/flow-router/.npm/package/npm-shrinkwrap.json @@ -1,23 +1,20 @@ { - "lockfileVersion": 1, + "lockfileVersion": 4, "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, "page": { - "version": "https://github.com/kadirahq/page.js/archive/34ddf45ea8e4c37269ce3df456b44fc0efc595c6.tar.gz", - "integrity": "sha512-BGG5XDCSYGI4C/pGoYdGIIU1YpEYdCYN2HxwuKpYLgIDdT+tlqRs4IOQTJLTRnXKbt9r07Cucj0Y6fTGZcYizw==", - "dependencies": { - "path-to-regexp": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz", - "integrity": "sha512-DBw9IhWfevR2zCVwEZURTuQNseCvu/Q9f5ZgqMCK0Rh61bDa4uyjPAOy9b55yKiPT59zZn+7uYKxmWwsguInwg==", - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - } - } - } - } + "version": "1.6.4", + "resolved": "https://github.com/kadirahq/page.js/archive/34ddf45ea8e4c37269ce3df456b44fc0efc595c6.tar.gz", + "integrity": "sha512-BGG5XDCSYGI4C/pGoYdGIIU1YpEYdCYN2HxwuKpYLgIDdT+tlqRs4IOQTJLTRnXKbt9r07Cucj0Y6fTGZcYizw==" + }, + "path-to-regexp": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz", + "integrity": "sha512-DBw9IhWfevR2zCVwEZURTuQNseCvu/Q9f5ZgqMCK0Rh61bDa4uyjPAOy9b55yKiPT59zZn+7uYKxmWwsguInwg==" }, "qs": { "version": "5.2.0", diff --git a/apps/meteor/packages/linkedin-oauth/package.js b/apps/meteor/packages/linkedin-oauth/package.js index 314734066248a..ed7b37ae1cd45 100644 --- a/apps/meteor/packages/linkedin-oauth/package.js +++ b/apps/meteor/packages/linkedin-oauth/package.js @@ -7,7 +7,6 @@ Package.describe({ }); Package.onUse((api) => { - api.versionsFrom('2.5'); api.use('ecmascript'); api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); diff --git a/apps/meteor/packages/meteor-restivus/package.js b/apps/meteor/packages/meteor-restivus/package.js index e6e433d866b3c..1f2856270ea95 100644 --- a/apps/meteor/packages/meteor-restivus/package.js +++ b/apps/meteor/packages/meteor-restivus/package.js @@ -6,9 +6,6 @@ Package.describe({ }); Package.onUse(function (api) { - // Minimum Meteor version - api.versionsFrom('2.5'); - // Meteor dependencies api.use('check'); api.use('webapp'); diff --git a/apps/meteor/packages/meteor-user-presence/package.js b/apps/meteor/packages/meteor-user-presence/package.js index 96950704caa73..700c67cdf7374 100644 --- a/apps/meteor/packages/meteor-user-presence/package.js +++ b/apps/meteor/packages/meteor-user-presence/package.js @@ -6,8 +6,6 @@ Package.describe({ }); Package.onUse(function (api) { - api.versionsFrom('1.0.2.1'); - api.use('tracker'); api.use('check'); api.use('ecmascript'); diff --git a/apps/meteor/server/configuration/accounts_meld.js b/apps/meteor/server/configuration/accounts_meld.js index 64d8a4eeb109e..037150def37ee 100644 --- a/apps/meteor/server/configuration/accounts_meld.js +++ b/apps/meteor/server/configuration/accounts_meld.js @@ -4,8 +4,7 @@ import _ from 'underscore'; export async function configureAccounts() { const orig_updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService; - - const updateOrCreateUserFromExternalServiceAsync = async function (serviceName, serviceData = {}, ...args /* , options*/) { + Accounts.updateOrCreateUserFromExternalService = async function (serviceName, serviceData = {}, ...args /* , options*/) { const services = ['facebook', 'github', 'gitlab', 'google', 'meteor-developer', 'linkedin', 'twitter', 'apple']; if (services.includes(serviceName) === false && serviceData._OAuthCustom !== true) { @@ -46,9 +45,4 @@ export async function configureAccounts() { return orig_updateOrCreateUserFromExternalService.apply(this, [serviceName, serviceData, ...args]); }; - - Accounts.updateOrCreateUserFromExternalService = function (...args) { - // Depends on meteor support for Async - return Promise.await(updateOrCreateUserFromExternalServiceAsync.call(this, ...args)); - }; } diff --git a/apps/meteor/server/lib/cas/createNewUser.ts b/apps/meteor/server/lib/cas/createNewUser.ts index d04fa2d224973..ebb4143b53be6 100644 --- a/apps/meteor/server/lib/cas/createNewUser.ts +++ b/apps/meteor/server/lib/cas/createNewUser.ts @@ -36,7 +36,7 @@ export const createNewUser = async (username: string, { attributes, casVersion, // Create the user logger.debug(`User "${username}" does not exist yet, creating it`); - const userId = Accounts.insertUserDoc({}, newUser); + const userId = await Accounts.insertUserDoc({}, newUser); // Fetch and use it const user = await Users.findOneById(userId); diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index 893a970578b0c..294cdcd4feab8 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -1,35 +1,31 @@ import './models/startup'; - /** * ./settings uses top level await, in theory the settings creation * and the startup should be done in parallel */ import './settings'; -import '../app/lib/server/startup'; +import '../app/settings/server'; +import { startLicense } from '../ee/app/license/server/startup'; +import { registerEEBroker } from '../ee/server'; +import { startFederationService } from '../ee/server/startup/services'; import { configureLoginServices } from './configuration'; import { configureLogLevel } from './configureLogLevel'; -import { startLicense, registerEEBroker } from './ee'; import { registerServices } from './services/startup'; import { startup } from './startup'; + +import './routes'; +import '../app/lib/server/startup'; import './importPackages'; import './methods'; import './publications'; -import './routes'; - -await import('./lib/logger/startup'); - -await import('../lib/oauthRedirectUriServer'); +import './lib/logger/startup'; +import '../lib/oauthRedirectUriServer'; +import './lib/pushConfig'; +import './features/EmailInbox/index'; -await import('./lib/pushConfig'); +await Promise.all([configureLogLevel(), registerServices(), registerEEBroker(), startup()]); -await import('./stream/stdout'); -await import('./features/EmailInbox/index'); - -await configureLogLevel(); -await registerServices(); -await import('../app/settings/server'); -await configureLoginServices(); -await registerEEBroker(); -await startup(); await startLicense(); + +await Promise.all([configureLoginServices(), startFederationService()]); diff --git a/apps/meteor/server/methods/registerUser.ts b/apps/meteor/server/methods/registerUser.ts index 09e44c5e1b824..178061aafa605 100644 --- a/apps/meteor/server/methods/registerUser.ts +++ b/apps/meteor/server/methods/registerUser.ts @@ -33,7 +33,7 @@ Meteor.methods({ const AllowAnonymousWrite = settings.get('Accounts_AllowAnonymousWrite'); const manuallyApproveNewUsers = settings.get('Accounts_ManuallyApproveNewUsers'); if (AllowAnonymousRead === true && AllowAnonymousWrite === true && !formData.email) { - const userId = Accounts.insertUserDoc( + const userId = await Accounts.insertUserDoc( {}, { globalRoles: ['anonymous'], @@ -41,9 +41,9 @@ Meteor.methods({ }, ); - const stampedLoginToken = Accounts._generateStampedLoginToken(); + const stampedLoginToken = await Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedLoginToken); + await Accounts._insertLoginToken(userId, stampedLoginToken); return stampedLoginToken; } check( diff --git a/apps/meteor/server/services/meteor/service.ts b/apps/meteor/server/services/meteor/service.ts index 2ed97981c8429..075807ac31da0 100644 --- a/apps/meteor/server/services/meteor/service.ts +++ b/apps/meteor/server/services/meteor/service.ts @@ -37,7 +37,7 @@ if (disableOplog) { // Overrides the native observe changes to prevent database polling and stores the callbacks // for the users' tokens to re-implement the reactivity based on our database listeners const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); - MongoInternals.Connection.prototype._observeChanges = function ( + MongoInternals.Connection.prototype._observeChanges = async function ( { collectionName, selector, @@ -52,19 +52,18 @@ if (disableOplog) { }, _ordered: boolean, callbacks: Callbacks, - ): any { + ): Promise { // console.error('Connection.Collection.prototype._observeChanges', collectionName, selector, options); let cbs: Set<{ hashedToken: string; callbacks: Callbacks }>; let data: { hashedToken: string; callbacks: Callbacks }; if (callbacks?.added) { - const records = Promise.await( - mongo - .rawCollection(collectionName) - .find(selector, { - ...(options.projection || options.fields ? { projection: options.projection || options.fields } : {}), - }) - .toArray(), - ); + const records = await mongo + .rawCollection(collectionName) + .find(selector, { + ...(options.projection || options.fields ? { projection: options.projection || options.fields } : {}), + }) + .toArray(); + for (const { _id, ...fields } of records) { callbacks.added(String(_id), fields); } diff --git a/ee/apps/account-service/Dockerfile b/ee/apps/account-service/Dockerfile index a97290a433945..136e0134ca214 100644 --- a/ee/apps/account-service/Dockerfile +++ b/ee/apps/account-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -69,7 +69,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json index 9bc0547170aa5..b7fd8b3bc1e34 100644 --- a/ee/apps/account-service/package.json +++ b/ee/apps/account-service/package.json @@ -29,7 +29,6 @@ "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/apps/authorization-service/Dockerfile b/ee/apps/authorization-service/Dockerfile index 9ddeadd380fef..10dad15760b1c 100644 --- a/ee/apps/authorization-service/Dockerfile +++ b/ee/apps/authorization-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -66,7 +66,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json index 6b94f219790b1..e17a7aca28437 100644 --- a/ee/apps/authorization-service/package.json +++ b/ee/apps/authorization-service/package.json @@ -27,7 +27,6 @@ "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/apps/ddp-streamer/Dockerfile b/ee/apps/ddp-streamer/Dockerfile index dea2bc3790a1a..7a1ee05c05900 100644 --- a/ee/apps/ddp-streamer/Dockerfile +++ b/ee/apps/ddp-streamer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -72,7 +72,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json index 1c70ea4158590..0220828561d2b 100644 --- a/ee/apps/ddp-streamer/package.json +++ b/ee/apps/ddp-streamer/package.json @@ -29,7 +29,6 @@ "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "jaeger-client": "^3.19.0", "mem": "^8.1.1", diff --git a/ee/apps/omnichannel-transcript/Dockerfile b/ee/apps/omnichannel-transcript/Dockerfile index 0f18534e14539..6ec2a636124b6 100644 --- a/ee/apps/omnichannel-transcript/Dockerfile +++ b/ee/apps/omnichannel-transcript/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -75,7 +75,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index 9f22cee1dc903..7dc5461f7e4db 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -31,7 +31,6 @@ "emoji-toolkit": "^7.0.1", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/apps/presence-service/Dockerfile b/ee/apps/presence-service/Dockerfile index 78c6a98f809aa..09bf36b4c5654 100644 --- a/ee/apps/presence-service/Dockerfile +++ b/ee/apps/presence-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -69,7 +69,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json index 46e0b33cb0b87..7480963d87aca 100644 --- a/ee/apps/presence-service/package.json +++ b/ee/apps/presence-service/package.json @@ -27,7 +27,6 @@ "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/apps/queue-worker/Dockerfile b/ee/apps/queue-worker/Dockerfile index 0f18534e14539..6ec2a636124b6 100644 --- a/ee/apps/queue-worker/Dockerfile +++ b/ee/apps/queue-worker/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -75,7 +75,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index f6a51d73f5b86..821cd9664c46c 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -29,7 +29,6 @@ "emoji-toolkit": "^7.0.1", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/apps/stream-hub-service/Dockerfile b/ee/apps/stream-hub-service/Dockerfile index 9ddeadd380fef..10dad15760b1c 100644 --- a/ee/apps/stream-hub-service/Dockerfile +++ b/ee/apps/stream-hub-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.21.3-alpine +FROM node:20.15.1-alpine3.20 ARG SERVICE @@ -66,7 +66,7 @@ ENV NODE_ENV=production \ WORKDIR /app/ee/apps/${SERVICE} RUN apk update && \ - apk --no-cache --virtual build-dependencies add g++ python3 make && \ + apk --no-cache --virtual build-dependencies add g++ python3 make py3-setuptools && \ yarn workspaces focus --production && \ rm -rf /var/cache/apk/* && \ apk del build-dependencies diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json index 970c965d85514..855fbf51346b1 100644 --- a/ee/apps/stream-hub-service/package.json +++ b/ee/apps/stream-hub-service/package.json @@ -27,7 +27,6 @@ "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "gc-stats": "^1.4.1", "mem": "^8.1.1", "moleculer": "^0.14.34", diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json index 257298431f79b..96e7462b0dbd0 100644 --- a/ee/packages/omnichannel-services/package.json +++ b/ee/packages/omnichannel-services/package.json @@ -26,7 +26,6 @@ "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", "eventemitter3": "^4.0.7", - "fibers": "^5.0.3", "mem": "^8.1.1", "moment-timezone": "^0.5.46", "mongo-message-queue": "^1.0.0", diff --git a/package.json b/package.json index 8079a03c805e8..fd87a3e2b5ad3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "homepage": "https://github.com/RocketChat/Rocket.Chat#readme", "engines": { "yarn": "3.5.0", - "node": "14.21.3", + "node": "20.15.1", "npm": "Use yarn instead" }, "packageManager": "yarn@3.5.0", @@ -54,7 +54,7 @@ ] }, "volta": { - "node": "14.21.3", + "node": "20.15.1", "yarn": "1.22.18" }, "resolutions": { diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 69ad6e8b0dd39..8fd497e8c98d9 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -38,8 +38,6 @@ "@rocket.chat/message-parser": "workspace:^", "@rocket.chat/models": "workspace:^", "@rocket.chat/rest-typings": "workspace:^", - "@rocket.chat/ui-kit": "workspace:~", - "@types/fibers": "^3.1.4", - "fibers": "^5.0.3" + "@rocket.chat/ui-kit": "workspace:~" } } diff --git a/packages/core-services/src/lib/ContextStore.ts b/packages/core-services/src/lib/ContextStore.ts index a886e537df395..f04cb2f27561d 100644 --- a/packages/core-services/src/lib/ContextStore.ts +++ b/packages/core-services/src/lib/ContextStore.ts @@ -1,7 +1,5 @@ import { AsyncLocalStorage } from 'async_hooks'; -import Fiber from 'fibers'; - interface IContextStore { getStore(): T | undefined; run(store: T, callback: (...args: any) => void, ...args: any): void; @@ -9,23 +7,3 @@ interface IContextStore { // This is the default implementation of the context store but there is a bug on Meteor 2.5 that prevents us from using it export class AsyncContextStore extends AsyncLocalStorage implements IContextStore {} - -export class FibersContextStore implements IContextStore { - getStore(): T | undefined { - return Fiber.current as unknown as T; - } - - run(store: T, callback: (...args: any) => void, ...args: any): void { - // eslint-disable-next-line new-cap - return Fiber((...rest: any) => { - const fiber = Fiber.current as Record; - for (const key in store) { - if (store.hasOwnProperty(key)) { - fiber[key] = store[key]; - } - } - - Fiber.yield(callback(...rest)); - }).run(...args); - } -} diff --git a/packages/core-services/src/lib/asyncLocalStorage.ts b/packages/core-services/src/lib/asyncLocalStorage.ts index 04352e99f95b4..cec53d979e971 100644 --- a/packages/core-services/src/lib/asyncLocalStorage.ts +++ b/packages/core-services/src/lib/asyncLocalStorage.ts @@ -1,5 +1,4 @@ import type { IServiceContext } from '../types/ServiceClass'; -import { FibersContextStore } from './ContextStore'; +import { AsyncContextStore } from './ContextStore'; -// TODO Evalute again using AsyncContextStore instead of FibersContextStore in a future Meteor release (after 2.5) -export const asyncLocalStorage = new FibersContextStore(); +export const asyncLocalStorage = new AsyncContextStore(); diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 6602d319b1f0b..27b33c71c2582 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -39,7 +39,7 @@ "docs": "cross-env NODE_ENV=production build-storybook -o ../../static/fuselage-ui-kit", "storybook": "cross-env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' start-storybook -p 6006 --no-version-updates", "build-storybook": "cross-env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' NODE_ENV=production build-storybook", - "build-preview": "yarn build-storybook", + "build-preview": "NODE_OPTIONS=--openssl-legacy-provider yarn build-storybook", ".:build-preview-move": "mkdir -p ../../.preview/ && cp -r ./storybook-static ../../.preview/fuselage-ui-kit" }, "peerDependencies": { diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index bc9f37c765ee6..b1994649f72dd 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -9,13 +9,13 @@ ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.build.json", - "build-storybook": "build-storybook", - "build-preview": "build-storybook --quiet", + "build-storybook": "NODE_OPTIONS=--openssl-legacy-provider build-storybook", + "build-preview": "NODE_OPTIONS=--openssl-legacy-provider build-storybook --quiet", ".:build-preview-move": "mkdir -p ../../.preview && cp -r ./storybook-static ../../.preview/gazzodown", "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput", "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", - "storybook": "start-storybook -p 6006", + "storybook": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006", "test": "jest", "testunit": "jest", "typecheck": "tsc --noEmit" diff --git a/packages/i18n/src/locales/ca.i18n.json b/packages/i18n/src/locales/ca.i18n.json index 6544ddff278ba..7911179957f31 100644 --- a/packages/i18n/src/locales/ca.i18n.json +++ b/packages/i18n/src/locales/ca.i18n.json @@ -708,6 +708,7 @@ "Call_Center": "Centre de trucades", "Calls_in_queue_zero": "La cua és buida", "Calls_in_queue_one": "{{count}} Trucada a la cua", + "Calls_in_queue_many": "{{count}} Trucades a la cua", "Calls_in_queue_other": "{{count}} Trucades a la cua", "Call_declined": "Trucada rebutjada!", "Call_Information": "Informació de la trucada", @@ -2822,6 +2823,7 @@ "Message_Code_highlight": "Llista d'idiomes de ressaltat de codi", "Message_Code_highlight_Description": "Llista d'idiomes separats per comes (tots els idiomes admesos en [highlight.js](https://github.com/highlightjs/highlight.js/tree/11.6.0#supported-languages)) que s'utilitzarà per a ressaltar blocs de codi", "message_counter_one": "missatge {{count}}", + "message_counter_many": "{{count}} missatges", "message_counter_other": "{{count}} missatges", "Message_DateFormat": "Format de data", "Message_DateFormat_Description": "Veure: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", @@ -2915,6 +2917,7 @@ "meteor_status_failed": "La connexió del servidor ha fallat", "meteor_status_offline": "Mode fora de línia", "meteor_status_reconnect_in_one": "intentant de nou en un segon ...", + "meteor_status_reconnect_in_many": "provant de nou d'aquí a {{count}} segons ...", "meteor_status_reconnect_in_other": "provant de nou d'aquí a {{count}} segons ...", "meteor_status_try_now_offline": "Connectar de nou", "meteor_status_try_now_waiting": "Prova-ho ara", diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index 131833b0ce69a..3737346e5cc8f 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -1,52 +1,54 @@ { - "name": "@rocket.chat/ui-composer", - "version": "0.3.0", - "private": true, - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "files": [ - "/dist" - ], - "scripts": { - "lint": "eslint --ext .js,.jsx,.ts,.tsx .", - "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", - "build": "rm -rf dist && tsc -p tsconfig.build.json", - "typecheck": "tsc --noEmit", - "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput", - "storybook": "start-storybook -p 6006" - }, - "devDependencies": { - "@babel/core": "~7.22.20", - "@react-aria/toolbar": "^3.0.0-beta.1", - "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "^0.59.1", - "@rocket.chat/icons": "~0.38.0", - "@storybook/addon-actions": "~6.5.16", - "@storybook/addon-docs": "~6.5.16", - "@storybook/addon-essentials": "~6.5.16", - "@storybook/builder-webpack4": "~6.5.16", - "@storybook/manager-webpack4": "~6.5.16", - "@storybook/react": "~6.5.16", - "@storybook/testing-library": "~0.0.13", - "@types/react": "~17.0.80", - "@types/react-dom": "~17.0.25", - "eslint": "~8.45.0", - "eslint-plugin-react": "~7.32.2", - "eslint-plugin-react-hooks": "~4.6.2", - "eslint-plugin-storybook": "~0.6.15", - "react": "~17.0.2", - "react-docgen-typescript-plugin": "~1.0.8", - "react-dom": "~17.0.2", - "typescript": "~5.5.4" - }, - "peerDependencies": { - "@react-aria/toolbar": "*", - "@rocket.chat/fuselage": "*", - "@rocket.chat/icons": "*", - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, - "volta": { - "extends": "../../package.json" - } + "name": "@rocket.chat/ui-composer", + "version": "0.3.0", + "private": true, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "/dist" + ], + "scripts": { + "lint": "eslint --ext .js,.jsx,.ts,.tsx .", + "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", + "build": "rm -rf dist && tsc -p tsconfig.build.json", + "typecheck": "tsc --noEmit", + "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput", + "storybook": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006", + "build-preview": "NODE_OPTIONS=--openssl-legacy-provider build-storybook", + ".:build-preview-move": "mkdir -p ../../.preview/ && cp -r ./storybook-static ../../.preview/ui-composer" + }, + "devDependencies": { + "@babel/core": "~7.22.20", + "@react-aria/toolbar": "^3.0.0-beta.1", + "@rocket.chat/eslint-config": "workspace:^", + "@rocket.chat/fuselage": "^0.59.1", + "@rocket.chat/icons": "~0.38.0", + "@storybook/addon-actions": "~6.5.16", + "@storybook/addon-docs": "~6.5.16", + "@storybook/addon-essentials": "~6.5.16", + "@storybook/builder-webpack4": "~6.5.16", + "@storybook/manager-webpack4": "~6.5.16", + "@storybook/react": "~6.5.16", + "@storybook/testing-library": "~0.0.13", + "@types/react": "~17.0.80", + "@types/react-dom": "~17.0.25", + "eslint": "~8.45.0", + "eslint-plugin-react": "~7.32.2", + "eslint-plugin-react-hooks": "~4.6.2", + "eslint-plugin-storybook": "~0.6.15", + "react": "~17.0.2", + "react-docgen-typescript-plugin": "~1.0.8", + "react-dom": "~17.0.2", + "typescript": "~5.5.4" + }, + "peerDependencies": { + "@react-aria/toolbar": "*", + "@rocket.chat/fuselage": "*", + "@rocket.chat/icons": "*", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "volta": { + "extends": "../../package.json" + } } diff --git a/yarn.lock b/yarn.lock index 707d8d18f4c5f..18b7b6e189d04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8707,7 +8707,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -8842,7 +8841,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -8897,11 +8895,9 @@ __metadata: "@rocket.chat/models": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" "@rocket.chat/ui-kit": "workspace:~" - "@types/fibers": ^3.1.4 "@types/jest": ~29.5.13 babel-jest: ^29.5.0 eslint: ~8.45.0 - fibers: ^5.0.3 jest: ~29.7.0 mongodb: ^4.17.2 prettier: ~2.8.8 @@ -9012,7 +9008,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 jaeger-client: ^3.19.0 mem: ^8.1.1 @@ -9744,7 +9739,6 @@ __metadata: "@types/ejson": ^2.2.2 "@types/express": ^4.17.21 "@types/express-rate-limit": ^5.1.3 - "@types/fibers": ^3.1.4 "@types/google-libphonenumber": ^7.4.30 "@types/gravatar": ^1.8.6 "@types/he": ^1.1.2 @@ -9870,7 +9864,6 @@ __metadata: fast-glob: ^3.2.12 fastq: ^1.13.0 fflate: ^0.7.4 - fibers: ^5.0.3 file-type: ^16.5.4 filenamify: ^4.3.0 filesize: 9.0.11 @@ -9890,7 +9883,7 @@ __metadata: imap: ^0.8.19 ip-range-check: ^0.2.0 is-svg: ^4.3.2 - isolated-vm: 4.4.2 + isolated-vm: 4.7.2 jest: ~29.7.0 jschardet: ^3.0.0 jsdom: ^16.7.0 @@ -10108,7 +10101,6 @@ __metadata: emoji-toolkit: ^7.0.1 eslint: ~8.45.0 eventemitter3: ^4.0.7 - fibers: ^5.0.3 jest: ~29.7.0 mem: ^8.1.1 moment-timezone: ^0.5.46 @@ -10143,7 +10135,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -10279,7 +10270,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -10345,7 +10335,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -10504,7 +10493,6 @@ __metadata: eslint: ~8.45.0 event-loop-stats: ^1.4.1 eventemitter3: ^4.0.7 - fibers: ^5.0.3 gc-stats: ^1.4.1 mem: ^8.1.1 moleculer: ^0.14.34 @@ -22058,7 +22046,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^1.0.2, detect-libc@npm:^1.0.3": +"detect-libc@npm:^1.0.2": version: 1.0.3 resolution: "detect-libc@npm:1.0.3" bin: @@ -24605,15 +24593,6 @@ __metadata: languageName: node linkType: hard -"fibers@npm:^5.0.3": - version: 5.0.3 - resolution: "fibers@npm:5.0.3" - dependencies: - detect-libc: ^1.0.3 - checksum: d66c5e18a911aab3480b846e1c837e5c7cfacb27a2a5fe512919865eaecef33cdd4abc14d777191a6a93473dc52356d48549c91a2a7b8b3450544c44104b23f3 - languageName: node - linkType: hard - "figgy-pudding@npm:^3.5.1": version: 3.5.2 resolution: "figgy-pudding@npm:3.5.2" @@ -28883,12 +28862,13 @@ __metadata: languageName: node linkType: hard -"isolated-vm@npm:4.4.2": - version: 4.4.2 - resolution: "isolated-vm@npm:4.4.2" +"isolated-vm@npm:4.7.2": + version: 4.7.2 + resolution: "isolated-vm@npm:4.7.2" dependencies: node-gyp: latest - checksum: 86d12d96f90ceef74a3fc096439c71b0c115235ae3053d600eb8f7c678443d9ca3c8a2805dcd7f97463d11eb7d2e667868946b90e377a3e6d50fdd4085506fbc + prebuild-install: ^7.1.1 + checksum: 16f43f6413623dc7009a8bb9fa567fb30ffc151e21e9a7ae616f25626e750ba823527fb24e2e17408943c6bbbcc7235db89f41262d43a8d8155ad99e888b0760 languageName: node linkType: hard @@ -38371,7 +38351,6 @@ __metadata: ejson: ^2.2.3 eventemitter3: ^4.0.7 express: ^4.17.3 - fibers: ^5.0.3 jaeger-client: ^3.19.0 mem: ^8.1.1 moleculer: ^0.14.34 From f76d36fcaf725997784e8c85ff4ac8f733e25787 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 10 Oct 2024 13:23:09 -0300 Subject: [PATCH 72/85] chore!: Remove unused `UI_Click_Direct_Message` setting (#33183) --- apps/meteor/server/settings/layout.ts | 4 ---- apps/meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v309.ts | 11 +++++++++++ packages/i18n/src/locales/af.i18n.json | 2 -- packages/i18n/src/locales/ar.i18n.json | 2 -- packages/i18n/src/locales/az.i18n.json | 2 -- packages/i18n/src/locales/be-BY.i18n.json | 2 -- packages/i18n/src/locales/bg.i18n.json | 2 -- packages/i18n/src/locales/bs.i18n.json | 2 -- packages/i18n/src/locales/ca.i18n.json | 2 -- packages/i18n/src/locales/cs.i18n.json | 2 -- packages/i18n/src/locales/cy.i18n.json | 2 -- packages/i18n/src/locales/da.i18n.json | 2 -- packages/i18n/src/locales/de-AT.i18n.json | 2 -- packages/i18n/src/locales/de-IN.i18n.json | 4 +--- packages/i18n/src/locales/de.i18n.json | 2 -- packages/i18n/src/locales/el.i18n.json | 2 -- packages/i18n/src/locales/en.i18n.json | 2 -- packages/i18n/src/locales/eo.i18n.json | 2 -- packages/i18n/src/locales/es.i18n.json | 2 -- packages/i18n/src/locales/fa.i18n.json | 2 -- packages/i18n/src/locales/fi.i18n.json | 2 -- packages/i18n/src/locales/fr.i18n.json | 2 -- packages/i18n/src/locales/he.i18n.json | 1 - packages/i18n/src/locales/hi-IN.i18n.json | 2 -- packages/i18n/src/locales/hr.i18n.json | 2 -- packages/i18n/src/locales/hu.i18n.json | 2 -- packages/i18n/src/locales/id.i18n.json | 2 -- packages/i18n/src/locales/it.i18n.json | 2 -- packages/i18n/src/locales/ja.i18n.json | 2 -- packages/i18n/src/locales/ka-GE.i18n.json | 2 -- packages/i18n/src/locales/km.i18n.json | 2 -- packages/i18n/src/locales/ko.i18n.json | 2 -- packages/i18n/src/locales/ku.i18n.json | 2 -- packages/i18n/src/locales/lo.i18n.json | 2 -- packages/i18n/src/locales/lt.i18n.json | 2 -- packages/i18n/src/locales/lv.i18n.json | 2 -- packages/i18n/src/locales/mn.i18n.json | 2 -- packages/i18n/src/locales/ms-MY.i18n.json | 2 -- packages/i18n/src/locales/nl.i18n.json | 2 -- packages/i18n/src/locales/nn.i18n.json | 2 -- packages/i18n/src/locales/no.i18n.json | 2 -- packages/i18n/src/locales/pl.i18n.json | 2 -- packages/i18n/src/locales/pt-BR.i18n.json | 2 -- packages/i18n/src/locales/pt.i18n.json | 2 -- packages/i18n/src/locales/ro.i18n.json | 2 -- packages/i18n/src/locales/ru.i18n.json | 2 -- packages/i18n/src/locales/se.i18n.json | 2 -- packages/i18n/src/locales/sk-SK.i18n.json | 2 -- packages/i18n/src/locales/sl-SI.i18n.json | 2 -- packages/i18n/src/locales/sq.i18n.json | 2 -- packages/i18n/src/locales/sr.i18n.json | 2 -- packages/i18n/src/locales/sv.i18n.json | 2 -- packages/i18n/src/locales/ta-IN.i18n.json | 2 -- packages/i18n/src/locales/th-TH.i18n.json | 2 -- packages/i18n/src/locales/tr.i18n.json | 2 -- packages/i18n/src/locales/uk.i18n.json | 2 -- packages/i18n/src/locales/vi-VN.i18n.json | 2 -- packages/i18n/src/locales/zh-HK.i18n.json | 2 -- packages/i18n/src/locales/zh-TW.i18n.json | 2 -- packages/i18n/src/locales/zh.i18n.json | 2 -- 61 files changed, 13 insertions(+), 120 deletions(-) create mode 100644 apps/meteor/server/startup/migrations/v309.ts diff --git a/apps/meteor/server/settings/layout.ts b/apps/meteor/server/settings/layout.ts index 58884e733b7ae..d89c5559c1d68 100644 --- a/apps/meteor/server/settings/layout.ts +++ b/apps/meteor/server/settings/layout.ts @@ -173,10 +173,6 @@ export const createLayoutSettings = () => type: 'boolean', public: true, }); - await this.add('UI_Click_Direct_Message', false, { - type: 'boolean', - public: true, - }); await this.add('Number_of_users_autocomplete_suggestions', 5, { type: 'int', diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 71ef63582e5b2..bd17b46189347 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -41,5 +41,6 @@ import './v305'; import './v306'; import './v307'; import './v308'; +import './v309'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v309.ts b/apps/meteor/server/startup/migrations/v309.ts new file mode 100644 index 0000000000000..4df0e0a1c4772 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v309.ts @@ -0,0 +1,11 @@ +import { Settings } from '@rocket.chat/models'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 309, + name: 'Remove unused UI_Click_Direct_Message setting', + async up() { + await Settings.removeById('UI_Click_Direct_Message'); + }, +}); diff --git a/packages/i18n/src/locales/af.i18n.json b/packages/i18n/src/locales/af.i18n.json index b71bbdd474af4..484d1ff2cc58e 100644 --- a/packages/i18n/src/locales/af.i18n.json +++ b/packages/i18n/src/locales/af.i18n.json @@ -2474,8 +2474,6 @@ "Type_your_password": "Tik jou wagwoord", "Type_your_username": "Tik jou gebruikersnaam", "UI_Allow_room_names_with_special_chars": "Laat spesiale karakters in kamer name toe", - "UI_Click_Direct_Message": "Kliek om regstreekse boodskap te skep", - "UI_Click_Direct_Message_Description": "Slaan die oopmaakprofielflik oor, gaan reguit na gesprek", "UI_DisplayRoles": "Wys Rolle", "UI_Group_Channels_By_Type": "Groepskanale per tipe", "UI_Merge_Channels_Groups": "Voeg privaat groepe saam met kanale saam", diff --git a/packages/i18n/src/locales/ar.i18n.json b/packages/i18n/src/locales/ar.i18n.json index d70af13aa54f8..5b05ad8e104c4 100644 --- a/packages/i18n/src/locales/ar.i18n.json +++ b/packages/i18n/src/locales/ar.i18n.json @@ -4323,8 +4323,6 @@ "Type_your_password": "اكتب كلمة المرور الخاصة بك", "Type_your_username": "اكتب اسم المستخدم الخاص بك", "UI_Allow_room_names_with_special_chars": "السماح بالأحرف الخاصة في أسماء Room", - "UI_Click_Direct_Message": "انقر لإنشاء رسالة مباشرة", - "UI_Click_Direct_Message_Description": "تخطي فتح علامة تبويب الملف الشخصي، وبدلاً من ذلك الانتقال مباشرة إلى المحادثة", "UI_DisplayRoles": "عرض الأدوار", "UI_Group_Channels_By_Type": "تجميع القنوات حسب النوع", "UI_Merge_Channels_Groups": "دمج المجموعات الخاصة مع Channels", diff --git a/packages/i18n/src/locales/az.i18n.json b/packages/i18n/src/locales/az.i18n.json index d4ac33cb2279a..c956a418e5ff5 100644 --- a/packages/i18n/src/locales/az.i18n.json +++ b/packages/i18n/src/locales/az.i18n.json @@ -2474,8 +2474,6 @@ "Type_your_password": "Şifrənizi yazın", "Type_your_username": "İstifadəçi adınızı yazın", "UI_Allow_room_names_with_special_chars": "Otaq adlarında xüsusi simvollara icazə verin", - "UI_Click_Direct_Message": "Birbaşa Mesaj yaratmaq üçün basın", - "UI_Click_Direct_Message_Description": "Açıq profil nişanını atın, əvəzinə söhbətə düz gedin", "UI_DisplayRoles": "Rolu göstərin", "UI_Group_Channels_By_Type": "Qrup növlərinə görə qruplar", "UI_Merge_Channels_Groups": "Şəxsi qrupları Kanallar ilə birləşdirin", diff --git a/packages/i18n/src/locales/be-BY.i18n.json b/packages/i18n/src/locales/be-BY.i18n.json index 9a6956cbf54e7..abb3ddecb80bb 100644 --- a/packages/i18n/src/locales/be-BY.i18n.json +++ b/packages/i18n/src/locales/be-BY.i18n.json @@ -2492,8 +2492,6 @@ "Type_your_password": "Калі ласка, увядзіце пароль", "Type_your_username": "Калі ласка, увядзіце імя карыстальніка", "UI_Allow_room_names_with_special_chars": "Дазволіць спецыяльныя сімвалы ў нумары імёнаў", - "UI_Click_Direct_Message": "Націсніце, каб стварыць прамое паведамленне", - "UI_Click_Direct_Message_Description": "Прапусціць адкрыццё ўкладкі профілю, а адразу перайсці да размовы", "UI_DisplayRoles": "паказаць Ролі", "UI_Group_Channels_By_Type": "Група каналаў па тыпу", "UI_Merge_Channels_Groups": "Аб'яднанне прыватных груп з каналамі", diff --git a/packages/i18n/src/locales/bg.i18n.json b/packages/i18n/src/locales/bg.i18n.json index 8eb016f16eb12..e0c1e62c6f05f 100644 --- a/packages/i18n/src/locales/bg.i18n.json +++ b/packages/i18n/src/locales/bg.i18n.json @@ -2471,8 +2471,6 @@ "Type_your_password": "Въведете паролата си", "Type_your_username": "Въведете потребителското си име", "UI_Allow_room_names_with_special_chars": "Позволете специални символи в имената на стаите", - "UI_Click_Direct_Message": "Кликнете, за да създадете директно съобщение", - "UI_Click_Direct_Message_Description": "Пропуснете раздела за профила на отваряне, вместо това отидете направо в разговор", "UI_DisplayRoles": "Показване на роли", "UI_Group_Channels_By_Type": "Групи канали по тип", "UI_Merge_Channels_Groups": "Обединяване на частни групи с канали", diff --git a/packages/i18n/src/locales/bs.i18n.json b/packages/i18n/src/locales/bs.i18n.json index 3be570d0eb9d9..03c230496a438 100644 --- a/packages/i18n/src/locales/bs.i18n.json +++ b/packages/i18n/src/locales/bs.i18n.json @@ -2468,8 +2468,6 @@ "Type_your_password": "Upišite svoju lozinku", "Type_your_username": "Upišite svoje korisničko ime", "UI_Allow_room_names_with_special_chars": "Omogući posebne znakove u imenima soba", - "UI_Click_Direct_Message": "Kliknite da biste stvorili izravnu poruku", - "UI_Click_Direct_Message_Description": "Preskoči karticu profila otvaranja, umjesto toga idite ravno u razgovor", "UI_DisplayRoles": "Prikaži uloge", "UI_Group_Channels_By_Type": "Grupni kanali prema vrsti", "UI_Merge_Channels_Groups": "Spoji privatne grupe s kanalima", diff --git a/packages/i18n/src/locales/ca.i18n.json b/packages/i18n/src/locales/ca.i18n.json index 7911179957f31..1ea9f18334374 100644 --- a/packages/i18n/src/locales/ca.i18n.json +++ b/packages/i18n/src/locales/ca.i18n.json @@ -4241,8 +4241,6 @@ "Type_your_password": "Escriviu la vostra contrasenya", "Type_your_username": "Escriviu el vostre nom d'usuari", "UI_Allow_room_names_with_special_chars": "Permetre caràcters especials en noms de sales", - "UI_Click_Direct_Message": "Feu clic per crear un missatge directe", - "UI_Click_Direct_Message_Description": "Evita obrir la pestanya del perfil, vés directe a la conversa", "UI_DisplayRoles": "Mostra rols", "UI_Group_Channels_By_Type": "Agrupar canals per tipus", "UI_Merge_Channels_Groups": "Uneix grups privats amb Channels", diff --git a/packages/i18n/src/locales/cs.i18n.json b/packages/i18n/src/locales/cs.i18n.json index d1fdb8411264f..8cb9cc35128cd 100644 --- a/packages/i18n/src/locales/cs.i18n.json +++ b/packages/i18n/src/locales/cs.i18n.json @@ -3586,8 +3586,6 @@ "Type_your_password": "Zadejte své heslo", "Type_your_username": "Zadejte své uživatelské jméno", "UI_Allow_room_names_with_special_chars": "Povolit speciální znaky v názvech místností", - "UI_Click_Direct_Message": "Kliknutím vytvořit přímou zprávu", - "UI_Click_Direct_Message_Description": "Přeskočit záložku profilu a přejít přímo do konverzace", "UI_DisplayRoles": "Zobrazit Role", "UI_Group_Channels_By_Type": "Skupinové místnosti podle typu", "UI_Merge_Channels_Groups": "Sloučit privátní skupiny s místnostmi", diff --git a/packages/i18n/src/locales/cy.i18n.json b/packages/i18n/src/locales/cy.i18n.json index f39c7f2b9935a..b07027243130b 100644 --- a/packages/i18n/src/locales/cy.i18n.json +++ b/packages/i18n/src/locales/cy.i18n.json @@ -2469,8 +2469,6 @@ "Type_your_password": "Teipiwch eich cyfrinair", "Type_your_username": "Teipiwch eich enw defnyddiwr", "UI_Allow_room_names_with_special_chars": "Caniatáu Cymeriadau Arbennig mewn Enwau Ystafelloedd", - "UI_Click_Direct_Message": "Cliciwch i Creu Neges Uniongyrchol", - "UI_Click_Direct_Message_Description": "Neidio tab proffil agor, yn hytrach ewch yn syth i sgwrs", "UI_DisplayRoles": "Rolau Arddangos", "UI_Group_Channels_By_Type": "Sianelau grŵp yn ôl math", "UI_Merge_Channels_Groups": "Cyfuno Grwpiau Preifat â Sianeli", diff --git a/packages/i18n/src/locales/da.i18n.json b/packages/i18n/src/locales/da.i18n.json index f6dc28df9f84b..48b5fdf0f69e5 100644 --- a/packages/i18n/src/locales/da.i18n.json +++ b/packages/i18n/src/locales/da.i18n.json @@ -3694,8 +3694,6 @@ "Type_your_password": "Indtast dit kodeord", "Type_your_username": "Indtast dit brugernavn", "UI_Allow_room_names_with_special_chars": "Tillad særlige tegn i rumnavne", - "UI_Click_Direct_Message": "Klik for at oprette direkte besked", - "UI_Click_Direct_Message_Description": "Spring over åbningsprofil fanen, i stedet gå direkte til samtale", "UI_DisplayRoles": "Vis roller", "UI_Group_Channels_By_Type": "Gruppekanaler efter type", "UI_Merge_Channels_Groups": "Flett private grupper med kanaler", diff --git a/packages/i18n/src/locales/de-AT.i18n.json b/packages/i18n/src/locales/de-AT.i18n.json index aa4bea9350666..e70a6ade27d67 100644 --- a/packages/i18n/src/locales/de-AT.i18n.json +++ b/packages/i18n/src/locales/de-AT.i18n.json @@ -2477,8 +2477,6 @@ "Type_your_password": "Geben Sie Ihr Passwort ein", "Type_your_username": "Gib deinen Benutzernamen ein", "UI_Allow_room_names_with_special_chars": "Erlaube Sonderzeichen in Raumnamen", - "UI_Click_Direct_Message": "Klicken Sie auf Direktnachricht erstellen", - "UI_Click_Direct_Message_Description": "Überspringe die Registerkarte \"Öffnen\", gehe stattdessen direkt zur Konversation", "UI_DisplayRoles": "Rollen anzeigen", "UI_Group_Channels_By_Type": "Gruppieren Sie Kanäle nach Typ", "UI_Merge_Channels_Groups": "Private Gruppen mit öffentlichen Kanälen gemeinsam anzeigen", diff --git a/packages/i18n/src/locales/de-IN.i18n.json b/packages/i18n/src/locales/de-IN.i18n.json index c58e77105882f..3ab7a222d068c 100644 --- a/packages/i18n/src/locales/de-IN.i18n.json +++ b/packages/i18n/src/locales/de-IN.i18n.json @@ -2796,8 +2796,6 @@ "Type_your_password": "Gib Dein Passwort ein", "Type_your_username": "Gib Deinen Benutzernamen ein", "UI_Allow_room_names_with_special_chars": "Sonderzeichen im Raumnamen erlauben", - "UI_Click_Direct_Message": "Anklicken, um eine Direktnachricht zu erstellen", - "UI_Click_Direct_Message_Description": "Den Profil-Tab überspringen und direkt zur Konversation gehen", "UI_DisplayRoles": "Rollen anzeigen", "UI_Group_Channels_By_Type": "Gruppiere Kanäle nach Typ", "UI_Merge_Channels_Groups": "Führe private und öffentliche Kanäle zusammen", @@ -3082,4 +3080,4 @@ "Your_question": "Deine Frage", "Your_server_link": "Dein Server-Link", "Your_workspace_is_ready": "Dein Arbeitsbereich ist einsatzbereit 🎉" -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/de.i18n.json b/packages/i18n/src/locales/de.i18n.json index 253b4987e40de..64d8175ad1cfc 100644 --- a/packages/i18n/src/locales/de.i18n.json +++ b/packages/i18n/src/locales/de.i18n.json @@ -4845,8 +4845,6 @@ "Type_your_password": "Geben Sie Ihr Passwort ein", "Type_your_username": "Geben Sie Ihren Benutzernamen ein", "UI_Allow_room_names_with_special_chars": "Sonderzeichen im Room-Namen erlauben", - "UI_Click_Direct_Message": "Anklicken, um eine Direktnachricht zu erstellen", - "UI_Click_Direct_Message_Description": "Den Profil-Tab überspringen und direkt zur Konversation gehen", "UI_DisplayRoles": "Rollen anzeigen", "UI_Group_Channels_By_Type": "Kanäle nach Typ gruppieren", "UI_Merge_Channels_Groups": "Private und öffentliche Channels zusammenführen", diff --git a/packages/i18n/src/locales/el.i18n.json b/packages/i18n/src/locales/el.i18n.json index 6c1fe148c1197..ecabeeaf31ae2 100644 --- a/packages/i18n/src/locales/el.i18n.json +++ b/packages/i18n/src/locales/el.i18n.json @@ -2481,8 +2481,6 @@ "Type_your_password": "Πληκτρολογήστε τον κωδικό πρόσβασης", "Type_your_username": "Πληκτρολογήστε το όνομα χρήστη σας", "UI_Allow_room_names_with_special_chars": "Αφήστε τους ειδικούς χαρακτήρες στα ονόματα των δωματίων", - "UI_Click_Direct_Message": "Κάντε κλικ στο στοιχείο Δημιουργία άμεσου μηνύματος", - "UI_Click_Direct_Message_Description": "Παράλειψη καρτέλας προφίλ ανοίγματος, αντί να πάτε κατευθείαν σε συνομιλία", "UI_DisplayRoles": "Ρόλοι οθόνη", "UI_Group_Channels_By_Type": "Ομαδοποιήστε τα κανάλια ανά τύπο", "UI_Merge_Channels_Groups": "Συγχώνευση ιδιωτικές ομάδες με τα κανάλια", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 6288757202b1d..903a8cfccedf4 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -5556,8 +5556,6 @@ "Type_your_password": "Type your password", "Type_your_username": "Type your username", "UI_Allow_room_names_with_special_chars": "Allow Special Characters in Room Names", - "UI_Click_Direct_Message": "Click to Create Direct Message", - "UI_Click_Direct_Message_Description": "Skip opening profile tab, instead go straight to conversation", "UI_DisplayRoles": "Display Roles", "UI_Group_Channels_By_Type": "Group channels by type", "UI_Merge_Channels_Groups": "Merge Private Groups with Channels", diff --git a/packages/i18n/src/locales/eo.i18n.json b/packages/i18n/src/locales/eo.i18n.json index 4148469b335bc..6340ba5044951 100644 --- a/packages/i18n/src/locales/eo.i18n.json +++ b/packages/i18n/src/locales/eo.i18n.json @@ -2474,8 +2474,6 @@ "Type_your_password": "Tajpu vian pasvorton", "Type_your_username": "Tajpu vian uzantnomon", "UI_Allow_room_names_with_special_chars": "Permesu Specialajn Karakterojn en Ĉambraj Nomoj", - "UI_Click_Direct_Message": "Alklaku por krei Rektan Mesaĝon", - "UI_Click_Direct_Message_Description": "Antaŭi malfermi profilon langeton, anstataŭ rekta al konversacio", "UI_DisplayRoles": "Montru Rolojn", "UI_Group_Channels_By_Type": "Grupoj de kanaloj laŭ tipo", "UI_Merge_Channels_Groups": "Kunfandi privatajn grupojn kun kanaloj", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index c66c07dda80c4..3910360a9f9bc 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -4306,8 +4306,6 @@ "Type_your_password": "Escribe tu contraseña", "Type_your_username": "Escribe tu nombre de usuario", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiales en nombres de Room", - "UI_Click_Direct_Message": "Clic para crear un mensaje directo", - "UI_Click_Direct_Message_Description": "Omitir la apertura de la pestaña de perfil e ir directamente a la conversación en su lugar", "UI_DisplayRoles": "Mostrar roles", "UI_Group_Channels_By_Type": "Agrupar canales por tipo", "UI_Merge_Channels_Groups": "Fusionar grupos privados con Channels", diff --git a/packages/i18n/src/locales/fa.i18n.json b/packages/i18n/src/locales/fa.i18n.json index 72bfc5ccc9b50..f8b7b62f536b3 100644 --- a/packages/i18n/src/locales/fa.i18n.json +++ b/packages/i18n/src/locales/fa.i18n.json @@ -2808,8 +2808,6 @@ "Type_your_password": "رمز عبور خود را تایپ کنید", "Type_your_username": "نام کاربری خود را وارد کنید", "UI_Allow_room_names_with_special_chars": "اجازه کاراکترهای ویژه در نام اتاق", - "UI_Click_Direct_Message": "برای ایجاد پیام مستقیم کلیک کنید", - "UI_Click_Direct_Message_Description": "برگه پروفایل را باز کنید، به جای آن به گفتگو بپردازید", "UI_DisplayRoles": "نقش ها", "UI_Group_Channels_By_Type": "گروه‌بندی کانال‌ها با نوع", "UI_Merge_Channels_Groups": "ادغام گروه های خصوصی با کانال ها", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index be26b145f766f..414aa4ac54da8 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -4942,8 +4942,6 @@ "Type_your_password": "Kirjoita salasanasi", "Type_your_username": "Kirjoita käyttäjätunnuksesi", "UI_Allow_room_names_with_special_chars": "Salli erikoismerkit huoneiden nimessä", - "UI_Click_Direct_Message": "Napsauta jotta luot suoran viestin", - "UI_Click_Direct_Message_Description": "Ohita Profiili-välilehti ja siirry suoraan keskusteluun", "UI_DisplayRoles": "Näytä roolit", "UI_Group_Channels_By_Type": "Ryhmittele kanavat tyypin mukaan", "UI_Merge_Channels_Groups": "Yhdistä yksityisiä ryhmiä kanaviin", diff --git a/packages/i18n/src/locales/fr.i18n.json b/packages/i18n/src/locales/fr.i18n.json index 29e7704b4c3a3..7dcf08d039865 100644 --- a/packages/i18n/src/locales/fr.i18n.json +++ b/packages/i18n/src/locales/fr.i18n.json @@ -4319,8 +4319,6 @@ "Type_your_password": "Entrez votre mot de passe", "Type_your_username": "Entrez votre nom d'utilisateur", "UI_Allow_room_names_with_special_chars": "Autoriser les caractères spéciaux dans les noms de salon", - "UI_Click_Direct_Message": "Cliquer pour créer un message direct", - "UI_Click_Direct_Message_Description": "Ne pas ouvrir l'onglet de profil, aller directement à la conversation", "UI_DisplayRoles": "Afficher les rôles", "UI_Group_Channels_By_Type": "Regrouper les canaux par type", "UI_Merge_Channels_Groups": "Fusionner des groupes privés avec des canaux", diff --git a/packages/i18n/src/locales/he.i18n.json b/packages/i18n/src/locales/he.i18n.json index 5c6c3d1f140bf..7cdc81b3ec1cd 100644 --- a/packages/i18n/src/locales/he.i18n.json +++ b/packages/i18n/src/locales/he.i18n.json @@ -1360,7 +1360,6 @@ "Type_your_name": "הכנס את השם שלך", "Type_your_password": "הכנס את הסיסמה שלך", "Type_your_username": "הכנס את שם המשתמש שלך", - "UI_Click_Direct_Message": "לחץ לשליחת הודעה פרטית", "UI_DisplayRoles": "תפקידי תצוגה", "UI_Group_Channels_By_Type": "מיין קבוצות לפי סוג", "UI_Merge_Channels_Groups": "מיזוג קבוצות פרטיות עם קבוצות פומביות", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index ce5010c4a2c6e..68df36a411bd1 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -5213,8 +5213,6 @@ "Type_your_password": "अपना पासवर्ड टाइप करें", "Type_your_username": "अपना उपयोगकर्ता नाम टाइप करें", "UI_Allow_room_names_with_special_chars": "कमरे के नाम में विशेष वर्णों की अनुमति दें", - "UI_Click_Direct_Message": "सीधा संदेश बनाने के लिए क्लिक करें", - "UI_Click_Direct_Message_Description": "प्रोफ़ाइल टैब खोलना छोड़ें, इसके बजाय सीधे बातचीत पर जाएँ", "UI_DisplayRoles": "भूमिकाएँ प्रदर्शित करें", "UI_Group_Channels_By_Type": "चैनलों को प्रकार के अनुसार समूहित करें", "UI_Merge_Channels_Groups": "निजी समूहों को चैनलों के साथ मिलाएं", diff --git a/packages/i18n/src/locales/hr.i18n.json b/packages/i18n/src/locales/hr.i18n.json index 2b9d5fc3340c8..ef6cf511ee7a8 100644 --- a/packages/i18n/src/locales/hr.i18n.json +++ b/packages/i18n/src/locales/hr.i18n.json @@ -2608,8 +2608,6 @@ "Type_your_password": "Upišite svoju lozinku", "Type_your_username": "Upišite svoje korisničko ime", "UI_Allow_room_names_with_special_chars": "Omogući posebne znakove u imenima soba", - "UI_Click_Direct_Message": "Kliknite da biste stvorili izravnu poruku", - "UI_Click_Direct_Message_Description": "Preskoči karticu profila otvaranja, umjesto toga idite ravno u razgovor", "UI_DisplayRoles": "Prikaži uloge", "UI_Group_Channels_By_Type": "Grupni kanali prema vrsti", "UI_Merge_Channels_Groups": "Spoji privatne grupe s kanalima", diff --git a/packages/i18n/src/locales/hu.i18n.json b/packages/i18n/src/locales/hu.i18n.json index 1165f2d1294de..eea095eb83129 100644 --- a/packages/i18n/src/locales/hu.i18n.json +++ b/packages/i18n/src/locales/hu.i18n.json @@ -4754,8 +4754,6 @@ "Type_your_password": "Írja be a jelszavát", "Type_your_username": "Írja be felhasználónevét", "UI_Allow_room_names_with_special_chars": "Különleges karakterek engedélyezése a szobanevekben", - "UI_Click_Direct_Message": "Kattintson a közvetlen üzenet létrehozásához", - "UI_Click_Direct_Message_Description": "A profillap megnyitásának kihagyása, ugrás inkább egyenesen a beszélgetéshez", "UI_DisplayRoles": "Szerepek megjelenítése", "UI_Group_Channels_By_Type": "Csatornák csoportosítása típus szerint", "UI_Merge_Channels_Groups": "Személyes csoportok egyesítése a csatornákkal", diff --git a/packages/i18n/src/locales/id.i18n.json b/packages/i18n/src/locales/id.i18n.json index 3131657ec519b..5e29925ead23d 100644 --- a/packages/i18n/src/locales/id.i18n.json +++ b/packages/i18n/src/locales/id.i18n.json @@ -2482,8 +2482,6 @@ "Type_your_password": "Ketikkan kata sandi Anda", "Type_your_username": "Ketikkan nama pengguna Anda", "UI_Allow_room_names_with_special_chars": "Biarkan Karakter Khusus dalam Nama Kamar", - "UI_Click_Direct_Message": "Klik untuk membuat Direct Message", - "UI_Click_Direct_Message_Description": "Skip membuka tab profil, malah langsung ke percakapan", "UI_DisplayRoles": "Peran display", "UI_Group_Channels_By_Type": "Kelompokkan saluran menurut tipe", "UI_Merge_Channels_Groups": "Gabungkan grup pribadi dengan saluran", diff --git a/packages/i18n/src/locales/it.i18n.json b/packages/i18n/src/locales/it.i18n.json index 9a13dddc370ae..ad9e2892af610 100644 --- a/packages/i18n/src/locales/it.i18n.json +++ b/packages/i18n/src/locales/it.i18n.json @@ -3042,8 +3042,6 @@ "Type_your_password": "Digita la tua password", "Type_your_username": "Inserisci il tuo nome utente", "UI_Allow_room_names_with_special_chars": "Consenti caratteri speciali nei nomi delle stanze", - "UI_Click_Direct_Message": "Clicca per creare un messaggio diretto", - "UI_Click_Direct_Message_Description": "Salta la scheda del profilo di apertura, invece vai direttamente alla conversazione", "UI_DisplayRoles": "Mostra Ruoli", "UI_Group_Channels_By_Type": "Raggruppa i canali per tipo", "UI_Merge_Channels_Groups": "Unisci i gruppi privati con i canali", diff --git a/packages/i18n/src/locales/ja.i18n.json b/packages/i18n/src/locales/ja.i18n.json index cdf67b056bd8a..1df9e0ce170a3 100644 --- a/packages/i18n/src/locales/ja.i18n.json +++ b/packages/i18n/src/locales/ja.i18n.json @@ -4267,8 +4267,6 @@ "Type_your_password": "パスワードを入力", "Type_your_username": "ユーザー名を入力", "UI_Allow_room_names_with_special_chars": "Room名に特殊文字を許可", - "UI_Click_Direct_Message": "クリックしてダイレクトメッセージを作成", - "UI_Click_Direct_Message_Description": "プロフィールタブを開くのをスキップし、会話に直接移動", "UI_DisplayRoles": "ロールの表示", "UI_Group_Channels_By_Type": "種類別にチャネルをグループ化", "UI_Merge_Channels_Groups": "プライベートグループとChannelをマージ", diff --git a/packages/i18n/src/locales/ka-GE.i18n.json b/packages/i18n/src/locales/ka-GE.i18n.json index aac3c689457b0..cf231b838dcec 100644 --- a/packages/i18n/src/locales/ka-GE.i18n.json +++ b/packages/i18n/src/locales/ka-GE.i18n.json @@ -3324,8 +3324,6 @@ "Type_your_password": "აკრიფეთ თქვენი პაროლი", "Type_your_username": "აკრიფეთ თქვენი მომხმარებლის სახელი", "UI_Allow_room_names_with_special_chars": "ოთახების სახელებში სპეციალური ნიშნების დაშვება", - "UI_Click_Direct_Message": "დააჭირეთ პირდაპირი შეტყობინების შესაქმნელად", - "UI_Click_Direct_Message_Description": "გამოტოვე პროფილის ტაბის გახსნა, გადადი პირდაპირ საუბარზე", "UI_DisplayRoles": "როლების ჩვენება", "UI_Group_Channels_By_Type": "ჯგუფის არხები ტიპის მიხედვით", "UI_Merge_Channels_Groups": "პირადი ჯგუფების არხებთან გაერთიანება", diff --git a/packages/i18n/src/locales/km.i18n.json b/packages/i18n/src/locales/km.i18n.json index 4217963fa8a2a..77815b2a8e492 100644 --- a/packages/i18n/src/locales/km.i18n.json +++ b/packages/i18n/src/locales/km.i18n.json @@ -2815,8 +2815,6 @@ "Type_your_password": "បញ្ចូលពាក្យសម្ងាត់របស់អ្នក", "Type_your_username": "បញ្ចូលឈ្មោះអ្នកប្រើរបស់អ្នក", "UI_Allow_room_names_with_special_chars": "អនុញ្ញាតតួអក្សរពិសេសក្នុងឈ្មោះបន្ទប់", - "UI_Click_Direct_Message": "ចុចដើម្បីបង្កើតសារដោយផ្ទាល់", - "UI_Click_Direct_Message_Description": "រំលងផ្ទាំងបើកប្រវត្តិរូបជំនួសវិញទៅត្រង់ការសន្ទនា", "UI_DisplayRoles": "បង្ហាញតួនាទី", "UI_Group_Channels_By_Type": "បណ្តាញក្រុមតាមប្រភេទ", "UI_Merge_Channels_Groups": "បញ្ចូលចូលគ្នាជាមួយបណ្តាញក្រុមឯកជន", diff --git a/packages/i18n/src/locales/ko.i18n.json b/packages/i18n/src/locales/ko.i18n.json index 76472af94d5f0..8ad8378ebd4b8 100644 --- a/packages/i18n/src/locales/ko.i18n.json +++ b/packages/i18n/src/locales/ko.i18n.json @@ -3643,8 +3643,6 @@ "Type_your_password": "암호를 입력하세요.", "Type_your_username": "사용자명을 입력하세요.", "UI_Allow_room_names_with_special_chars": "대화방명에 특수문자 허용", - "UI_Click_Direct_Message": "클릭하여 1:1 대화방 열기", - "UI_Click_Direct_Message_Description": "프로필 탭을 건너뛰고 대화방으로 이동합니다.", "UI_DisplayRoles": "역할 표시하기", "UI_Group_Channels_By_Type": "유형별로 채널 그룹화", "UI_Merge_Channels_Groups": "Channel 과 비공개 그룹 병합", diff --git a/packages/i18n/src/locales/ku.i18n.json b/packages/i18n/src/locales/ku.i18n.json index 4e94705ce77f9..3a135c886c9c7 100644 --- a/packages/i18n/src/locales/ku.i18n.json +++ b/packages/i18n/src/locales/ku.i18n.json @@ -2468,8 +2468,6 @@ "Type_your_password": "Şîfreya te binivîse", "Type_your_username": "Navê te binivîse", "UI_Allow_room_names_with_special_chars": "Li Niştimanî Nasnameyên Taybet Taybetî bide", - "UI_Click_Direct_Message": "Bişkojka Rêveberiya Direct Directîf bikî", - "UI_Click_Direct_Message_Description": "Vegerîna profesyonê vekin, veguhertina rasterast li ser biaxivin", "UI_DisplayRoles": "ristên Display", "UI_Group_Channels_By_Type": "Kanalên grûpê bi cureyê", "UI_Merge_Channels_Groups": "Merge komên taybet bi kanalên", diff --git a/packages/i18n/src/locales/lo.i18n.json b/packages/i18n/src/locales/lo.i18n.json index f4ddab5a2a581..ad1721739c251 100644 --- a/packages/i18n/src/locales/lo.i18n.json +++ b/packages/i18n/src/locales/lo.i18n.json @@ -2511,8 +2511,6 @@ "Type_your_password": "ພິມລະຫັດຜ່ານຂອງທ່ານ", "Type_your_username": "ພິມຊື່ຜູ້ໃຊ້ຂອງທ່ານ", "UI_Allow_room_names_with_special_chars": "ອະນຸຍາດໃຫ້ຕົວອັກສອນພິເສດໃນຊື່ຫ້ອງ", - "UI_Click_Direct_Message": "ກົດເພື່ອສ້າງຂໍ້ຄວາມໂດຍກົງ", - "UI_Click_Direct_Message_Description": "ຂ້າມແຖບໂປຣແກຣມເປີດ, ແທນທີ່ຈະໄປຫາການສົນທະນາ", "UI_DisplayRoles": "ພາລະບົດບາດການສະແດງ", "UI_Group_Channels_By_Type": "ກຸ່ມຂອງກຸ່ມໂດຍປະເພດ", "UI_Merge_Channels_Groups": "ລວມກຸ່ມເອກະຊົນມີຊ່ອງ", diff --git a/packages/i18n/src/locales/lt.i18n.json b/packages/i18n/src/locales/lt.i18n.json index 131eb20d4e89f..16a99624534e3 100644 --- a/packages/i18n/src/locales/lt.i18n.json +++ b/packages/i18n/src/locales/lt.i18n.json @@ -2529,8 +2529,6 @@ "Type_your_password": "Įveskite savo slaptažodį", "Type_your_username": "Įveskite savo vartotojo vardą", "UI_Allow_room_names_with_special_chars": "Leisti specialius simbolius kambario pavadinimuose", - "UI_Click_Direct_Message": "Spustelėkite, jei norite sukurti tiesioginę žinutę", - "UI_Click_Direct_Message_Description": "Praleiskite atidarymo profilio skirtuką, o ne tiesiog eikite į pokalbį", "UI_DisplayRoles": "Rodyti vaidmenis", "UI_Group_Channels_By_Type": "Grupuoti kanalus pagal tipą", "UI_Merge_Channels_Groups": "Prijungti privačias grupes su kanalais", diff --git a/packages/i18n/src/locales/lv.i18n.json b/packages/i18n/src/locales/lv.i18n.json index 8fe44db8ba3d5..f316ee4f18ff2 100644 --- a/packages/i18n/src/locales/lv.i18n.json +++ b/packages/i18n/src/locales/lv.i18n.json @@ -2481,8 +2481,6 @@ "Type_your_password": "Ievadiet savu paroli", "Type_your_username": "Ievadiet savu lietotājvārdu", "UI_Allow_room_names_with_special_chars": "Atļaut īpašus simbolus istabu nosaukumos", - "UI_Click_Direct_Message": "Noklikšķiniet, lai izveidotu ziņojumu", - "UI_Click_Direct_Message_Description": "Izlaist profila atvēršanas cilni, tā vietā dodieties tieši uz sarunu", "UI_DisplayRoles": "Parādt lomas", "UI_Group_Channels_By_Type": "Grupēt kanālus pēc veida", "UI_Merge_Channels_Groups": "Apvienot privātās grupas ar kanāliem", diff --git a/packages/i18n/src/locales/mn.i18n.json b/packages/i18n/src/locales/mn.i18n.json index d66141b425fbe..aad1cb2b8829b 100644 --- a/packages/i18n/src/locales/mn.i18n.json +++ b/packages/i18n/src/locales/mn.i18n.json @@ -2467,8 +2467,6 @@ "Type_your_password": "Нууц үгээ бичнэ үү", "Type_your_username": "Хэрэглэгчийн нэрээ оруулна уу", "UI_Allow_room_names_with_special_chars": "Өрөөний нэрээр тусгай тэмдэгтүүдийг зөвшөөрөх", - "UI_Click_Direct_Message": "Шууд зурвас үүсгэх бол товшино уу", - "UI_Click_Direct_Message_Description": "Нээлтийн профайлын цонх алга, харин оронд нь харилцан яриа руу яваарай", "UI_DisplayRoles": "Дэлгэцийн үүрэг", "UI_Group_Channels_By_Type": "Группийн сувгууд төрөл хэлбэрээр", "UI_Merge_Channels_Groups": "Сувгуудтай хувийн бүлэгнүүдийг нэгтгэх", diff --git a/packages/i18n/src/locales/ms-MY.i18n.json b/packages/i18n/src/locales/ms-MY.i18n.json index 42156cb7aaebe..b62ff81cae20d 100644 --- a/packages/i18n/src/locales/ms-MY.i18n.json +++ b/packages/i18n/src/locales/ms-MY.i18n.json @@ -2481,8 +2481,6 @@ "Type_your_password": "Taip kata laluan anda", "Type_your_username": "Taip nama pengguna anda", "UI_Allow_room_names_with_special_chars": "Benarkan Huruf Khas dalam Nama Bilik", - "UI_Click_Direct_Message": "Klik untuk Buat Mesej Langsung", - "UI_Click_Direct_Message_Description": "Langkau tab profil terbuka, bukan langsung ke perbualan", "UI_DisplayRoles": "Peranan paparan", "UI_Group_Channels_By_Type": "Saluran kumpulan mengikut jenis", "UI_Merge_Channels_Groups": "Gabungkan kumpulan peribadi dengan saluran", diff --git a/packages/i18n/src/locales/nl.i18n.json b/packages/i18n/src/locales/nl.i18n.json index b9500f2305996..d734faa206da0 100644 --- a/packages/i18n/src/locales/nl.i18n.json +++ b/packages/i18n/src/locales/nl.i18n.json @@ -4306,8 +4306,6 @@ "Type_your_password": "Typ je wachtwoord", "Type_your_username": "Typ je gebruikersnaam", "UI_Allow_room_names_with_special_chars": "Sta speciale tekens toe in kamernamen", - "UI_Click_Direct_Message": "Klik om een direct bericht aan te maken", - "UI_Click_Direct_Message_Description": "Sla het openen van het profieltabblad over en ga rechtstreeks naar het gesprek", "UI_DisplayRoles": "Rollen tonen", "UI_Group_Channels_By_Type": "Groepeer kanalen op type", "UI_Merge_Channels_Groups": "Privégroepen met kanalen samenvoegen", diff --git a/packages/i18n/src/locales/nn.i18n.json b/packages/i18n/src/locales/nn.i18n.json index 9786da87acdcc..f6697250ced28 100644 --- a/packages/i18n/src/locales/nn.i18n.json +++ b/packages/i18n/src/locales/nn.i18n.json @@ -4120,8 +4120,6 @@ "Type_your_password": "Skriv inn passordet ditt", "Type_your_username": "Skriv inn brukernavnet ditt", "UI_Allow_room_names_with_special_chars": "Tillat spesialtegn i romnavn", - "UI_Click_Direct_Message": "Klikk for å opprette direkte melding", - "UI_Click_Direct_Message_Description": "Hopp over åpningsprofil-fanen, i stedet gå direkte til samtale", "UI_DisplayRoles": "Vis roller", "UI_Group_Channels_By_Type": "Gruppekanaler etter type", "UI_Merge_Channels_Groups": "Slett private grupper med kanaler", diff --git a/packages/i18n/src/locales/no.i18n.json b/packages/i18n/src/locales/no.i18n.json index 9786da87acdcc..f6697250ced28 100644 --- a/packages/i18n/src/locales/no.i18n.json +++ b/packages/i18n/src/locales/no.i18n.json @@ -4120,8 +4120,6 @@ "Type_your_password": "Skriv inn passordet ditt", "Type_your_username": "Skriv inn brukernavnet ditt", "UI_Allow_room_names_with_special_chars": "Tillat spesialtegn i romnavn", - "UI_Click_Direct_Message": "Klikk for å opprette direkte melding", - "UI_Click_Direct_Message_Description": "Hopp over åpningsprofil-fanen, i stedet gå direkte til samtale", "UI_DisplayRoles": "Vis roller", "UI_Group_Channels_By_Type": "Gruppekanaler etter type", "UI_Merge_Channels_Groups": "Slett private grupper med kanaler", diff --git a/packages/i18n/src/locales/pl.i18n.json b/packages/i18n/src/locales/pl.i18n.json index 08dfbd471e862..63c629f2391d5 100644 --- a/packages/i18n/src/locales/pl.i18n.json +++ b/packages/i18n/src/locales/pl.i18n.json @@ -4752,8 +4752,6 @@ "Type_your_password": "Wpisz swoje hasło", "Type_your_username": "Wpisz swoją nazwę użytkownika", "UI_Allow_room_names_with_special_chars": "Zezwalaj na specjalne znaki w nazwach pokoi", - "UI_Click_Direct_Message": "Kliknij, aby utworzyć bezpośrednią wiadomość", - "UI_Click_Direct_Message_Description": "Pomiń otwarcie karty profilu, zamiast tego przejdź bezpośrednio do rozmowy", "UI_DisplayRoles": "Pokaż role", "UI_Group_Channels_By_Type": "Grupuj kanały według typu", "UI_Merge_Channels_Groups": "Scalanie prywatne grupy z kanałami", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index a5e79c8ae9db7..b299fb0fbbfc8 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -4458,8 +4458,6 @@ "Type_your_password": "Digite sua senha", "Type_your_username": "Digite seu nome", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiais em nomes de salas", - "UI_Click_Direct_Message": "Clique para criar mensagem direta", - "UI_Click_Direct_Message_Description": "Pule a aba do perfil de abertura e vá direto para a conversa", "UI_DisplayRoles": "Exibir funções", "UI_Group_Channels_By_Type": "Canais de grupo por tipo", "UI_Merge_Channels_Groups": "Mesclar grupos privados com canais", diff --git a/packages/i18n/src/locales/pt.i18n.json b/packages/i18n/src/locales/pt.i18n.json index 62316ffb6f15b..bd3c2dda458ca 100644 --- a/packages/i18n/src/locales/pt.i18n.json +++ b/packages/i18n/src/locales/pt.i18n.json @@ -2862,8 +2862,6 @@ "Type_your_password": "Digite a sua senha", "Type_your_username": "Digite o seu nome de utilizador", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiais em nomes de salas", - "UI_Click_Direct_Message": "Clique para criar mensagem directa", - "UI_Click_Direct_Message_Description": "Salte a aba de perfil de abertura, em vez disso vá direto para a conversa", "UI_DisplayRoles": "Exibir funções", "UI_Group_Channels_By_Type": "Canais de grupo por tipo", "UI_Merge_Channels_Groups": "Mesclar grupos privados com canais", diff --git a/packages/i18n/src/locales/ro.i18n.json b/packages/i18n/src/locales/ro.i18n.json index d94a55107b111..41bb29ed88286 100644 --- a/packages/i18n/src/locales/ro.i18n.json +++ b/packages/i18n/src/locales/ro.i18n.json @@ -2473,8 +2473,6 @@ "Type_your_password": "Introduceți parola", "Type_your_username": "Introduceți numele de utilizator", "UI_Allow_room_names_with_special_chars": "Permiteți caracterele speciale în numele camerelor", - "UI_Click_Direct_Message": "Faceți clic pentru a crea mesajul direct", - "UI_Click_Direct_Message_Description": "Sari peste fila profilului de deschidere, mergeți direct la conversație", "UI_DisplayRoles": "Roluri de afișare", "UI_Group_Channels_By_Type": "Grupați canalele după tip", "UI_Merge_Channels_Groups": "Îmbinare grupuri private cu canale", diff --git a/packages/i18n/src/locales/ru.i18n.json b/packages/i18n/src/locales/ru.i18n.json index c7f66b4375c9c..dcdd76a37d529 100644 --- a/packages/i18n/src/locales/ru.i18n.json +++ b/packages/i18n/src/locales/ru.i18n.json @@ -4494,8 +4494,6 @@ "Type_your_password": "Введите пароль", "Type_your_username": "Введите имя пользователя", "UI_Allow_room_names_with_special_chars": "Разрешить специальные символы в названии комнаты", - "UI_Click_Direct_Message": "Нажмите, чтобы создать личное сообщение", - "UI_Click_Direct_Message_Description": "Не открывать вкладку профиля, сразу переходить к беседе", "UI_DisplayRoles": "Показывать роли пользователей", "UI_Group_Channels_By_Type": "Группировать каналы по типу", "UI_Merge_Channels_Groups": "Отображать публичные и закрытые каналы единым списком", diff --git a/packages/i18n/src/locales/se.i18n.json b/packages/i18n/src/locales/se.i18n.json index 3838e9d551929..51d1781372f3d 100644 --- a/packages/i18n/src/locales/se.i18n.json +++ b/packages/i18n/src/locales/se.i18n.json @@ -5556,8 +5556,6 @@ "Type_your_password": "Type your password", "Type_your_username": "Type your username", "UI_Allow_room_names_with_special_chars": "Allow Special Characters in Room Names", - "UI_Click_Direct_Message": "Click to Create Direct Message", - "UI_Click_Direct_Message_Description": "Skip opening profile tab, instead go straight to conversation", "UI_DisplayRoles": "Display Roles", "UI_Group_Channels_By_Type": "Group channels by type", "UI_Merge_Channels_Groups": "Merge Private Groups with Channels", diff --git a/packages/i18n/src/locales/sk-SK.i18n.json b/packages/i18n/src/locales/sk-SK.i18n.json index bb879df65f167..fe53212bc8e08 100644 --- a/packages/i18n/src/locales/sk-SK.i18n.json +++ b/packages/i18n/src/locales/sk-SK.i18n.json @@ -2483,8 +2483,6 @@ "Type_your_password": "Zadajte svoje heslo", "Type_your_username": "Zadajte svoje používateľské meno", "UI_Allow_room_names_with_special_chars": "Povoliť špeciálne znaky v názvoch miestností", - "UI_Click_Direct_Message": "Kliknutím vytvoríte priamu správu", - "UI_Click_Direct_Message_Description": "Vynechajte kartu profilu otvárania, namiesto toho prejdite rovno do konverzácie", "UI_DisplayRoles": "Zobrazenie rolí", "UI_Group_Channels_By_Type": "Skupinové kanály podľa typu", "UI_Merge_Channels_Groups": "Zlúčiť súkromné ​​skupiny s kanálmi", diff --git a/packages/i18n/src/locales/sl-SI.i18n.json b/packages/i18n/src/locales/sl-SI.i18n.json index 557fb1f0a4987..46710fdd45fa8 100644 --- a/packages/i18n/src/locales/sl-SI.i18n.json +++ b/packages/i18n/src/locales/sl-SI.i18n.json @@ -2463,8 +2463,6 @@ "Type_your_password": "Vnesite geslo", "Type_your_username": "Vnesite svoje uporabniško ime", "UI_Allow_room_names_with_special_chars": "V imenih sob dovoli posebne znake", - "UI_Click_Direct_Message": "Kliknite za pisanje neposrednega sporočila", - "UI_Click_Direct_Message_Description": "Preskočite odpiranje zavihek profila, namesto tega pojdite naravnost v pogovor", "UI_DisplayRoles": "Prikaži vloge", "UI_Group_Channels_By_Type": "Skupinski kanali po vrsti", "UI_Merge_Channels_Groups": "Združite zasebne skupine s kanali", diff --git a/packages/i18n/src/locales/sq.i18n.json b/packages/i18n/src/locales/sq.i18n.json index 40936b38a3d10..342ea7e9ea319 100644 --- a/packages/i18n/src/locales/sq.i18n.json +++ b/packages/i18n/src/locales/sq.i18n.json @@ -2473,8 +2473,6 @@ "Type_your_password": "Shkruaj fjalëkalimin tënd", "Type_your_username": "Shkruaj emrin e përdoruesit", "UI_Allow_room_names_with_special_chars": "Lejo karaktere speciale në Emrat e dhomave", - "UI_Click_Direct_Message": "Kliko për të krijuar mesazhin e drejtpërdrejtë", - "UI_Click_Direct_Message_Description": "Hiq hapjen e skedës së profilit, në vend të kësaj shko direkt në bisedë", "UI_DisplayRoles": "Display Rolet", "UI_Group_Channels_By_Type": "Kanalet e grupit sipas llojit", "UI_Merge_Channels_Groups": "Bashkojë grupet private me kanale", diff --git a/packages/i18n/src/locales/sr.i18n.json b/packages/i18n/src/locales/sr.i18n.json index 4e1ce1b6f34bd..a483c6be75f5b 100644 --- a/packages/i18n/src/locales/sr.i18n.json +++ b/packages/i18n/src/locales/sr.i18n.json @@ -2279,8 +2279,6 @@ "Type_your_password": "Унесите своју лозинку", "Type_your_username": "Унесите своје корисничко име", "UI_Allow_room_names_with_special_chars": "Дозволи посебне знакове у именима соба", - "UI_Click_Direct_Message": "Кликните да бисте креирали директну поруку", - "UI_Click_Direct_Message_Description": "Прескочите отворени табулатор профила, уместо да идете директно у разговор", "UI_DisplayRoles": "Дисплаи Улоге", "UI_Group_Channels_By_Type": "Групни канали по типу", "UI_Merge_Channels_Groups": "Мерге приватне групе са каналима", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index 2606c483a3c60..eddd365b407e4 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -4951,8 +4951,6 @@ "Type_your_password": "Skriv ditt lösenord", "Type_your_username": "Skriv ditt användarnamn", "UI_Allow_room_names_with_special_chars": "Tillåt särskilda tecken i rumsnamn", - "UI_Click_Direct_Message": "Klicka för att skapa direktmeddelande", - "UI_Click_Direct_Message_Description": "Hoppa över öppningsprofilfliken, gå istället direkt till konversation", "UI_DisplayRoles": "Visa roller", "UI_Group_Channels_By_Type": "Gruppkanaler efter typ", "UI_Merge_Channels_Groups": "Slå ihop privata grupper med kanaler", diff --git a/packages/i18n/src/locales/ta-IN.i18n.json b/packages/i18n/src/locales/ta-IN.i18n.json index cab6bfaeb8edb..83f1d0726a679 100644 --- a/packages/i18n/src/locales/ta-IN.i18n.json +++ b/packages/i18n/src/locales/ta-IN.i18n.json @@ -2473,8 +2473,6 @@ "Type_your_password": "உங்கள் கடவுச்சொல்லை உள்ளிடவும்", "Type_your_username": "உங்கள் பயனர்பெயரைத் தட்டச்சு செய்க", "UI_Allow_room_names_with_special_chars": "அறை பெயர்களில் சிறப்பு எழுத்துகள் அனுமதி", - "UI_Click_Direct_Message": "நேரடி செய்தி உருவாக்க கிளிக் செய்க", - "UI_Click_Direct_Message_Description": "சுயவிவரத் தாவலைத் திறந்து, உரையாடலுக்கு நேராக செல்லுங்கள்", "UI_DisplayRoles": "காட்சி பாத்திரங்கள்", "UI_Group_Channels_By_Type": "வகை மூலம் குழு சேனல்கள்", "UI_Merge_Channels_Groups": "சேனல்கள் கொண்ட தனியார் குழுக்கள் ஒன்றாக்க", diff --git a/packages/i18n/src/locales/th-TH.i18n.json b/packages/i18n/src/locales/th-TH.i18n.json index 321ed8676512e..0f5c7de06fe0e 100644 --- a/packages/i18n/src/locales/th-TH.i18n.json +++ b/packages/i18n/src/locales/th-TH.i18n.json @@ -2465,8 +2465,6 @@ "Type_your_password": "พิมพ์รหัสผ่านของคุณ", "Type_your_username": "พิมพ์ชื่อผู้ใช้ของคุณ", "UI_Allow_room_names_with_special_chars": "อนุญาตให้ใช้อักขระพิเศษในชื่อห้อง", - "UI_Click_Direct_Message": "คลิกเพื่อสร้างข้อความตรง", - "UI_Click_Direct_Message_Description": "ข้ามแท็บโปรไฟล์เปิดแทนที่จะไปตรงไปที่การสนทนา", "UI_DisplayRoles": "แสดงบทบาท", "UI_Group_Channels_By_Type": "จัดกลุ่มตามประเภท", "UI_Merge_Channels_Groups": "รวมกลุ่มส่วนตัวด้วยแชเนล", diff --git a/packages/i18n/src/locales/tr.i18n.json b/packages/i18n/src/locales/tr.i18n.json index d758aea6a4193..86385ed56b373 100644 --- a/packages/i18n/src/locales/tr.i18n.json +++ b/packages/i18n/src/locales/tr.i18n.json @@ -2942,8 +2942,6 @@ "Type_your_password": "Şifrenizi giriniz", "Type_your_username": "Kullanıcı adınızı yazın", "UI_Allow_room_names_with_special_chars": "Oda Adlarında Özel Karakterlere İzin Ver", - "UI_Click_Direct_Message": "Doğrudan İleti Oluşturmak İçin Tıklayın", - "UI_Click_Direct_Message_Description": "Açılış profili sekmesini atla, bunun yerine konuşmaya devam et", "UI_DisplayRoles": "Rolleri Göster", "UI_Group_Channels_By_Type": "Kanalları türe göre grupla", "UI_Merge_Channels_Groups": "Özel grupları kanallar ile birleştir", diff --git a/packages/i18n/src/locales/uk.i18n.json b/packages/i18n/src/locales/uk.i18n.json index 691ef48eb2992..40f4f74bf8ae3 100644 --- a/packages/i18n/src/locales/uk.i18n.json +++ b/packages/i18n/src/locales/uk.i18n.json @@ -3035,8 +3035,6 @@ "Type_your_password": "Введіть свій пароль", "Type_your_username": "Введіть своє ім'я користувача", "UI_Allow_room_names_with_special_chars": "Дозволити спеціальні символи в іменах номерів", - "UI_Click_Direct_Message": "Натисніть, щоб створити пряме повідомлення", - "UI_Click_Direct_Message_Description": "Пропустити відкриття вкладки профілю, замість цього перейти прямо до бесіди", "UI_DisplayRoles": "Показати Ролі", "UI_Group_Channels_By_Type": "Групувати канали за типами", "UI_Merge_Channels_Groups": "Об'єднання приватних груп з каналами", diff --git a/packages/i18n/src/locales/vi-VN.i18n.json b/packages/i18n/src/locales/vi-VN.i18n.json index 75cc46bad7eb6..05df188f9c23b 100644 --- a/packages/i18n/src/locales/vi-VN.i18n.json +++ b/packages/i18n/src/locales/vi-VN.i18n.json @@ -2574,8 +2574,6 @@ "Type_your_password": "Loại mật khẩu", "Type_your_username": "Nhập tên người dùng", "UI_Allow_room_names_with_special_chars": "Cho phép các ký tự đặc biệt trong tên phòng", - "UI_Click_Direct_Message": "Nhấp để tạo tin nhắn trực tiếp", - "UI_Click_Direct_Message_Description": "Bỏ qua tab hồ sơ, thay vào đó đi thẳng vào cuộc trò chuyện", "UI_DisplayRoles": "Hiển thị vai trò", "UI_Group_Channels_By_Type": "Nhóm kênh theo loại", "UI_Merge_Channels_Groups": "Hợp nhất các nhóm riêng tư với các kênh", diff --git a/packages/i18n/src/locales/zh-HK.i18n.json b/packages/i18n/src/locales/zh-HK.i18n.json index e0c26531d67b5..42fd9fb00919a 100644 --- a/packages/i18n/src/locales/zh-HK.i18n.json +++ b/packages/i18n/src/locales/zh-HK.i18n.json @@ -2501,8 +2501,6 @@ "Type_your_password": "输入您的密码", "Type_your_username": "输入您的用户名", "UI_Allow_room_names_with_special_chars": "允许房间名称中的特殊字符", - "UI_Click_Direct_Message": "点击创建直接消息", - "UI_Click_Direct_Message_Description": "跳过打开配置文件选项卡,而是直接进入会话", "UI_DisplayRoles": "显示角色", "UI_Group_Channels_By_Type": "按类型分组频道", "UI_Merge_Channels_Groups": "用渠道合并私人组", diff --git a/packages/i18n/src/locales/zh-TW.i18n.json b/packages/i18n/src/locales/zh-TW.i18n.json index 945721f6e82d9..13ba9888704b8 100644 --- a/packages/i18n/src/locales/zh-TW.i18n.json +++ b/packages/i18n/src/locales/zh-TW.i18n.json @@ -4078,8 +4078,6 @@ "Type_your_password": "輸入您的密碼", "Type_your_username": "輸入您的使用者名稱", "UI_Allow_room_names_with_special_chars": "允許 Room 名稱中的特殊字元", - "UI_Click_Direct_Message": "點擊建立直接訊息", - "UI_Click_Direct_Message_Description": "略過打開配置選項分頁,而是直接進入會話", "UI_DisplayRoles": "顯示角色", "UI_Group_Channels_By_Type": "按類型分組頻道", "UI_Merge_Channels_Groups": "合併私人群組到 Channel", diff --git a/packages/i18n/src/locales/zh.i18n.json b/packages/i18n/src/locales/zh.i18n.json index d0ea0cbeacf77..31ba47b5f279d 100644 --- a/packages/i18n/src/locales/zh.i18n.json +++ b/packages/i18n/src/locales/zh.i18n.json @@ -3736,8 +3736,6 @@ "Type_your_password": "输入您的密码", "Type_your_username": "输入您的用户名", "UI_Allow_room_names_with_special_chars": "允许Room名称中的特殊字符", - "UI_Click_Direct_Message": "点击以创建私聊", - "UI_Click_Direct_Message_Description": "跳过打开个人信息标签页,直接进入会话", "UI_DisplayRoles": "显示角色", "UI_Group_Channels_By_Type": "按类型分组频道", "UI_Merge_Channels_Groups": "将私人组与频道合并", From 0a028c49044badc861942b7de73a3a190c19b3b5 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Fri, 11 Oct 2024 12:06:37 -0300 Subject: [PATCH 73/85] regression(Marketplace): Restore changes lost in rebase (#33514) * regression(Marketplace): Restore missing button on empty state * regression(Marketplace): Restore missing text in `AppsUsageCard` * regression(Marketplace): Fix misalignment on marketplace header * regression(Marketplace): Fix wrong import on uninstall app modal * regression(Marketplace): Restore link to uninstall app modal --- .../AppsUsageCard/AppsUsageCard.spec.tsx | 6 ++--- .../cards/AppsUsageCard/AppsUsageCard.tsx | 22 +++++++++---------- .../marketplace/AppsPage/AppsFilters.tsx | 2 +- .../AppsPage/PrivateEmptyStateUpgrade.tsx | 19 +++++++++++----- .../UninstallGrandfatheredAppModal.tsx | 10 +++++++-- packages/i18n/src/locales/en.i18n.json | 1 - packages/i18n/src/locales/es.i18n.json | 1 - packages/i18n/src/locales/hi-IN.i18n.json | 1 - packages/i18n/src/locales/pl.i18n.json | 1 - packages/i18n/src/locales/se.i18n.json | 1 - 10 files changed, 36 insertions(+), 28 deletions(-) diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx index b5d44e3bf942c..84861f8cc9164 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.spec.tsx @@ -8,8 +8,6 @@ import AppsUsageCard from './AppsUsageCard'; const appRoot = mockAppRoot().withTranslations('en', 'core', { Apps_InfoText_limited: 'Community workspaces can enable up to {{marketplaceAppsMaxCount}} marketplace apps. Private apps can only be enabled in <1>premium plans.', - Apps_InfoText: - 'Community allows up to {{privateAppsMaxCount}} private apps and {{marketplaceAppsMaxCount}} marketplace apps to be enabled', }); it('should render a skeleton if no data', () => { @@ -80,5 +78,7 @@ it('should render a full progress bar with private apps disabled', async () => { await userEvent.click(screen.getByRole('button', { name: 'Click_here_for_more_info' })); - expect(screen.getByText('Community allows up to 0 private apps and 5 marketplace apps to be enabled')).toBeInTheDocument(); + expect( + screen.getByText('Community workspaces can enable up to 5 marketplace apps. Private apps can only be enabled in premium plans.'), + ).toBeInTheDocument(); }); diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx index aa8e91c0f8575..c23411fe492ea 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard/AppsUsageCard.tsx @@ -40,18 +40,16 @@ const AppsUsageCard = ({ privateAppsLimit, marketplaceAppsLimit }: AppsUsageCard const card: CardProps = { title: t('Apps'), - infoText: - privateAppsCount > 0 ? ( - - Community workspaces can enable up to {{ marketplaceAppsMaxCount }} marketplace apps. Private apps can only be enabled in{' '} - - premium plans - - . - - ) : ( - t('Apps_InfoText', { privateAppsMaxCount, marketplaceAppsMaxCount }) - ), + infoText: ( + + Community workspaces can enable up to {{ marketplaceAppsMaxCount }} marketplace apps. Private apps can only be enabled in{' '} + + premium plans + + . + + ), + ...(marketplaceAppsAboveWarning && { upgradeButton: ( diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsFilters.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsFilters.tsx index a1f486395dbba..1a31d474134df 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsFilters.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsFilters.tsx @@ -59,7 +59,7 @@ const AppsFilters = ({ const fixFiltersSize = breakpoints.includes('lg') ? { maxWidth: 'x200', minWidth: 'x200' } : null; return ( - + {!isPrivateAppsPage && ( diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx index e3ae7d0e31974..213fa2570f3ca 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyStateUpgrade.tsx @@ -1,26 +1,35 @@ -import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions } from '@rocket.chat/fuselage'; +import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions, Button } from '@rocket.chat/fuselage'; import { usePermission } from '@rocket.chat/ui-contexts'; import React from 'react'; import { useTranslation } from 'react-i18next'; +import { useExternalLink } from '../../../hooks/useExternalLink'; import UpgradeButton from '../../admin/subscription/components/UpgradeButton'; +import { PRICING_LINK } from '../../admin/subscription/utils/links'; const PrivateEmptyStateUpgrade = () => { const { t } = useTranslation(); const isAdmin = usePermission('manage-apps'); + const handleOpenLink = useExternalLink(); + return ( {t('Private_apps_upgrade_empty_state_title')} {t('Private_apps_upgrade_empty_state_description')} - {isAdmin && ( - + + {isAdmin && ( {t('Upgrade')} - - )} + )} + {!isAdmin && ( + + )} + ); }; diff --git a/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx b/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx index 13d71cc2108b1..aa05e8851036a 100644 --- a/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx +++ b/apps/meteor/client/views/marketplace/components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal.tsx @@ -1,6 +1,6 @@ import { Button, Modal } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import MarkdownText from '../../../../components/MarkdownText'; import type { MarketplaceRouteContext } from '../../hooks/useAppsCountQuery'; @@ -15,7 +15,7 @@ type UninstallGrandfatheredAppModalProps = { }; const UninstallGrandfatheredAppModal = ({ context, limit, appName, handleUninstall, handleClose }: UninstallGrandfatheredAppModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const privateAppsEnabled = usePrivateAppsEnabled(); const modalContent = @@ -35,6 +35,12 @@ const UninstallGrandfatheredAppModal = ({ context, limit, appName, handleUninsta + + {/* TODO: Move the link to a go link when available */} + + {t('Learn_more')} + +