Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/flat-tables-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/core-typings': patch
'@rocket.chat/meteor': patch
---

Fixes association of encrypted messages and encrypted files, so that if one of them is removed, the other gets removed as well.
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ API.v1.addRoute(
delete this.bodyParams.description;

await applyAirGappedRestrictionsValidation(() =>
sendFileMessage(this.userId, { roomId: this.urlParams.rid, file, msgData: this.bodyParams }, { parseAttachmentsForE2EE: false }),
sendFileMessage(this.userId, { roomId: this.urlParams.rid, file, msgData: this.bodyParams }),
);

await Uploads.confirmTemporaryFile(this.urlParams.fileId, this.userId);
Expand Down
17 changes: 4 additions & 13 deletions apps/meteor/app/file-upload/server/methods/sendFileMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,6 @@ export const sendFileMessage = async (
file: Partial<IUpload>;
msgData?: Record<string, any>;
},
{
parseAttachmentsForE2EE,
}: {
parseAttachmentsForE2EE: boolean;
} = {
parseAttachmentsForE2EE: true,
},
): Promise<boolean> => {
const user = await Users.findOneById(userId, { projection: { services: 0 } });

Expand Down Expand Up @@ -220,12 +213,10 @@ export const sendFileMessage = async (
groupable: msgData?.groupable ?? false,
};

if (parseAttachmentsForE2EE || msgData?.t !== 'e2e') {
const { files, attachments } = await parseFileIntoMessageAttachments(file, roomId, user);
data.file = files[0];
data.files = files;
data.attachments = attachments;
}
const { files, attachments } = await parseFileIntoMessageAttachments(file, roomId, user);
data.file = files[0];
data.files = files;
data.attachments = attachments;

const msg = await executeSendMessage(userId, data);

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 @@ -48,7 +48,8 @@ export async function cleanRoomHistory({
});

const targetMessageIdsForAttachmentRemoval = new Set<string>();
const pruneMessageAttachment = { color: NOTIFICATION_ATTACHMENT_COLOR, text };
// Since we remove every file from the messages, we don't need to specify which fileId has been removed.
const pruneMessageAttachment = { type: 'removed-file', color: NOTIFICATION_ATTACHMENT_COLOR, text };

async function performFileAttachmentCleanupBatch() {
if (targetMessageIdsForAttachmentRemoval.size === 0) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function getEmailContent({ message, user, room }) {
});
}

if (message.t === 'e2e' && !message.file && !message.files?.length) {
if (message.t === 'e2e') {
return settings.get('Email_notification_show_message') ? i18n.t('Encrypted_message_preview_unavailable', { lng }) : header;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const processMessage = async (msg: IMessage & { ignored?: boolean }, { subscript
msg.ignored = true;
}

if (msg.t === 'e2e' && !msg.file) {
if (msg.t === 'e2e') {
msg.e2e = 'pending';
}

Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/lib/chats/flows/uploadFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const uploadFiles = async (chat: ChatAPI, files: readonly File[], resetFi
hashes: {
sha256: encryptedFile.hash,
},
fileId: _id,
};

if (/^image\/.+/.test(file.type)) {
Expand Down
43 changes: 41 additions & 2 deletions apps/meteor/client/lib/e2ee/rocketchat.e2e.room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
EncryptedMessageContent,
EncryptedContent,
} from '@rocket.chat/core-typings';
import { isEncryptedMessageContent } from '@rocket.chat/core-typings';
import { isEncryptedMessageContent, isFileAttachment, isRemovedFileAttachment } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import type { Optional } from '@tanstack/react-query';
import EJSON from 'ejson';
Expand Down Expand Up @@ -660,6 +660,45 @@ export class E2ERoom extends Emitter {
return data;
}

async decryptMessageContent(message: IE2EEMessage): Promise<IE2EEMessage> {
const { attachments } = message;
const deletedAttachments = attachments?.filter((att) => isRemovedFileAttachment(att)) || [];
const deletedAllAttachment = deletedAttachments.find((att) => !att.fileId);

const content = await this.decrypt(message.content);
Object.assign(message, content);

// If the encrypted message had deleted files and the decrypted message has files, compare both lists to remove from the final result any file that was flagged as deleted
if (!deletedAttachments.length || !message.attachments?.length || !content.attachments?.length) {
return message;
}

message.attachments = message.attachments.map((att) => {
if (!isFileAttachment(att)) {
return att;
}

if (deletedAllAttachment) {
return deletedAllAttachment;
}

const fileId = att.fileId || message.file?._id;
if (!fileId) {
return att;
}

for (const removedAttachment of deletedAttachments) {
if (removedAttachment.fileId === fileId) {
return removedAttachment;
}
}

return att;
});

return message;
}

// Decrypt messages
async decryptMessage(message: IMessage | IE2EEMessage): Promise<IE2EEMessage | IMessage> {
if (message.t !== 'e2e' || message.e2e === 'done') {
Expand All @@ -668,7 +707,7 @@ export class E2ERoom extends Emitter {

if (isEncryptedMessageContent(message)) {
return {
...(await this.decryptContent({ ...message })),
...(await this.decryptMessageContent({ ...message })),
e2e: 'done' as const,
};
}
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/server/services/upload/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class UploadService extends ServiceClassInternal implements IUploadServic

private async updateMessageRemovingFiles(msg: IMessage, filesToRemove: IUpload['_id'][], user: IUser): Promise<void> {
const text = `_${i18n.t('File_removed')}_`;
const newAttachment = { color: NOTIFICATION_ATTACHMENT_COLOR, text };
const color = NOTIFICATION_ATTACHMENT_COLOR;

const newFiles = msg.files?.filter((file) => !filesToRemove.includes(file._id));
const newAttachments = msg.attachments?.map((attachment) => {
Expand All @@ -120,7 +120,7 @@ export class UploadService extends ServiceClassInternal implements IUploadServic
return attachment;
}

return newAttachment;
return { type: 'removed-file', color, text, ...(attachment.fileId && { fileId: attachment.fileId }) };
});
const newFile = msg.file?._id && !filesToRemove.includes(msg.file._id) ? msg.file : newFiles?.[0];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { MessageAttachmentBase } from '../MessageAttachmentBase';
import type { FileProp } from './FileProp';

export type RemovedFileAttachmentProps = MessageAttachmentBase & {
type: 'removed-file';
/* If fileId is missing, then every file in the message has been removed */
fileId?: FileProp['_id'];
};

export const isRemovedFileAttachment = (attachment: MessageAttachmentBase): attachment is RemovedFileAttachmentProps =>
'type' in attachment && attachment.type === 'removed-file';
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './AudioAttachmentProps';
export * from './FileAttachmentProps';
export * from './FileProp';
export * from './ImageAttachmentProps';
export * from './RemovedFileAttachmentProps';
export * from './VideoAttachmentProps';
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { FileProp } from './Files';
import type { FileAttachmentProps } from './Files/FileAttachmentProps';
import type { RemovedFileAttachmentProps } from './Files/RemovedFileAttachmentProps';
import type { MessageAttachmentAction } from './MessageAttachmentAction';
import type { MessageAttachmentDefault } from './MessageAttachmentDefault';
import type { MessageQuoteAttachment } from './MessageQuoteAttachment';

export type MessageAttachment = MessageAttachmentAction | MessageAttachmentDefault | FileAttachmentProps | MessageQuoteAttachment;
export type MessageAttachment =
| MessageAttachmentAction
| MessageAttachmentDefault
| FileAttachmentProps
| MessageQuoteAttachment
| RemovedFileAttachmentProps;

export type FilesAndAttachments = {
files: FileProp[];
Expand Down
Loading