RocketChat/Rocket.Chat

View on GitHub
apps/meteor/app/api/server/v1/moderation.ts

Summary

Maintainability
D
2 days
Test Coverage
import type { IModerationReport, IUser, IUserEmail } from '@rocket.chat/core-typings';
import { ModerationReports, Users } from '@rocket.chat/models';
import {
    isReportHistoryProps,
    isArchiveReportProps,
    isReportInfoParams,
    isGetUserReportsParams,
    isModerationReportUserPost,
    isModerationDeleteMsgHistoryParams,
    isReportsByMsgIdParams,
} from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';

import { deleteReportedMessages } from '../../../../server/lib/moderation/deleteReportedMessages';
import { API } from '../api';
import { getPaginationItems } from '../helpers/getPaginationItems';

type ReportMessage = Pick<IModerationReport, '_id' | 'message' | 'ts' | 'room'>;

API.v1.addRoute(
    'moderation.reportsByUsers',
    {
        authRequired: true,
        validateParams: isReportHistoryProps,
        permissionsRequired: ['view-moderation-console'],
    },
    {
        async get() {
            const { latest: _latest, oldest: _oldest, selector = '' } = this.queryParams;

            const { count = 20, offset = 0 } = await getPaginationItems(this.queryParams);
            const { sort } = await this.parseJsonQuery();

            const latest = _latest ? new Date(_latest) : new Date();
            const oldest = _oldest ? new Date(_oldest) : new Date(0);

            const escapedSelector = escapeRegExp(selector);

            const reports = await ModerationReports.findMessageReportsGroupedByUser(latest, oldest, escapedSelector, {
                offset,
                count,
                sort,
            }).toArray();

            if (reports.length === 0) {
                return API.v1.success({
                    reports,
                    count: 0,
                    offset,
                    total: 0,
                });
            }

            const total = await ModerationReports.getTotalUniqueReportedUsers(latest, oldest, escapedSelector, true);

            return API.v1.success({
                reports,
                count: reports.length,
                offset,
                total,
            });
        },
    },
);

API.v1.addRoute(
    'moderation.userReports',
    {
        authRequired: true,
        validateParams: isReportHistoryProps,
        permissionsRequired: ['view-moderation-console'],
    },
    {
        async get() {
            const { latest: _latest, oldest: _oldest, selector = '' } = this.queryParams;

            const { count = 20, offset = 0 } = await getPaginationItems(this.queryParams);

            const { sort } = await this.parseJsonQuery();

            const latest = _latest ? new Date(_latest) : new Date();

            const oldest = _oldest ? new Date(_oldest) : new Date(0);

            const escapedSelector = escapeRegExp(selector);

            const reports = await ModerationReports.findUserReports(latest, oldest, escapedSelector, {
                offset,
                count,
                sort,
            }).toArray();

            if (reports.length === 0) {
                return API.v1.success({
                    reports,
                    count: 0,
                    offset,
                    total: 0,
                });
            }

            const total = await ModerationReports.getTotalUniqueReportedUsers(latest, oldest, escapedSelector);

            const result = {
                reports,
                count: reports.length,
                offset,
                total,
            };
            return API.v1.success(result);
        },
    },
);

API.v1.addRoute(
    'moderation.user.reportedMessages',
    {
        authRequired: true,
        validateParams: isGetUserReportsParams,
        permissionsRequired: ['view-moderation-console'],
    },
    {
        async get() {
            const { userId, selector = '' } = this.queryParams;

            const { sort } = await this.parseJsonQuery();

            const { count = 50, offset = 0 } = await getPaginationItems(this.queryParams);

            const user = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'name'>>(userId, {
                projection: { _id: 1, username: 1, name: 1 },
            });

            const escapedSelector = escapeRegExp(selector);

            const { cursor, totalCount } = ModerationReports.findReportedMessagesByReportedUserId(userId, escapedSelector, {
                offset,
                count,
                sort,
            });

            const [reports, total] = await Promise.all([cursor.toArray(), totalCount]);

            const uniqueMessages: ReportMessage[] = [];
            const visited = new Set<string>();
            for (const report of reports) {
                if (visited.has(report.message._id)) {
                    continue;
                }
                visited.add(report.message._id);
                uniqueMessages.push(report);
            }

            return API.v1.success({
                user,
                messages: uniqueMessages,
                count: reports.length,
                total,
                offset,
            });
        },
    },
);

API.v1.addRoute(
    'moderation.user.reportsByUserId',
    {
        authRequired: true,
        validateParams: isGetUserReportsParams,
        permissionsRequired: ['view-moderation-console'],
    },
    {
        async get() {
            const { userId, selector = '' } = this.queryParams;
            const { sort } = await this.parseJsonQuery();
            const { count = 50, offset = 0 } = await getPaginationItems(this.queryParams);

            const user = await Users.findOneById<IUser>(userId, {
                projection: {
                    _id: 1,
                    username: 1,
                    name: 1,
                    avatarETag: 1,
                    active: 1,
                    roles: 1,
                    emails: 1,
                    createdAt: 1,
                },
            });

            const escapedSelector = escapeRegExp(selector);
            const { cursor, totalCount } = ModerationReports.findUserReportsByReportedUserId(userId, escapedSelector, {
                offset,
                count,
                sort,
            });

            const [reports, total] = await Promise.all([cursor.toArray(), totalCount]);

            const emailSet = new Map<IUserEmail['address'], IUserEmail>();

            reports.forEach((report) => {
                const email = report.reportedUser?.emails?.[0];
                if (email) {
                    emailSet.set(email.address, email);
                }
            });
            if (user) {
                user.emails = Array.from(emailSet.values());
            }

            return API.v1.success({
                user,
                reports,
                count: reports.length,
                total,
                offset,
            });
        },
    },
);

API.v1.addRoute(
    'moderation.user.deleteReportedMessages',
    {
        authRequired: true,
        validateParams: isModerationDeleteMsgHistoryParams,
        permissionsRequired: ['manage-moderation-actions'],
    },
    {
        async post() {
            // TODO change complicated params
            const { userId, reason } = this.bodyParams;

            const sanitizedReason = reason?.trim() ? reason : 'No reason provided';

            const { user: moderator } = this;

            const { count = 50, offset = 0 } = await getPaginationItems(this.queryParams);

            const { cursor, totalCount } = ModerationReports.findReportedMessagesByReportedUserId(userId, '', {
                offset,
                count,
                sort: { ts: -1 },
            });

            const [messages, total] = await Promise.all([cursor.toArray(), totalCount]);

            if (total === 0) {
                return API.v1.failure('No reported messages found for this user.');
            }

            await deleteReportedMessages(
                messages.map((message) => message.message),
                moderator,
            );

            await ModerationReports.hideMessageReportsByUserId(userId, this.userId, sanitizedReason, 'DELETE Messages');

            return API.v1.success();
        },
    },
);

API.v1.addRoute(
    'moderation.dismissReports',
    {
        authRequired: true,
        validateParams: isArchiveReportProps,
        permissionsRequired: ['manage-moderation-actions'],
    },
    {
        async post() {
            const { userId, msgId, reason, action: actionParam } = this.bodyParams;

            if (userId) {
                const report = await ModerationReports.findOne({ 'message.u._id': userId, '_hidden': { $ne: true } }, { projection: { _id: 1 } });
                if (!report) {
                    return API.v1.failure('no-reports-found');
                }
            }

            if (msgId) {
                const report = await ModerationReports.findOne({ 'message._id': msgId, '_hidden': { $ne: true } }, { projection: { _id: 1 } });
                if (!report) {
                    return API.v1.failure('no-reports-found');
                }
            }

            const sanitizedReason: string = reason?.trim() ? reason : 'No reason provided';
            const action: string = actionParam ?? 'None';

            const { userId: moderatorId } = this;

            if (userId) {
                await ModerationReports.hideMessageReportsByUserId(userId, moderatorId, sanitizedReason, action);
            } else {
                await ModerationReports.hideMessageReportsByMessageId(msgId as string, moderatorId, sanitizedReason, action);
            }

            return API.v1.success();
        },
    },
);

API.v1.addRoute(
    'moderation.dismissUserReports',
    {
        authRequired: true,
        validateParams: isArchiveReportProps,
        permissionsRequired: ['manage-moderation-actions'],
    },
    {
        async post() {
            const { userId, reason, action: actionParam } = this.bodyParams;

            if (!userId) {
                return API.v1.failure('error-user-id-param-not-provided');
            }

            const sanitizedReason: string = reason ?? 'No reason provided';
            const action: string = actionParam ?? 'None';

            const { userId: moderatorId } = this;

            await ModerationReports.hideUserReportsByUserId(userId, moderatorId, sanitizedReason, action);

            return API.v1.success();
        },
    },
);

API.v1.addRoute(
    'moderation.reports',
    {
        authRequired: true,
        validateParams: isReportsByMsgIdParams,
        permissionsRequired: ['view-moderation-console'],
    },
    {
        async get() {
            const { msgId } = this.queryParams;

            const { count = 50, offset = 0 } = await getPaginationItems(this.queryParams);
            const { sort } = await this.parseJsonQuery();
            const { selector = '' } = this.queryParams;

            const escapedSelector = escapeRegExp(selector);

            const { cursor, totalCount } = ModerationReports.findReportsByMessageId(msgId, escapedSelector, { count, sort, offset });

            const [reports, total] = await Promise.all([cursor.toArray(), totalCount]);

            return API.v1.success({
                reports,
                count: reports.length,
                offset,
                total,
            });
        },
    },
);

API.v1.addRoute(
    'moderation.reportInfo',
    {
        authRequired: true,
        permissionsRequired: ['view-moderation-console'],
        validateParams: isReportInfoParams,
    },
    {
        async get() {
            const { reportId } = this.queryParams;

            const report = await ModerationReports.findOneById(reportId);

            if (!report) {
                return API.v1.failure('error-report-not-found');
            }

            return API.v1.success({ report });
        },
    },
);

API.v1.addRoute(
    'moderation.reportUser',
    {
        authRequired: true,
        validateParams: isModerationReportUserPost,
    },
    {
        async post() {
            const { userId, description } = this.bodyParams;

            const {
                user: { _id, name, username, createdAt },
            } = this;

            const reportedUser = await Users.findOneById(userId, { projection: { _id: 1, name: 1, username: 1, emails: 1, createdAt: 1 } });

            if (!reportedUser) {
                return API.v1.failure('Invalid user id provided.');
            }

            await ModerationReports.createWithDescriptionAndUser(reportedUser, description, { _id, name, username, createdAt });

            return API.v1.success();
        },
    },
);