Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

236 lines
6.2 KiB
TypeScript

import type { ILivechatAgent, ILivechatVisitor, IMessage, IRoom, IUser, IAuditLog } from '@rocket.chat/core-typings';
import { LivechatRooms, Messages, Rooms, Users, AuditLog } from '@rocket.chat/models';
import { escapeRegExp, isTruthy } from '@rocket.chat/tools';
import { Meteor } from 'meteor/meteor';
import type { Filter } from 'mongodb';
import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { callbacks } from '../../../../server/lib/callbacks';
import { i18n } from '../../../../server/lib/i18n';
import { updateCounter } from '../../../../server/lib/statistics';
const getValue = (room: IRoom | null) => room && { rids: [room._id], name: room.name };
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 ({
type,
roomId: rid,
users: usernames,
visitor,
agent,
userId,
}: {
type: string;
roomId: IRoom['_id'];
users: NonNullable<IUser['username']>[];
visitor: ILivechatVisitor['_id'];
agent: ILivechatAgent['_id'];
userId: string;
}) => {
if (rid) {
return getValue(await Rooms.findOne({ _id: rid, abacAttributes: { $exists: false } }));
}
if (type === 'd') {
return getValue(await Rooms.findDirectRoomContainingAllUsernames(usernames));
}
if (type === 'l') {
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}, { userId });
const rooms = await LivechatRooms.findByVisitorIdAndAgentId(
visitor,
agent,
{
projection: { _id: 1 },
},
extraQuery,
).toArray();
return rooms?.length ? { rids: rooms.map(({ _id }) => _id), name: i18n.t('Omnichannel') } : undefined;
}
};
const requireAuditor = async (userId: string | null): Promise<IUser> => {
if (!userId) {
throw new Meteor.Error('Not allowed');
}
const user = await Users.findOneById(userId);
if (!user || !(await hasPermissionAsync(user._id, 'can-audit'))) {
throw new Meteor.Error('Not allowed');
}
return user;
};
type AuditMessagesParams = {
rid?: IRoom['_id'];
startDate: Date;
endDate: Date;
users: NonNullable<IUser['username']>[];
msg: IMessage['msg'];
type: string;
visitor?: ILivechatVisitor['_id'];
agent?: ILivechatAgent['_id'];
};
export const auditGetMessagesMethod = async (userId: string | null, params: AuditMessagesParams): Promise<IMessage[]> => {
const { rid, startDate, endDate, users: usernames, msg, type, visitor, agent } = params;
const user = await requireAuditor(userId);
const userFields = {
_id: user._id,
username: user.username,
...(user.name && { name: user.name }),
...(user.avatarETag && { avatarETag: user.avatarETag }),
};
let rids;
let name;
const query: Filter<IMessage> = {
ts: {
$gt: startDate,
$lt: endDate,
},
};
if (type === 'u') {
const usersId = await getUsersIdFromUserName(usernames);
query['u._id'] = { $in: usersId };
const abacRooms = await Rooms.findAllPrivateRoomsWithAbacAttributes({ projection: { _id: 1 } })
.map((doc) => doc._id)
.toArray();
query.rid = { $nin: abacRooms };
} else {
const roomInfo = await getRoomInfoByAuditParams({
type,
roomId: rid ?? '',
users: usernames,
visitor: visitor ?? '',
agent: agent ?? '',
userId: user._id,
});
if (!roomInfo) {
throw new Meteor.Error(`Room doesn't exist`);
}
rids = roomInfo.rids;
name = roomInfo.name;
query.rid = { $in: rids };
}
if (msg) {
const regex = new RegExp(escapeRegExp(msg).trim(), 'i');
query.msg = regex;
}
const messages = await Messages.find(query).toArray();
await AuditLog.insertOne(
{
ts: new Date(),
results: messages.length,
u: userFields,
fields: { msg, users: usernames, rids, room: name, startDate, endDate, type, visitor, agent },
},
{ ignoreUndefined: true },
);
updateCounter({ settingsId: 'Message_Auditing_Panel_Load_Count' });
return messages;
};
type AuditOmnichannelMessagesParams = {
startDate: Date;
endDate: Date;
users: NonNullable<IUser['username']>[];
msg: IMessage['msg'];
type: string;
visitor?: ILivechatVisitor['_id'];
agent?: ILivechatAgent['_id'];
};
export const auditGetOmnichannelMessagesMethod = async (
userId: string | null,
params: AuditOmnichannelMessagesParams,
): Promise<IMessage[]> => {
const { startDate, endDate, users: usernames, msg, type, visitor, agent } = params;
const user = await requireAuditor(userId);
const userFields = {
_id: user._id,
username: user.username,
...(user.name && { name: user.name }),
...(user.avatarETag && { avatarETag: user.avatarETag }),
};
// No livechat.applyRoomRestrictions here (unlike the type 'l' path in getRoomInfoByAuditParams): this
// mirrors the original DDP auditGetOmnichannelMessages. Access is gated by the can-audit permission and
// audit is intended to span all omnichannel rooms, so unit/visibility restrictions are not applied.
const rooms = await LivechatRooms.findByVisitorIdAndAgentId(visitor, agent, {
projection: { _id: 1 },
}).toArray();
// keep rids an array — `$in: undefined` throws "$in needs an array" when no rooms match
const rids = rooms.map(({ _id }) => _id);
const name = i18n.t('Omnichannel');
const query: Filter<IMessage> = {
rid: { $in: rids },
ts: {
$gt: startDate,
$lt: endDate,
},
};
if (msg) {
const regex = new RegExp(escapeRegExp(msg).trim(), 'i');
query.msg = regex;
}
const messages = await Messages.find(query).toArray();
await AuditLog.insertOne(
{
ts: new Date(),
results: messages.length,
u: userFields,
fields: { msg, users: usernames, rids, room: name, startDate, endDate, type, visitor, agent },
},
{ ignoreUndefined: true },
);
return messages;
};
export const auditGetAuditionsMethod = async (userId: string | null, startDate: Date, endDate: Date): Promise<IAuditLog[]> => {
if (!userId || !(await hasPermissionAsync(userId, 'can-audit-log'))) {
throw new Meteor.Error('Not allowed');
}
return AuditLog.find(
{
ts: {
$gt: startDate,
$lt: endDate,
},
},
{
projection: {
'u.services': 0,
'u.roles': 0,
'u.lastLogin': 0,
'u.statusConnection': 0,
'u.emails': 0,
},
},
).toArray();
};