Skip to content

Commit

Permalink
fix(matrix-client/messages-saga): bypass encrypting files during uplo…
Browse files Browse the repository at this point in the history
…ad if non encrypted room (#2184)
  • Loading branch information
domw30 authored Aug 20, 2024
1 parent 6410aca commit 1d395e4
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 37 deletions.
105 changes: 93 additions & 12 deletions src/lib/chat/matrix-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,25 +789,70 @@ describe('matrix client', () => {
});

describe('uploadFileMessage', () => {
it('logs warning if room is not encrypted and returns ', async () => {
it('does not encrypt and sends a file message successfully in a non-encrypted room', async () => {
const roomId = '!testRoomId';
const optimisticId = 'optimistic-id';
const rootMessageId = 'root-message-id';
const media = {
name: 'test-file',
size: 1000,
type: 'image/png',
};

const isRoomEncrypted = jest.fn(() => false);

when(mockGetImageDimensions).calledWith(expect.anything()).mockResolvedValue({ width: 800, height: 600 });

when(mockUploadAttachment).calledWith(expect.anything()).mockResolvedValue({
name: 'test-file',
key: 'attachments/../test.jpg',
url: 'attachments/../test.jpg',
type: 'file',
});

const sendMessage = jest.fn(() =>
Promise.resolve({
event_id: 'new-message-id',
})
);

const client = subject({
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted })),
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted, sendMessage })),
});

await client.connect(null, 'token');
client.uploadFileMessage(roomId, {} as File, rootMessageId, optimisticId);
const result = await client.uploadFileMessage(roomId, media as File, rootMessageId, optimisticId);

expect(sendMessage).toBeCalledWith(
roomId,
expect.objectContaining({
body: '',
msgtype: 'm.image',
file: {
url: 'attachments/../test.jpg',
mimetype: 'image/png',
size: 1000,
},
info: {
mimetype: 'image/png',
size: 1000,
name: 'test-file',
optimisticId: 'optimistic-id',
rootMessageId: 'root-message-id',
width: 800,
height: 600,
},
optimisticId: 'optimistic-id',
})
);

expect(mockEncryptFile).not.toHaveBeenCalled();
expect(result).toMatchObject({
id: 'new-message-id',
optimisticId: optimisticId,
});
});

it('sends a file message successfully', async () => {
// const originalMessageId = 'orig-message-id';
it('encrypts and sends a file message successfully in an encrypted room', async () => {
const roomId = '!testRoomId';
const optimisticId = 'optimistic-id';
const rootMessageId = 'root-message-id';
Expand All @@ -817,6 +862,8 @@ describe('matrix client', () => {
type: 'image/png',
};

const isRoomEncrypted = jest.fn(() => true);

when(mockGetImageDimensions).calledWith(expect.anything()).mockResolvedValue({ width: 800, height: 600 });

when(mockEncryptFile)
Expand Down Expand Up @@ -845,7 +892,7 @@ describe('matrix client', () => {
);

const client = subject({
createClient: jest.fn(() => getSdkClient({ sendMessage })),
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted, sendMessage })),
});

await client.connect(null, 'token');
Expand Down Expand Up @@ -884,21 +931,53 @@ describe('matrix client', () => {
});

describe('uploadImageUrl', () => {
it('logs warning if room is not encrypted and returns ', async () => {
it('sends an image URL message successfully in a non-encrypted room', async () => {
const roomId = '!testRoomId';
const url = 'http://example.com/image.gif';
const width = 500;
const height = 300;
const size = 1500;
const optimisticId = 'optimistic-id';
const rootMessageId = 'root-message-id';

const isRoomEncrypted = jest.fn(() => false);

const sendMessage = jest.fn(() =>
Promise.resolve({
event_id: 'new-message-id',
})
);

const client = subject({
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted })),
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted, sendMessage })),
});

await client.connect(null, 'token');
await client.uploadImageUrl(roomId, 'http://example.com/image.gif', 500, 300, 1500, rootMessageId, optimisticId);

expect(mockEncryptFile).not.toHaveBeenCalled();
const result = await client.uploadImageUrl(roomId, url, width, height, size, rootMessageId, optimisticId);

expect(sendMessage).toBeCalledWith(
roomId,
expect.objectContaining({
body: '',
msgtype: 'm.image',
url: url,
info: {
mimetype: 'image/gif',
w: width,
h: height,
size: size,
optimisticId,
rootMessageId,
},
optimisticId,
})
);

expect(result).toMatchObject({
id: 'new-message-id',
optimisticId,
});
});

it('sends an image URL message successfully in an encrypted room', async () => {
Expand All @@ -910,14 +989,16 @@ describe('matrix client', () => {
const optimisticId = 'optimistic-id';
const rootMessageId = 'root-message-id';

const isRoomEncrypted = jest.fn(() => true);

const sendMessage = jest.fn(() =>
Promise.resolve({
event_id: 'new-message-id',
})
);

const client = subject({
createClient: jest.fn(() => getSdkClient({ sendMessage })),
createClient: jest.fn(() => getSdkClient({ isRoomEncrypted, sendMessage })),
});

await client.connect(null, 'token');
Expand Down
37 changes: 20 additions & 17 deletions src/lib/chat/matrix-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,23 +594,29 @@ export class MatrixClient implements IChatClient {
}

async uploadFileMessage(roomId: string, media: File, rootMessageId: string = '', optimisticId = '') {
if (!this.matrix.isRoomEncrypted(roomId)) {
console.warn('uploadFileMessage called for non-encrypted room', roomId);
return;
}
const isEncrypted = this.matrix.isRoomEncrypted(roomId);

const { width, height } = await getImageDimensions(media);
const encrypedFileInfo = await encryptFile(media);
const uploadedFile = await uploadAttachment(encrypedFileInfo.file);

// https://spec.matrix.org/v1.8/client-server-api/#extensions-to-mroommessage-msgtypes
const file = {
url: uploadedFile.key,
...encrypedFileInfo.info,
};
let file;
if (isEncrypted) {
const encryptedFileInfo = await encryptFile(media); // Call encryptFile if the room is encrypted
const uploadedFile = await uploadAttachment(encryptedFileInfo.file);
file = {
url: uploadedFile.key,
...encryptedFileInfo.info,
};
} else {
const uploadedFile = await uploadAttachment(media); // Directly upload the file without encryption
file = {
url: uploadedFile.key,
mimetype: media.type,
size: media.size,
};
}

const content = {
body: null,
body: isEncrypted ? null : '',
msgtype: MsgType.Image,
file,
info: {
Expand Down Expand Up @@ -644,13 +650,10 @@ export class MatrixClient implements IChatClient {
rootMessageId: string = '',
optimisticId = ''
) {
if (!this.matrix.isRoomEncrypted(roomId)) {
console.warn('uploadGiphyMessage called for non-encrypted room', roomId);
return;
}
const isEncrypted = this.matrix.isRoomEncrypted(roomId);

const content = {
body: null,
body: isEncrypted ? null : '',
msgtype: MsgType.Image,
url: url,
info: {
Expand Down
25 changes: 17 additions & 8 deletions src/lib/chat/matrix/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,30 @@ export async function encryptFile(file: File): Promise<{ info: encrypt.IEncrypte

// https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/utils/DecryptFile.ts#L50
export async function decryptFile(encryptedFile, mimetype): Promise<Blob> {
// Determine if the file is encrypted by checking for encryption-related fields
const isEncrypted = !!(encryptedFile.key && encryptedFile.iv && encryptedFile.hashes?.sha256);

// Get the signed URL for the file
const signedUrl = await getAttachmentUrl({ key: encryptedFile.url });

// Download the encrypted file as an array buffer.
// Download the file as an array buffer
const response = await fetch(signedUrl);
if (!response.ok) {
throw new Error(`Error occurred while downloading file ${encryptedFile.url}: ${await response.text()}`);
}
const responseData: ArrayBuffer = await response.arrayBuffer();

try {
// Decrypt the array buffer using the information taken from the event content.
const dataArray = await encrypt.decryptAttachment(responseData, encryptedFile);
// Turn the array into a Blob and give it the correct MIME-type.
return new Blob([dataArray], { type: mimetype });
} catch (e) {
throw new Error(`Error occurred while decrypting file ${encryptedFile.url}: ${e}`);
if (isEncrypted) {
try {
// Decrypt the array buffer using the information taken from the event content
const dataArray = await encrypt.decryptAttachment(responseData, encryptedFile);
// Turn the array into a Blob and give it the correct MIME-type
return new Blob([dataArray], { type: mimetype });
} catch (e) {
throw new Error(`Error occurred while decrypting file ${encryptedFile.url}: ${e}`);
}
} else {
// For non-encrypted files, directly create a Blob from the downloaded data
return new Blob([responseData], { type: mimetype });
}
}

0 comments on commit 1d395e4

Please sign in to comment.