Skip to content

Commit

Permalink
chore: not pick room if it is with an unverified contact
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustrb committed Oct 14, 2024
1 parent 81020db commit 1720265
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 52 deletions.
9 changes: 9 additions & 0 deletions apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ API.v1.addRoute(
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room)) {
// TODO: register the error
throw new Error('error-unverified-contact');
}

const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
if (!guest) {
throw new Error('error-invalid-visitor');
Expand Down Expand Up @@ -398,6 +403,10 @@ API.v1.addRoute(
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room)) {
throw new Error('error-unverified-contact');
}

if (!(await canAccessRoomAsync(room, user))) {
throw new Error('error-not-allowed');
}
Expand Down
8 changes: 8 additions & 0 deletions apps/meteor/app/livechat/server/api/v1/videoCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ API.v1.addRoute(
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) {
throw new Error('error-unverified-contact');
}

const webrtcCallingAllowed = rcSettings.get('WebRTC_Enabled') === true && rcSettings.get('Omnichannel_call_provider') === 'WebRTC';
if (!webrtcCallingAllowed) {
throw new Error('webRTC calling not enabled');
Expand Down Expand Up @@ -95,6 +99,10 @@ API.v1.addRoute(
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) {
throw new Error('error-unverified-contact');
}

const call = await Messages.findOneById(callId);
if (!call || call.t !== 'livechat_webrtc_video_call') {
throw new Error('invalid-callId');
Expand Down
13 changes: 12 additions & 1 deletion apps/meteor/app/livechat/server/lib/Contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,21 @@ export async function validateContactManager(contactManagerUserId: string) {
}

export async function unverifyContactChannel(
contact: Pick<ILivechatContact, '_id' | 'channels'>,
contactId: ILivechatContact['_id'],
channelName: string,
visitorId: string,
): Promise<ILivechatContact> {
const contact: Pick<ILivechatContact, '_id' | 'channels'> | null = await LivechatContacts.findOneById(contactId, {
projection: {
_id: 1,
channels: 1,
},
});

if (!contact) {
throw new Error('error-invalid-channel');
}

if (!contact.channels) {
throw new Error('error-invalid-channel');
}
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi
if (
!RoutingManager.getConfig()?.autoAssignAgent ||
!(await Omnichannel.isWithinMACLimit(room)) ||
(await Omnichannel.isUnverifiedContact(room)) ||
(department?.allowReceiveForwardOffline && !(await LivechatTyped.checkOnlineAgents(departmentId)))
) {
logger.debug(`Room ${room._id} will be on department queue`);
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,10 @@ class LivechatClass {
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) {
throw new Error('error-unverified-contact');
}

const { _id, username, name, utcOffset } = user;
const transcriptRequest = {
requestedAt: new Date(),
Expand Down
11 changes: 11 additions & 0 deletions apps/meteor/app/livechat/server/lib/QueueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
import { i18n } from '../../../utils/lib/i18n';
import { unverifyContactChannel } from './Contacts';
import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper';
import { Livechat } from './LivechatTyped';
import { RoutingManager } from './RoutingManager';
Expand Down Expand Up @@ -92,6 +93,12 @@ export class QueueManager {
return;
}

if (inquiry.v.contactId && (await Omnichannel.isUnverifiedContact(room))) {
logger.error({ msg: 'Contact is not verified, not routing inquiry', inquiry });
await Promise.all([unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id), saveQueueInquiry(inquiry)]);
return;
}

const inquiryAgent = await RoutingManager.delegateAgent(defaultAgent, inquiry);
logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`);
await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent);
Expand Down Expand Up @@ -122,6 +129,10 @@ export class QueueManager {
return LivechatInquiryStatus.QUEUED;
}

if (await Omnichannel.isUnverifiedContact(room)) {
return LivechatInquiryStatus.QUEUED;
}

if (RoutingManager.getConfig()?.autoAssignAgent) {
return LivechatInquiryStatus.READY;
}
Expand Down
51 changes: 1 addition & 50 deletions apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@ import type {
SelectedAgent,
InquiryWithAgentInfo,
TransferData,
ILivechatContact,
} from '@rocket.chat/core-typings';
import { LivechatInquiryStatus } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { Logger } from '@rocket.chat/logger';
import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users, LivechatContacts } from '@rocket.chat/models';
import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users } from '@rocket.chat/models';
import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { callbacks } from '../../../../lib/callbacks';
import { notifyOnLivechatInquiryChangedById, notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
import { isSingleContactEnabled, unverifyContactChannel } from './Contacts';
import {
createLivechatSubscription,
dispatchAgentDelegated,
Expand Down Expand Up @@ -241,53 +239,6 @@ export const RoutingManager: Routing = {
return room;
}

if (isSingleContactEnabled() && inquiry.v.contactId) {
const contact = await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'unknown' | 'channels'>>(inquiry.v.contactId, {
projection: {
_id: 1,
unknown: 1,
channels: 1,
},
});

if (!contact) {
logger.debug(`Could not find associated contact with visitor ${inquiry.v._id}`);
return room;
}

if (contact.unknown && settings.get<boolean>('Livechat_Block_Unknown_Contacts')) {
logger.debug(`Contact ${inquiry.v._id} is unknown and Livechat_Block_Unknown_Contacts so we can't handle it over to the queue`);
return room;
}

// Note: this should either be 1 or 0, if he is verified or not, but since we have an array of channels instead of a set there is no strong
// guarantee that is the case :P
const isContactVerified =
(contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []).length > 0;

if (!isContactVerified && settings.get<boolean>('Livechat_Block_Unverified_Contacts')) {
logger.debug(
`Contact ${inquiry.v._id} is not verified and Livechat_Block_Unverified_Contacts is enabled so we can't handle him down to the queue`,
);
return room;
}

const contactVerificationApp = settings.get('Livechat_Contact_Verification_App');
// Note: Non-empty `Livechat_Contact_Verification_App` means the user has a Contact Verification App setup,
// therefore, we must give the app control over the room
if (contactVerificationApp !== '') {
// Note: If it is not `Livechat_Request_Verification_On_First_Contact_Only` it means that even though the contact
// was already verified, we must verify it again in order to handle the livechat conversation down to the queue
if (
!settings.get<boolean>('Livechat_Request_Verification_On_First_Contact_Only') ||
(!isContactVerified && settings.get<boolean>('Livechat_Block_Unverified_Contacts'))
) {
await unverifyContactChannel(contact, room.source.type, inquiry.v._id);
return room;
}
}
}

try {
await callbacks.run('livechat.checkAgentBeforeTakeInquiry', {
agent,
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/methods/returnAsInquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' });
}

if (await Omnichannel.isUnverifiedContact(room)) {
throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:returnAsInquiry' });
}

if (!room.open) {
throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' });
}
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/methods/sendTranscript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:sendTranscript' });
}

if (await Omnichannel.isUnverifiedContact(room)) {
throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:sendTranscript' });
}

return sendTranscript({ token, rid, email, subject, user });
},
});
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/methods/takeInquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export const takeInquiry = async (
throw new Error('error-mac-limit-reached');
}

if (await Omnichannel.isUnverifiedContact(room)) {
throw new Error('error-unverified-contact');
}

const agent = {
agentId: user._id,
username: user.username,
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/methods/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:transfer' });
}

if (await Omnichannel.isUnverifiedContact(room)) {
throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:transfer' });
}

const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, {
projection: { _id: 1 },
});
Expand Down
40 changes: 39 additions & 1 deletion apps/meteor/server/services/omnichannel/service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ServiceClassInternal } from '@rocket.chat/core-services';
import type { IOmnichannelService } from '@rocket.chat/core-services';
import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { AtLeast, ILivechatContact, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { LivechatContacts } from '@rocket.chat/models';
import moment from 'moment';

import { isSingleContactEnabled } from '../../../app/livechat/server/lib/Contacts';
import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped';
import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../app/settings/server';
Expand Down Expand Up @@ -60,4 +62,40 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha
const currentMonth = moment.utc().format('YYYY-MM');
return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts'));
}

async isUnverifiedContact(room: AtLeast<IOmnichannelRoom, 'v'>): Promise<boolean> {
if (!isSingleContactEnabled() || !room.v.contactId) {
return false;
}

const contact = await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'unknown' | 'channels'>>(room.v.contactId, {
projection: {
_id: 1,
unknown: 1,
channels: 1,
},
});

// Sanity check, should never happen
if (!contact) {
return false;
}

if (contact.unknown && settings.get<boolean>('Livechat_Block_Unknown_Contacts')) {
return true;
}

const isContactVerified =
(contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0;

if (!isContactVerified && settings.get<boolean>('Livechat_Block_Unverified_Contacts')) {
return true;
}

if (!settings.get<boolean>('Livechat_Request_Verification_On_First_Contact_Only')) {
return true;
}

return false;
}
}
1 change: 1 addition & 0 deletions packages/core-services/src/types/IOmnichannelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import type { IServiceClass } from './ServiceClass';
export interface IOmnichannelService extends IServiceClass {
getQueueWorker(): IOmnichannelQueue;
isWithinMACLimit(_room: AtLeast<IOmnichannelRoom, 'v'>): Promise<boolean>;
isUnverifiedContact(_room: AtLeast<IOmnichannelRoom, 'v'>): Promise<boolean>;
}

0 comments on commit 1720265

Please sign in to comment.