diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index e13518b4e83..8233b364690 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -41,7 +41,7 @@ type GenericCloseRoomParams = { }; export type CloseRoomParamsByUser = { - user: IUser; + user: IUser | null; } & GenericCloseRoomParams; export type CloseRoomParamsByVisitor = { @@ -90,11 +90,11 @@ class LivechatClass { let chatCloser: any; if (isRoomClosedByUserParams(params)) { const { user } = params; - this.logger.debug(`Closing by user ${user._id}`); + this.logger.debug(`Closing by user ${user?._id}`); closeData.closer = 'user'; closeData.closedBy = { - _id: user._id, - username: user.username, + _id: user?._id || '', + username: user?.username, }; chatCloser = user; } else if (isRoomClosedByVisitorParams(params)) { 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 f13b0289802..52bfe571ef2 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts @@ -5,7 +5,7 @@ import { Random } from '@rocket.chat/random'; import { Accounts } from 'meteor/accounts-base'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { escapeRegExp, escapeHTML } from '@rocket.chat/string-helpers'; -import type { IUser, IIncomingMessage, ILoginToken } from '@rocket.chat/core-typings'; +import type { IUser, IIncomingMessage, IPersonalAccessToken } from '@rocket.chat/core-typings'; import { CredentialTokens, Rooms, Users } from '@rocket.chat/models'; import { settings } from '../../../settings/server'; @@ -175,7 +175,7 @@ export class SAML { const stampedToken = Accounts._generateStampedLoginToken(); await Users.addPersonalAccessTokenToUser({ userId: user._id, - loginTokenObject: stampedToken as unknown as ILoginToken, + loginTokenObject: stampedToken as unknown as IPersonalAccessToken, }); const updateData: Record = { diff --git a/apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx b/apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx index 0b6ea0c9bf0..c24e4f34665 100644 --- a/apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx +++ b/apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx @@ -15,7 +15,7 @@ const hoverStyle = css` `; const ReadReceiptRow = ({ user, ts }: ReadReceipt): ReactElement => { - const displayName = useUserDisplayName(user); + const displayName = useUserDisplayName(user || {}); const formatDateAndTime = useFormatDateAndTime({ withSeconds: true }); return ( @@ -30,7 +30,7 @@ const ReadReceiptRow = ({ user, ts }: ReadReceipt): ReactElement => { className={hoverStyle} > - + {displayName} diff --git a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts index f362fb643a1..cf740cdc443 100644 --- a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts +++ b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts @@ -1,11 +1,10 @@ import get from 'lodash.get'; import type { IMessage, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { LivechatVisitors, Rooms } from '@rocket.chat/models'; +import { LivechatVisitors, Rooms, Users } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; import { callbacks } from '../../../../../lib/callbacks'; -import { Users } from '../../../../../app/models/server'; const placeholderFields = { 'contact.name': { @@ -44,8 +43,11 @@ const handleBeforeSaveMessage = async (message: IMessage, room?: IOmnichannelRoo let messageText = message.msg; const agentId = room?.servedBy?._id; + if (!agentId) { + return message; + } const visitorId = room?.v?._id; - const agent = Users.findOneById(agentId, { fields: { name: 1, _id: 1, emails: 1 } }) || {}; + const agent = (await Users.findOneById(agentId, { projection: { name: 1, _id: 1, emails: 1 } })) || {}; const visitor = visitorId && ((await LivechatVisitors.findOneById(visitorId, {})) || {}); Object.keys(placeholderFields).map((field) => { diff --git a/apps/meteor/ee/app/canned-responses/server/methods/saveCannedResponse.ts b/apps/meteor/ee/app/canned-responses/server/methods/saveCannedResponse.ts index b12e8192ec8..0c6d51f0fa2 100644 --- a/apps/meteor/ee/app/canned-responses/server/methods/saveCannedResponse.ts +++ b/apps/meteor/ee/app/canned-responses/server/methods/saveCannedResponse.ts @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; -import { LivechatDepartment, CannedResponse } from '@rocket.chat/models'; +import { LivechatDepartment, CannedResponse, Users } from '@rocket.chat/models'; import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; -import { Users } from '../../../../../app/models/server'; import notifications from '../../../../../app/notifications/server/lib/Notifications'; declare module '@rocket.chat/ui-contexts' { @@ -95,12 +94,12 @@ Meteor.methods({ result = await CannedResponse.updateCannedResponse(_id, { ...responseData, createdBy: cannedResponse.createdBy }); } else { - const user = Users.findOneById(Meteor.userId()); + const user = await Users.findOneById(userId); const data = { ...responseData, - ...(responseData.scope === 'user' && { userId: user._id }), - createdBy: { _id: user._id, username: user.username }, + ...(responseData.scope === 'user' && { userId: user?._id }), + createdBy: { _id: user?._id || '', username: user?.username || '' }, _createdAt: new Date(), }; diff --git a/apps/meteor/ee/app/license/server/getSeatsRequestLink.ts b/apps/meteor/ee/app/license/server/getSeatsRequestLink.ts index b911f011372..4394723370a 100644 --- a/apps/meteor/ee/app/license/server/getSeatsRequestLink.ts +++ b/apps/meteor/ee/app/license/server/getSeatsRequestLink.ts @@ -1,7 +1,5 @@ import type { ISetting } from '@rocket.chat/core-typings'; -import { Settings } from '@rocket.chat/models'; - -import { Users } from '../../../../app/models/server'; +import { Settings, Users } from '@rocket.chat/models'; type WizardSettings = Array; @@ -9,7 +7,7 @@ const url = 'https://go.rocket.chat/i/seats-cap-upgrade'; export const getSeatsRequestLink = async (): Promise => { const workspaceId = await Settings.findOneById('Cloud_Workspace_Id'); - const activeUsers = Users.getActiveLocalUserCount(); + const activeUsers = await Users.getActiveLocalUserCount(); const wizardSettings: WizardSettings = await Settings.findSetupWizardSettings().toArray(); const newUrl = new URL(url); diff --git a/apps/meteor/ee/app/license/server/license.ts b/apps/meteor/ee/app/license/server/license.ts index 3066bcf0f4c..3055e6d2f86 100644 --- a/apps/meteor/ee/app/license/server/license.ts +++ b/apps/meteor/ee/app/license/server/license.ts @@ -2,8 +2,8 @@ import { EventEmitter } from 'events'; import { Apps } from '@rocket.chat/core-services'; import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; +import { Users } from '@rocket.chat/models'; -import { Users } from '../../../../app/models/server'; import type { BundleFeature } from './bundles'; import { getBundleModules, isBundle, getBundleFromModule } from './bundles'; import decrypt from './decrypt'; @@ -211,12 +211,12 @@ class LicenseClass { this.showLicenses(); } - canAddNewUser(): boolean { + async canAddNewUser(): Promise { if (!maxActiveUsers) { return true; } - return maxActiveUsers > Users.getActiveLocalUserCount(); + return maxActiveUsers > (await Users.getActiveLocalUserCount()); } async canEnableApp(source: LicenseAppSources): Promise { @@ -330,7 +330,7 @@ export function getAppsConfig(): NonNullable { return License.getAppsConfig(); } -export function canAddNewUser(): boolean { +export async function canAddNewUser(): Promise { return License.canAddNewUser(); } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts index 75d3aa31b0d..896e1c3c298 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -39,13 +39,13 @@ const getAgentIdsToHandle = async (businessHour: Record): Promise): Promise => { const agentIds: string[] = await getAgentIdsToHandle(businessHour); await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); - Users.updateLivechatStatusBasedOnBusinessHours(); + await Users.updateLivechatStatusBasedOnBusinessHours(); }; export const closeBusinessHour = async (businessHour: Record): Promise => { const agentIds: string[] = await getAgentIdsToHandle(businessHour); await Users.removeBusinessHourByAgentIds(agentIds, businessHour._id); - Users.updateLivechatStatusBasedOnBusinessHours(); + await Users.updateLivechatStatusBasedOnBusinessHours(); }; export const removeBusinessHourByAgentIds = async (agentIds: string[], businessHourId: string): Promise => { @@ -53,7 +53,7 @@ export const removeBusinessHourByAgentIds = async (agentIds: string[], businessH return; } await Users.removeBusinessHourByAgentIds(agentIds, businessHourId); - Users.updateLivechatStatusBasedOnBusinessHours(); + await Users.updateLivechatStatusBasedOnBusinessHours(); }; export const resetDefaultBusinessHourIfNeeded = async (): Promise => { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts index 842d4b065e5..78097cd88eb 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts @@ -1,14 +1,14 @@ -import { LivechatVisitors, LivechatInquiry, LivechatRooms } from '@rocket.chat/models'; +import { LivechatVisitors, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; +import type { IUser } from '@rocket.chat/core-typings'; import { callbacks } from '../../../../../lib/callbacks'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { settings } from '../../../../../app/settings/server'; -import { Users } from '../../../../../app/models/server'; let contactManagerPreferred = false; let lastChattedAgentPreferred = false; -const normalizeDefaultAgent = (agent: { _id: string; username: string }) => { +const normalizeDefaultAgent = (agent?: Pick | null) => { if (!agent) { return; } @@ -17,8 +17,9 @@ const normalizeDefaultAgent = (agent: { _id: string; username: string }) => { return { agentId, username }; }; -const getDefaultAgent = async (username: string) => - username && normalizeDefaultAgent(await Users.findOneOnlineAgentByUserList(username, { fields: { _id: 1, username: 1 } })); +const getDefaultAgent = async (username?: string) => + username !== undefined && + normalizeDefaultAgent(await Users.findOneOnlineAgentByUserList(username, { projection: { _id: 1, username: 1 } })); settings.watch('Livechat_last_chatted_agent_routing', function (value) { lastChattedAgentPreferred = value; @@ -117,7 +118,12 @@ callbacks.add( const { servedBy: { username: usernameByRoom }, } = room; - const lastRoomAgent = normalizeDefaultAgent(Users.findOneOnlineAgentByUserList(usernameByRoom, { fields: { _id: 1, username: 1 } })); + if (!usernameByRoom) { + return defaultAgent; + } + const lastRoomAgent = normalizeDefaultAgent( + await Users.findOneOnlineAgentByUserList(usernameByRoom, { projection: { _id: 1, username: 1 } }), + ); return lastRoomAgent ?? defaultAgent; }, callbacks.priority.MEDIUM, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoTransferChatScheduler.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoTransferChatScheduler.ts index de705c43c34..c81634f2ff3 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoTransferChatScheduler.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoTransferChatScheduler.ts @@ -1,16 +1,14 @@ import { Agenda } from '@rocket.chat/agenda'; import { MongoInternals } from 'meteor/mongo'; import { Meteor } from 'meteor/meteor'; -import { LivechatRooms } from '@rocket.chat/models'; +import { LivechatRooms, Users } from '@rocket.chat/models'; import type { IUser } from '@rocket.chat/core-typings'; -import { Users } from '../../../../../app/models/server'; import { Livechat } from '../../../../../app/livechat/server'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { forwardRoomToAgent } from '../../../../../app/livechat/server/lib/Helper'; import { settings } from '../../../../../app/settings/server'; -const schedulerUser = Users.findOneById('rocket.cat'); const SCHEDULER_NAME = 'omnichannel_scheduler'; class AutoTransferChatSchedulerClass { @@ -35,6 +33,10 @@ class AutoTransferChatSchedulerClass { this.running = true; } + private async getSchedulerUser(): Promise { + return Users.findOneById('rocket.cat'); + } + public async scheduleRoom(roomId: string, timeout: number): Promise { await this.unscheduleRoom(roomId); @@ -77,7 +79,7 @@ class AutoTransferChatSchedulerClass { return Livechat.returnRoomAsInquiry(room._id, departmentId, { scope: 'autoTransferUnansweredChatsToQueue', comment: timeoutDuration, - transferredBy: schedulerUser, + transferredBy: await this.getSchedulerUser(), }); } @@ -85,7 +87,7 @@ class AutoTransferChatSchedulerClass { if (agent) { return forwardRoomToAgent(room, { userId: agent.agentId, - transferredBy: schedulerUser, + transferredBy: await this.getSchedulerUser(), transferredTo: agent, scope: 'autoTransferUnansweredChatsToAgent', comment: timeoutDuration, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js index 4f189bce12a..553e750355e 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js @@ -6,11 +6,11 @@ import { LivechatDepartment as LivechatDepartmentRaw, LivechatCustomField, LivechatInquiry, + Users, } from '@rocket.chat/models'; import { api } from '@rocket.chat/core-services'; import { memoizeDebounce } from './debounceByParams'; -import { Users } from '../../../../../app/models/server'; import { settings } from '../../../../../app/settings/server'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; @@ -29,7 +29,7 @@ export const getMaxNumberSimultaneousChat = async ({ agentId, departmentId }) => } if (agentId) { - const user = Users.getAgentInfo(agentId); + const user = await Users.getAgentInfo(agentId); const { livechat: { maxNumberSimultaneousChat } = {} } = user || {}; if (maxNumberSimultaneousChat > 0) { return maxNumberSimultaneousChat; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js index 98e0742b3fb..5d6cf1bc9e8 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js @@ -29,7 +29,7 @@ export const LivechatEnterprise = { async addMonitor(username) { check(username, String); - const user = await Users.findOneByUsername(username, { fields: { _id: 1, username: 1 } }); + const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { @@ -47,7 +47,7 @@ export const LivechatEnterprise = { async removeMonitor(username) { check(username, String); - const user = await Users.findOneByUsername(username, { fields: { _id: 1 } }); + const user = await Users.findOneByUsername(username, { projection: { _id: 1 } }); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts index d4ba1f68758..9a827a16c7c 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts @@ -4,11 +4,10 @@ import { MongoInternals } from 'meteor/mongo'; import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import type { IUser, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { LivechatRooms, LivechatInquiry as LivechatInquiryRaw } from '@rocket.chat/models'; +import { LivechatRooms, LivechatInquiry as LivechatInquiryRaw, Users } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; import { Logger } from '../../../../../app/logger/server'; -import { Users } from '../../../../../app/models/server'; import { Livechat } from '../../../../../app/livechat/server/lib/LivechatTyped'; const SCHEDULER_NAME = 'omnichannel_queue_inactivity_monitor'; @@ -41,12 +40,15 @@ class OmnichannelQueueInactivityMonitorClass { defaultConcurrency: 1, }); this.createIndex(); - this.user = Users.findOneById('rocket.cat'); const language = settings.get('Language') || 'en'; this.message = TAPi18n.__('Closed_automatically_chat_queued_too_long', { lng: language }); this.bindedCloseRoom = Meteor.bindEnvironment(this.closeRoom.bind(this)); } + private async getRocketCatUser(): Promise { + return Users.findOneById('rocket.cat'); + } + getName(inquiryId: string): string { return `${this._name}-${inquiryId}`; } @@ -93,12 +95,12 @@ class OmnichannelQueueInactivityMonitorClass { await this.scheduler.cancel({ name }); } - closeRoomAction(room: IOmnichannelRoom): Promise { + async closeRoomAction(room: IOmnichannelRoom): Promise { const comment = this.message; return Livechat.closeRoom({ comment, room, - user: this.user, + user: await this.getRocketCatUser(), }); } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts index f90c4f2ba6f..15d3f20a117 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import type { ILivechatVisitor } from '@rocket.chat/core-typings'; -import { LivechatVisitors, LivechatInquiry, LivechatRooms } from '@rocket.chat/models'; +import { LivechatVisitors, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; import { Message } from '@rocket.chat/core-services'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; -import { Users } from '../../../../../app/models/server'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { callbacks } from '../../../../../lib/callbacks'; @@ -62,7 +61,10 @@ Meteor.methods({ const { servedBy: { _id: agentId, username } = {} } = room; await RoutingManager.takeInquiry(inquiry, { agentId, username }, options); - const onHoldChatResumedBy = options.clientAction ? await Meteor.userAsync() : Users.findOneById('rocket.cat'); + const onHoldChatResumedBy = options.clientAction ? await Meteor.userAsync() : await Users.findOneById('rocket.cat'); + if (!onHoldChatResumedBy) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'livechat:resumeOnHold' }); + } const comment = await resolveOnHoldCommentInfo(options, room, onHoldChatResumedBy); diff --git a/apps/meteor/ee/server/api/licenses.ts b/apps/meteor/ee/server/api/licenses.ts index 61403d20511..b401bd3982f 100644 --- a/apps/meteor/ee/server/api/licenses.ts +++ b/apps/meteor/ee/server/api/licenses.ts @@ -1,8 +1,7 @@ import { check } from 'meteor/check'; -import { Settings } from '@rocket.chat/models'; +import { Settings, Users } from '@rocket.chat/models'; import { getLicenses, validateFormat, flatModules, getMaxActiveUsers, isEnterprise } from '../../app/license/server/license'; -import { Users } from '../../../app/models/server'; import { API } from '../../../app/api/server/api'; import { hasPermissionAsync } from '../../../app/authorization/server/functions/hasPermission'; import type { ILicense } from '../../app/license/definition/ILicense'; @@ -61,9 +60,9 @@ API.v1.addRoute( 'licenses.maxActiveUsers', { authRequired: true }, { - get() { + async get() { const maxActiveUsers = getMaxActiveUsers() || null; - const activeUsers = Users.getActiveLocalUserCount(); + const activeUsers = await Users.getActiveLocalUserCount(); return API.v1.success({ maxActiveUsers, activeUsers }); }, diff --git a/apps/meteor/ee/server/apps/communication/rest.js b/apps/meteor/ee/server/apps/communication/rest.js index b50b84e4bef..672a01d8548 100644 --- a/apps/meteor/ee/server/apps/communication/rest.js +++ b/apps/meteor/ee/server/apps/communication/rest.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; -import { Settings, Users as UsersRaw } from '@rocket.chat/models'; +import { Settings, Users } from '@rocket.chat/models'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { AppStatus, AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; @@ -9,7 +9,6 @@ import { getUploadFormData } from '../../../../app/api/server/lib/getUploadFormD import { getWorkspaceAccessToken, getWorkspaceAccessTokenWithScope } from '../../../../app/cloud/server'; import { settings } from '../../../../app/settings/server'; import { Info } from '../../../../app/utils/server'; -import { Users } from '../../../../app/models/server'; import { Apps } from '../orchestrator'; import { formatAppInstanceForRest } from '../../../lib/misc/formatAppInstanceForRest'; import { actionButtonsHandler } from './endpoints/actionButtonsHandler'; @@ -183,7 +182,7 @@ export class AppsRestApi { const subscribeRoute = this.queryParams.details === 'true' ? 'subscribe/details' : 'subscribe'; - const seats = Users.getActiveLocalUserCount(); + const seats = await Users.getActiveLocalUserCount(); return API.v1.success({ url: `${baseUrl}/apps/${this.queryParams.appId}/${ @@ -287,7 +286,7 @@ export class AppsRestApi { const subscribeRoute = this.queryParams.details === 'true' ? 'subscribe/details' : 'subscribe'; - const seats = Users.getActiveLocalUserCount(); + const seats = await Users.getActiveLocalUserCount(); return API.v1.success({ url: `${baseUrl}/apps/${this.queryParams.appId}/${ @@ -442,7 +441,7 @@ export class AppsRestApi { let admins = []; try { - const adminsRaw = await UsersRaw.findUsersInRoles('admin', undefined, { + const adminsRaw = await Users.findUsersInRoles('admin', undefined, { projection: { username: 1, name: 1, diff --git a/apps/meteor/ee/server/configuration/saml.ts b/apps/meteor/ee/server/configuration/saml.ts index 15492766f77..1e887d24d77 100644 --- a/apps/meteor/ee/server/configuration/saml.ts +++ b/apps/meteor/ee/server/configuration/saml.ts @@ -1,11 +1,10 @@ -import { Roles } from '@rocket.chat/models'; +import { Roles, Users } from '@rocket.chat/models'; import { onLicense } from '../../app/license/server'; import type { ISAMLUser } from '../../../app/meteor-accounts-saml/server/definition/ISAMLUser'; import { SAMLUtils } from '../../../app/meteor-accounts-saml/server/lib/Utils'; import { settings } from '../../../app/settings/server'; import { addSettings } from '../settings/saml'; -import { Users } from '../../../app/models/server'; import { ensureArray } from '../../../lib/utils/arrayUtils'; onLicense('saml-enterprise', () => { @@ -45,7 +44,7 @@ onLicense('saml-enterprise', () => { }); }); - SAMLUtils.events.on('updateCustomFields', (loginResult: Record, updatedUser: { userId: string; token: string }) => { + SAMLUtils.events.on('updateCustomFields', async (loginResult: Record, updatedUser: { userId: string; token: string }) => { const userDataCustomFieldMap = settings.get('SAML_Custom_Default_user_data_custom_fieldmap') as string; const customMap: Record = JSON.parse(userDataCustomFieldMap); @@ -63,7 +62,7 @@ onLicense('saml-enterprise', () => { customFieldsList[customAttribute] = value; } - Users.updateCustomFieldsById(updatedUser.userId, customFieldsList); + await Users.updateCustomFieldsById(updatedUser.userId, customFieldsList); }); }); diff --git a/apps/meteor/ee/server/lib/audit/methods.ts b/apps/meteor/ee/server/lib/audit/methods.ts index 1dc648a18d8..24f678f6a81 100644 --- a/apps/meteor/ee/server/lib/audit/methods.ts +++ b/apps/meteor/ee/server/lib/audit/methods.ts @@ -6,19 +6,20 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { ILivechatAgent, ILivechatVisitor, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import type { Filter } from 'mongodb'; -import { LivechatRooms, Messages, Rooms } from '@rocket.chat/models'; +import { LivechatRooms, Messages, Rooms, Users } from '@rocket.chat/models'; import AuditLog from './AuditLog'; -import { Users } from '../../../../app/models/server'; import { hasPermissionAsync } from '../../../../app/authorization/server/functions/hasPermission'; import { updateCounter } from '../../../../app/statistics/server'; import type { IAuditLog } from '../../../definition/IAuditLog'; +import { isTruthy } from '../../../../lib/isTruthy'; const getValue = (room: IRoom | null) => room && { rids: [room._id], name: room.name }; -const getUsersIdFromUserName = (usernames: IUser['username'][]) => { - const user: IUser[] = usernames ? Users.findByUsername({ $in: usernames }) : undefined; - return user.map((userId) => userId._id); +const getUsersIdFromUserName = async (usernames: IUser['username'][]) => { + const users = usernames ? await Users.findByUsernames(usernames.filter(isTruthy)).toArray() : undefined; + + return users?.filter(isTruthy).map((userId) => userId._id); }; const getRoomInfoByAuditParams = async ({ @@ -138,7 +139,7 @@ Meteor.methods({ }; if (type === 'u') { - const usersId = getUsersIdFromUserName(usernames); + const usersId = await getUsersIdFromUserName(usernames); query['u._id'] = { $in: usersId }; } else { const roomInfo = await getRoomInfoByAuditParams({ type, roomId: rid, users: usernames, visitor, agent }); diff --git a/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js b/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js index 53301042bab..b4ae01403c9 100644 --- a/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js +++ b/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js @@ -1,7 +1,6 @@ import { Random } from '@rocket.chat/random'; -import { LivechatVisitors, ReadReceipts, Messages, Rooms, Subscriptions } from '@rocket.chat/models'; +import { LivechatVisitors, ReadReceipts, Messages, Rooms, Subscriptions, Users } from '@rocket.chat/models'; -import { Users } from '../../../../app/models/server'; import { settings } from '../../../../app/settings/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -116,7 +115,7 @@ export const ReadReceipt = { ...receipt, user: receipt.token ? await LivechatVisitors.getVisitorByToken(receipt.token, { projection: { username: 1, name: 1 } }) - : Users.findOneById(receipt.userId, { fields: { username: 1, name: 1 } }), + : await Users.findOneById(receipt.userId, { projection: { username: 1, name: 1 } }), })), ); }, diff --git a/apps/meteor/ee/server/lib/syncUserRoles.ts b/apps/meteor/ee/server/lib/syncUserRoles.ts index dc1ffa74da5..542c73f9590 100644 --- a/apps/meteor/ee/server/lib/syncUserRoles.ts +++ b/apps/meteor/ee/server/lib/syncUserRoles.ts @@ -68,7 +68,7 @@ export async function syncUserRoles( } const wasGuest = existingRoles.length === 1 && existingRoles[0] === 'guest'; - if (wasGuest && !canAddNewUser()) { + if (wasGuest && !(await canAddNewUser())) { throw new Error('error-license-user-limit-reached'); } diff --git a/apps/meteor/ee/server/startup/seatsCap.ts b/apps/meteor/ee/server/startup/seatsCap.ts index 2d227c83975..fcb64378050 100644 --- a/apps/meteor/ee/server/startup/seatsCap.ts +++ b/apps/meteor/ee/server/startup/seatsCap.ts @@ -16,12 +16,12 @@ import { validateUserRoles } from '../../app/authorization/server/validateUserRo callbacks.add( 'onCreateUser', - ({ isGuest }: { isGuest: boolean }) => { + async ({ isGuest }: { isGuest: boolean }) => { if (isGuest) { return; } - if (!canAddNewUser()) { + if (!(await canAddNewUser())) { throw new Meteor.Error('error-license-user-limit-reached', TAPi18n.__('error-license-user-limit-reached')); } }, @@ -31,7 +31,7 @@ callbacks.add( callbacks.add( 'beforeActivateUser', - (user: IUser) => { + async (user: IUser) => { if (user.roles.length === 1 && user.roles.includes('guest')) { return; } @@ -40,7 +40,7 @@ callbacks.add( return; } - if (!canAddNewUser()) { + if (!(await canAddNewUser())) { throw new Meteor.Error('error-license-user-limit-reached', TAPi18n.__('error-license-user-limit-reached')); } }, @@ -71,7 +71,7 @@ callbacks.add( return; } - if (!canAddNewUser()) { + if (!(await canAddNewUser())) { throw new Meteor.Error('error-license-user-limit-reached', TAPi18n.__('error-license-user-limit-reached')); } }, diff --git a/apps/meteor/imports/personal-access-tokens/server/api/methods/generateToken.ts b/apps/meteor/imports/personal-access-tokens/server/api/methods/generateToken.ts index ba772ff1ab7..a7dfc6ab46f 100644 --- a/apps/meteor/imports/personal-access-tokens/server/api/methods/generateToken.ts +++ b/apps/meteor/imports/personal-access-tokens/server/api/methods/generateToken.ts @@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor'; import { Random } from '@rocket.chat/random'; import { Accounts } from 'meteor/accounts-base'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import { Users } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; -import { Users } from '../../../../../app/models/server'; import { twoFactorRequired } from '../../../../../app/2fa/server/twoFactorRequired'; declare module '@rocket.chat/ui-contexts' { @@ -29,7 +29,7 @@ Meteor.methods({ } const token = Random.secret(); - const tokenExist = Users.findPersonalAccessTokenByTokenNameAndUserId({ + const tokenExist = await Users.findPersonalAccessTokenByTokenNameAndUserId({ userId: uid, tokenName, }); @@ -39,7 +39,7 @@ Meteor.methods({ }); } - Users.addPersonalAccessTokenToUser({ + await Users.addPersonalAccessTokenToUser({ userId: uid, loginTokenObject: { hashedToken: Accounts._hashLoginToken(token), diff --git a/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts b/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts index 84964ec19d5..9f840bb3309 100644 --- a/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts +++ b/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts @@ -26,7 +26,7 @@ Meteor.methods({ }); } - const tokenExist = Users.findPersonalAccessTokenByTokenNameAndUserId({ + const tokenExist = await Users.findPersonalAccessTokenByTokenNameAndUserId({ userId: uid, tokenName, }); diff --git a/apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts b/apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts index 22fa837ee7e..7da13fe48ee 100644 --- a/apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts +++ b/apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import { Users } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; -import { Users } from '../../../../../app/models/server'; import { twoFactorRequired } from '../../../../../app/2fa/server/twoFactorRequired'; declare module '@rocket.chat/ui-contexts' { @@ -25,7 +25,7 @@ Meteor.methods({ method: 'personalAccessTokens:removeToken', }); } - const tokenExist = Users.findPersonalAccessTokenByTokenNameAndUserId({ + const tokenExist = await Users.findPersonalAccessTokenByTokenNameAndUserId({ userId: uid, tokenName, }); @@ -34,7 +34,7 @@ Meteor.methods({ method: 'personalAccessTokens:removeToken', }); } - Users.removePersonalAccessTokenOfUser({ + await Users.removePersonalAccessTokenOfUser({ userId: uid, loginTokenObject: { type: 'personalAccessToken', diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts index c521b769379..e0cc5d003d9 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts @@ -3,12 +3,11 @@ import { Match } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { isIMessageInbox } from '@rocket.chat/core-typings'; import type { IEmailInbox, IUser, IMessage, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { Messages, Uploads, LivechatRooms, Rooms } from '@rocket.chat/models'; +import { Messages, Uploads, LivechatRooms, Rooms, Users } from '@rocket.chat/models'; import { callbacks } from '../../../lib/callbacks'; import { FileUpload } from '../../../app/file-upload/server'; import { slashCommands } from '../../../app/utils/server'; -import { Users } from '../../../app/models/server'; import type { Inbox } from './EmailInbox'; import { inboxes } from './EmailInbox'; import { sendMessage } from '../../../app/lib/server/functions/sendMessage'; @@ -17,7 +16,7 @@ import { logger } from './logger'; const livechatQuoteRegExp = /^\[\s\]\(https?:\/\/.+\/live\/.+\?msg=(?.+?)\)\s(?.+)/s; -const user: IUser = Users.findOneById('rocket.cat'); +const getRocketCatUser = async (): Promise => Users.findOneById('rocket.cat'); const language = settings.get('Language') || 'en'; const t = (s: string): string => TAPi18n.__(s, { lng: language }); @@ -36,6 +35,11 @@ const sendErrorReplyMessage = async (error: string, options: any) => { ts: new Date(), }; + const user = await getRocketCatUser(); + if (!user) { + return; + } + return sendMessage(user, message, { _id: options.rid }); }; @@ -51,6 +55,11 @@ const sendSuccessReplyMessage = async (options: any) => { ts: new Date(), }; + const user = await getRocketCatUser(); + if (!user) { + return; + } + return sendMessage(user, message, { _id: options.rid }); }; @@ -184,6 +193,11 @@ callbacks.add( return message; } + const user = await getRocketCatUser(); + if (!user) { + return message; + } + if (message.files?.length && message.u.username !== 'rocket.cat') { await sendMessage( user, diff --git a/apps/meteor/server/lib/dataExport/sendViaEmail.ts b/apps/meteor/server/lib/dataExport/sendViaEmail.ts index 20cae511a30..3d9be0be1bf 100644 --- a/apps/meteor/server/lib/dataExport/sendViaEmail.ts +++ b/apps/meteor/server/lib/dataExport/sendViaEmail.ts @@ -1,9 +1,8 @@ import moment from 'moment'; import type { IMessage, IUser } from '@rocket.chat/core-typings'; -import { Messages } from '@rocket.chat/models'; +import { Messages, Users } from '@rocket.chat/models'; import * as Mailer from '../../../app/mailer/server/api'; -import { Users } from '../../../app/models/server'; import { settings } from '../../../app/settings/server'; import { Message } from '../../../app/ui-utils/server'; import { getMomentLocale } from '../getMomentLocale'; @@ -25,9 +24,11 @@ export async function sendViaEmail( const missing = [...data.toUsers].filter(Boolean); - Users.findUsersByUsernames(data.toUsers, { - fields: { 'username': 1, 'emails.address': 1 }, - }).forEach((user: IUser) => { + ( + await Users.findUsersByUsernames(data.toUsers, { + projection: { 'username': 1, 'emails.address': 1 }, + }).toArray() + ).forEach((user: IUser) => { const emailAddress = user.emails?.[0].address; if (!emailAddress) { diff --git a/apps/meteor/server/lib/dataExport/uploadZipFile.ts b/apps/meteor/server/lib/dataExport/uploadZipFile.ts index befbd4dcd0e..fa810225c2d 100644 --- a/apps/meteor/server/lib/dataExport/uploadZipFile.ts +++ b/apps/meteor/server/lib/dataExport/uploadZipFile.ts @@ -2,15 +2,15 @@ import { createReadStream } from 'fs'; import { open, stat } from 'fs/promises'; import type { IUser } from '@rocket.chat/core-typings'; +import { Users } from '@rocket.chat/models'; -import { Users } from '../../../app/models/server'; import { FileUpload } from '../../../app/file-upload/server'; export const uploadZipFile = async (filePath: string, userId: IUser['_id'], exportType: 'json' | 'html'): Promise => { const contentType = 'application/zip'; const { size } = await stat(filePath); - const user = Users.findOneById(userId); + const user = await Users.findOneById(userId); let userDisplayName = userId; if (user) { userDisplayName = user.name || user.username || userId; diff --git a/apps/meteor/server/lib/resetUserE2EKey.ts b/apps/meteor/server/lib/resetUserE2EKey.ts index 48db5c7e2cd..aabb5502af0 100644 --- a/apps/meteor/server/lib/resetUserE2EKey.ts +++ b/apps/meteor/server/lib/resetUserE2EKey.ts @@ -1,15 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import type { IUser } from '@rocket.chat/core-typings'; -import { Subscriptions, Users as UsersRaw } from '@rocket.chat/models'; +import { Subscriptions, Users } from '@rocket.chat/models'; -import { Users } from '../../app/models/server'; import { settings } from '../../app/settings/server'; import * as Mailer from '../../app/mailer/server/api'; import { isUserIdFederated } from './isUserIdFederated'; -const sendResetNotification = function (uid: string): void { - const user: IUser = Users.findOneById(uid, {}); +const sendResetNotification = async function (uid: string): Promise { + const user = await Users.findOneById(uid, {}); if (!user) { throw new Meteor.Error('invalid-user'); } @@ -60,7 +58,7 @@ const sendResetNotification = function (uid: string): void { export async function resetUserE2EEncriptionKey(uid: string, notifyUser: boolean): Promise { if (notifyUser) { - sendResetNotification(uid); + await sendResetNotification(uid); } const isUserFederated = await isUserIdFederated(uid); @@ -68,11 +66,11 @@ export async function resetUserE2EEncriptionKey(uid: string, notifyUser: boolean throw new Meteor.Error('error-not-allowed', 'Federated Users cant have TOTP', { function: 'resetTOTP' }); } - Users.resetE2EKey(uid); + await Users.resetE2EKey(uid); await Subscriptions.resetUserE2EKey(uid); // Force the user to logout, so that the keys can be generated again - await UsersRaw.unsetLoginTokens(uid); + await Users.unsetLoginTokens(uid); return true; } diff --git a/apps/meteor/server/lib/roles/getRoomRoles.ts b/apps/meteor/server/lib/roles/getRoomRoles.ts index 4b0d9b092ed..b325572831b 100644 --- a/apps/meteor/server/lib/roles/getRoomRoles.ts +++ b/apps/meteor/server/lib/roles/getRoomRoles.ts @@ -1,9 +1,8 @@ import _ from 'underscore'; import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; -import { Roles, Subscriptions } from '@rocket.chat/models'; +import { Roles, Subscriptions, Users } from '@rocket.chat/models'; import { settings } from '../../../app/settings/server'; -import { Users } from '../../../app/models/server'; export async function getRoomRoles(rid: IRoom['_id']): Promise { const options = { @@ -25,9 +24,11 @@ export async function getRoomRoles(rid: IRoom['_id']): Promise if (!useRealName) { return subscriptions; } - return subscriptions.map((subscription) => { - const user = Users.findOneById(subscription.u._id); - subscription.u.name = user?.name; - return subscription; - }); + return Promise.all( + subscriptions.map(async (subscription) => { + const user = await Users.findOneById(subscription.u._id); + subscription.u.name = user?.name; + return subscription; + }), + ); } diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 750a58c03d8..9ec526e7980 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -2000,6 +2000,12 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } + findByUsernames(usernames, options) { + const query = { $in: usernames }; + + return this.find(query, options); + } + findByUsernamesIgnoringCase(usernames, options) { const query = { username: { diff --git a/packages/core-typings/src/ReadReceipt.ts b/packages/core-typings/src/ReadReceipt.ts index 5842b191291..6576264eacc 100644 --- a/packages/core-typings/src/ReadReceipt.ts +++ b/packages/core-typings/src/ReadReceipt.ts @@ -1,3 +1,4 @@ +import type { ILivechatVisitor } from './ILivechatVisitor'; import type { IMessage } from './IMessage/IMessage'; import type { IRoom } from './IRoom'; import type { IUser } from './IUser'; @@ -6,7 +7,7 @@ export type ReadReceipt = { messageId: IMessage['_id']; roomId: IRoom['_id']; ts: Date; - user: Pick; + user: Pick | ILivechatVisitor | null; userId: IUser['_id']; _id: string; }; diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 6d4b887acfe..f10ae0a9e7d 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -1,5 +1,14 @@ import type { Document, UpdateResult, FindCursor, FindOptions, Filter, InsertOneResult, DeleteResult } from 'mongodb'; -import type { IUser, IRole, IRoom, ILivechatAgent, UserStatus, ILoginToken } from '@rocket.chat/core-typings'; +import type { + IUser, + IRole, + IRoom, + ILivechatAgent, + UserStatus, + ILoginToken, + IPersonalAccessToken, + AtLeast, +} from '@rocket.chat/core-typings'; import type { FindPaginated, IBaseModel } from './IBaseModel'; @@ -213,7 +222,7 @@ export interface IUsersModel extends IBaseModel { }[] >; findOneOnlineAgentByUserList( - userList: string[], + userList: string[] | string, options?: FindOptions, isLivechatEnabledWhenAgentIdle?: boolean, ): Promise; @@ -224,8 +233,11 @@ export interface IUsersModel extends IBaseModel { addRoomByUserId(userId: string, rid: string): Promise; removeRoomByRoomIds(rids: string[]): Promise; getLoginTokensByUserId(userId: string): FindCursor; - addPersonalAccessTokenToUser(data: { userId: string; loginTokenObject: ILoginToken }): Promise; - removePersonalAccessTokenOfUser(data: { userId: string; loginTokenObject: ILoginToken }): Promise; + addPersonalAccessTokenToUser(data: { userId: string; loginTokenObject: IPersonalAccessToken }): Promise; + removePersonalAccessTokenOfUser(data: { + userId: string; + loginTokenObject: AtLeast; + }): Promise; findPersonalAccessTokenByTokenNameAndUserId(data: { userId: string; tokenName: string }): Promise; setOperator(userId: string, operator: boolean): Promise; checkOnlineAgents(agentId: string): Promise; @@ -286,6 +298,7 @@ export interface IUsersModel extends IBaseModel { findNotIdUpdatedFrom(userId: string, updatedFrom: Date, options?: FindOptions): FindCursor; findByRoomId(roomId: string, options?: FindOptions): FindCursor; findByUsername(username: string, options?: FindOptions): FindCursor; + findByUsernames(usernames: string[], options?: FindOptions): FindCursor; findByUsernamesIgnoringCase(usernames: string[], options?: FindOptions): FindCursor; findActiveByUserIds(userIds: string[], options?: FindOptions): FindCursor; findActiveLocalGuests(idsExceptions: string[], options?: FindOptions): FindCursor;