Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/forty-socks-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@rocket.chat/core-services': minor
'@rocket.chat/model-typings': minor
'@rocket.chat/core-typings': minor
'@rocket.chat/models': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds a new endpoint to delete uploaded files individually
1 change: 1 addition & 0 deletions apps/meteor/app/api/server/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export type TypedOptions = {
} & SharedOptions<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'>;

export type TypedThis<TOptions extends TypedOptions, TPath extends string = ''> = {
readonly logger: Logger;
userId: TOptions['authRequired'] extends true ? string : string | undefined;
user: TOptions['authRequired'] extends true ? IUser : IUser | null;
token: TOptions['authRequired'] extends true ? string : string | undefined;
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/api/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import './v1/email-inbox';
import './v1/mailer';
import './v1/teams';
import './v1/moderation';
import './v1/uploads';

// This has to come last so all endpoints are registered before generating the OpenAPI documentation
import './default/openApi';
Expand Down
99 changes: 99 additions & 0 deletions apps/meteor/app/api/server/v1/uploads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Upload } from '@rocket.chat/core-services';
import type { IUpload } from '@rocket.chat/core-typings';
import { Messages, Uploads, Users } from '@rocket.chat/models';
import {
ajv,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
validateNotFoundErrorResponse,
} from '@rocket.chat/rest-typings';

import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';

type UploadsDeleteResult = {
/**
* The list of files that were successfully removed; May include additional files such as image thumbnails
* */
deletedFiles: IUpload['_id'][];
};

type UploadsDeleteParams = {
fileId: string;
};

const uploadsDeleteParamsSchema = {
type: 'object',
properties: {
fileId: {
type: 'string',
},
},
required: ['fileId'],
additionalProperties: false,
};

export const isUploadsDeleteParams = ajv.compile<UploadsDeleteParams>(uploadsDeleteParamsSchema);

const uploadsDeleteEndpoint = API.v1.post(
'uploads.delete',
{
authRequired: true,
body: isUploadsDeleteParams,
response: {
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
404: validateNotFoundErrorResponse,
200: ajv.compile<UploadsDeleteResult>({
type: 'object',
properties: {
success: {
type: 'boolean',
},
deletedFiles: {
description: 'The list of files that were successfully removed. May include additional files such as image thumbnails',
type: 'array',
items: {
type: 'string',
},
},
},
required: ['deletedFiles'],
additionalProperties: false,
}),
},
},
async function action() {
const { fileId } = this.bodyParams;

const file = await Uploads.findOneById(fileId);
if (!file?.userId || !file.rid) {
return API.v1.notFound();
}

const msg = await Messages.getMessageByFileId(fileId);
if (!(await Upload.canDeleteFile(this.userId, file, msg))) {
return API.v1.forbidden('forbidden');
}

const user = await Users.findOneById(this.userId);
// Safeguard, can't really happen
if (!user) {
return API.v1.forbidden('forbidden');
}

const { deletedFiles } = await Upload.deleteFile(user, fileId, msg);
return API.v1.success({
deletedFiles,
});
},
);

type UploadsEndpoints = ExtractRoutesFromAPI<typeof uploadsDeleteEndpoint>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends UploadsEndpoints {}
}
17 changes: 15 additions & 2 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,14 @@ API.v1.addRoute(

API.v1.addRoute(
'users.updateOwnBasicInfo',
{ authRequired: true, validateParams: isUsersUpdateOwnBasicInfoParamsPOST },
{
authRequired: true,
validateParams: isUsersUpdateOwnBasicInfoParamsPOST,
rateLimiterOptions: {
numRequestsAllowed: 1,
intervalTimeInMS: 60000,
},
},
{
async post() {
const userData = {
Expand Down Expand Up @@ -1374,7 +1381,13 @@ API.v1.addRoute(

API.v1.addRoute(
'users.setStatus',
{ authRequired: true },
{
authRequired: true,
rateLimiterOptions: {
numRequestsAllowed: 5,
intervalTimeInMS: 60000,
},
},
{
async post() {
check(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const elapsedTime = (ts: Date): number => {

export const canDeleteMessageAsync = async (
uid: string,
{ u, rid, ts }: { u: Pick<IUser, '_id' | 'username'>; rid: string; ts: Date },
{ u, rid, ts }: { u: Pick<IUser, '_id' | 'username'>; rid: string; ts?: Date },
): Promise<boolean> => {
const room = await Rooms.findOneById<Pick<IRoom, '_id' | 'ro' | 'unmuted' | 't' | 'teamId' | 'prid'>>(rid, {
projection: {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/crowd/server/crowd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Meteor } from 'meteor/meteor';
import { logger } from './logger';
import { crowdIntervalValuesToCronMap } from '../../../server/settings/crowd';
import { deleteUser } from '../../lib/server/functions/deleteUser';
import { _setRealName } from '../../lib/server/functions/setRealName';
import { setRealName } from '../../lib/server/functions/setRealName';
import { setUserActiveStatus } from '../../lib/server/functions/setUserActiveStatus';
import { notifyOnUserChange, notifyOnUserChangeById, notifyOnUserChangeAsync } from '../../lib/server/lib/notifyListener';
import { settings } from '../../settings/server';
Expand Down Expand Up @@ -206,7 +206,7 @@ export class CROWD {
}

if (crowdUser.displayname) {
await _setRealName(id, crowdUser.displayname);
await setRealName(id, crowdUser.displayname);
}

await Users.updateOne(
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/file-upload/server/methods/sendFileMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const parseFileIntoMessageAttachments = async (
image_url: fileUrl,
image_type: file.type as string,
image_size: file.size,
fileId: file._id,
};

if (file.identify?.size) {
Expand Down Expand Up @@ -115,6 +116,7 @@ export const parseFileIntoMessageAttachments = async (
audio_url: fileUrl,
audio_type: file.type as string,
audio_size: file.size,
fileId: file._id,
};
attachments.push(attachment);
} else if (/^video\/.+/.test(file.type as string)) {
Expand All @@ -127,6 +129,7 @@ export const parseFileIntoMessageAttachments = async (
video_url: fileUrl,
video_type: file.type as string,
video_size: file.size as number,
fileId: file._id,
};
attachments.push(attachment);
} else {
Expand All @@ -138,6 +141,7 @@ export const parseFileIntoMessageAttachments = async (
title_link: fileUrl,
title_link_download: true,
size: file.size as number,
fileId: file._id,
};
attachments.push(attachment);
}
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { IRoom } from '@rocket.chat/core-typings';
import { Messages, Rooms, Subscriptions, ReadReceipts, Users } from '@rocket.chat/models';

import { deleteRoom } from './deleteRoom';
import { NOTIFICATION_ATTACHMENT_COLOR } from '../../../../lib/constants';
import { i18n } from '../../../../server/lib/i18n';
import { FileUpload } from '../../../file-upload/server';
import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
Expand Down Expand Up @@ -47,7 +48,7 @@ export async function cleanRoomHistory({
});

const targetMessageIdsForAttachmentRemoval = new Set<string>();
const pruneMessageAttachment = { color: '#FD745E', text };
const pruneMessageAttachment = { color: NOTIFICATION_ATTACHMENT_COLOR, text };

async function performFileAttachmentCleanupBatch() {
if (targetMessageIdsForAttachmentRemoval.size === 0) return;
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/lib/server/functions/saveUserIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Updater } from '@rocket.chat/models';
import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptions, Users, CallHistory } from '@rocket.chat/models';
import type { ClientSession } from 'mongodb';

import { _setRealName } from './setRealName';
import { setRealName } from './setRealName';
import { _setUsername } from './setUsername';
import { updateGroupDMsName } from './updateGroupDMsName';
import { validateName } from './validateName';
Expand Down Expand Up @@ -65,7 +65,7 @@ export async function saveUserIdentity({
}

if (typeof rawName !== 'undefined' && nameChanged) {
if (!(await _setRealName(_id, name, user, updater, session))) {
if (!(await setRealName(_id, name, user, updater, session))) {
return false;
}
}
Expand Down
12 changes: 2 additions & 10 deletions apps/meteor/app/lib/server/functions/setEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { Meteor } from 'meteor/meteor';
import type { ClientSession } from 'mongodb';

import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import * as Mailer from '../../../mailer/server/api';
import { settings } from '../../../settings/server';
import { RateLimiter, validateEmailDomain } from '../lib';
import { validateEmailDomain } from '../lib';
import { checkEmailAvailability } from './checkEmailAvailability';
import { sendConfirmationEmail } from '../../../../server/methods/sendConfirmationEmail';

Expand Down Expand Up @@ -42,7 +41,7 @@ const _sendEmailChangeNotification = async function (to: string, newEmail: strin
}
};

const _setEmail = async function (
export const setEmail = async function (
userId: string,
email: string,
shouldSendVerificationEmail = true,
Expand Down Expand Up @@ -105,10 +104,3 @@ const _setEmail = async function (
}
return result;
};

export const setEmail = RateLimiter.limitFunction(_setEmail, 1, 60000, {
async 0() {
const userId = Meteor.userId();
return !userId || !(await hasPermissionAsync(userId, 'edit-other-user-info'));
}, // Administrators have permission to change others emails, so don't limit those
});
12 changes: 1 addition & 11 deletions apps/meteor/app/lib/server/functions/setRealName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import { api } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import type { ClientSession } from 'mongodb';

import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { settings } from '../../../settings/server';
import { RateLimiter } from '../lib';

export const _setRealName = async function (
export const setRealName = async function (
userId: string,
name: string,
fullUser?: IUser,
Expand Down Expand Up @@ -65,10 +62,3 @@ export const _setRealName = async function (

return user;
};

export const setRealName = RateLimiter.limitFunction(_setRealName, 1, 60000, {
async 0() {
const userId = Meteor.userId();
return !userId || !(await hasPermissionAsync(userId, 'edit-other-user-info'));
}, // Administrators have permission to change others names, so don't limit those
});
13 changes: 1 addition & 12 deletions apps/meteor/app/lib/server/functions/setStatusText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import { api } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import type { ClientSession } from 'mongodb';

import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { RateLimiter } from '../lib';

async function _setStatusText(
export async function setStatusText(
userId: string,
statusText: string,
{
Expand Down Expand Up @@ -59,11 +56,3 @@ async function _setStatusText(

return true;
}

export const setStatusText = RateLimiter.limitFunction(_setStatusText, 5, 60000, {
async 0() {
// Administrators have permission to change others status, so don't limit those
const userId = Meteor.userId();
return !userId || !(await hasPermissionAsync(userId, 'edit-other-user-info'));
},
});
Loading
Loading