From dccdcc5b4a0da4814f72a020bc4eccb8ea2497d8 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Fri, 12 Sep 2025 23:22:21 -0300 Subject: [PATCH] chore!: Remove deprecated `rooms.upload` endpoint (#36857) Co-authored-by: juliajforesti --- .changeset/swift-ears-sparkle.md | 6 + apps/meteor/app/api/server/v1/rooms.ts | 68 ----- apps/meteor/tests/data/uploads.helper.ts | 24 +- apps/meteor/tests/end-to-end/api/chat.ts | 12 - apps/meteor/tests/end-to-end/api/rooms.ts | 340 ++-------------------- packages/rest-typings/src/v1/rooms.ts | 14 - 6 files changed, 45 insertions(+), 419 deletions(-) create mode 100644 .changeset/swift-ears-sparkle.md diff --git a/.changeset/swift-ears-sparkle.md b/.changeset/swift-ears-sparkle.md new file mode 100644 index 00000000000..d332456dba3 --- /dev/null +++ b/.changeset/swift-ears-sparkle.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": major +"@rocket.chat/rest-typings": major +--- + +Removes the deprecated `/api/v1/rooms.upload` endpoint diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 2fdc1c071c7..068bcdf2665 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -188,74 +188,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'rooms.upload/:rid', - { - authRequired: true, - deprecation: { - version: '8.0.0', - alternatives: ['/v1/rooms.media/:rid'], - }, - }, - { - async post() { - if (!(await canAccessRoomIdAsync(this.urlParams.rid, this.userId))) { - return API.v1.forbidden(); - } - - const file = await getUploadFormData( - { - request: this.request, - }, - { field: 'file', sizeLimit: settings.get('FileUpload_MaxFileSize') }, - ); - - if (!file) { - throw new Meteor.Error('invalid-field'); - } - - const { fields } = file; - let { fileBuffer } = file; - - const details = { - name: file.filename, - size: fileBuffer.length, - type: file.mimetype, - rid: this.urlParams.rid, - userId: this.userId, - }; - - const stripExif = settings.get('Message_Attachments_Strip_Exif'); - if (stripExif) { - // No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc) - fileBuffer = await Media.stripExifFromBuffer(fileBuffer); - details.size = fileBuffer.length; - } - - const fileStore = FileUpload.getStore('Uploads'); - const uploadedFile = await fileStore.insert(details, fileBuffer); - - if ((fields.description?.length ?? 0) > settings.get('Message_MaxAllowedSize')) { - throw new Meteor.Error('error-message-size-exceeded'); - } - - uploadedFile.description = fields.description; - - delete fields.description; - - await applyAirGappedRestrictionsValidation(() => - sendFileMessage(this.userId, { roomId: this.urlParams.rid, file: uploadedFile, msgData: fields }), - ); - - const message = await Messages.getMessageByFileIdAndUsername(uploadedFile._id, this.userId); - - return API.v1.success({ - message, - }); - }, - }, -); - API.v1.addRoute( 'rooms.media/:rid', { authRequired: true }, diff --git a/apps/meteor/tests/data/uploads.helper.ts b/apps/meteor/tests/data/uploads.helper.ts index ae1641f9c1c..758fe68dba3 100644 --- a/apps/meteor/tests/data/uploads.helper.ts +++ b/apps/meteor/tests/data/uploads.helper.ts @@ -165,17 +165,25 @@ export async function testFileUploads( .end(done); }); + let fileId: string; it('should not return thumbnails', async () => { await request - .post(api(`rooms.upload/${testRoom._id}`)) + .post(api(`rooms.media/${testRoom._id}`)) .set(credentials) .attach('file', imgURL) .expect('Content-Type', 'application/json') .expect(200) .expect((res: Response) => { expect(res.body).to.have.property('success', true); + fileId = res.body.file._id; }); + await request + .post(api(`rooms.mediaConfirm/${testRoom._id}/${fileId}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + await request .get(api(filesEndpoint)) .set(credentials) @@ -198,19 +206,25 @@ export async function testFileUploads( it('should not return hidden files', async () => { let msgId; - let fileId: string; await request - .post(api(`rooms.upload/${testRoom._id}`)) + .post(api(`rooms.media/${testRoom._id}`)) .set(credentials) .attach('file', imgURL) .expect('Content-Type', 'application/json') .expect(200) .expect((res: Response) => { expect(res.body).to.have.property('success', true); - + fileId = res.body.file._id; + }); + await request + .post(api(`rooms.mediaConfirm/${testRoom._id}/${fileId}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); msgId = res.body.message._id; - fileId = res.body.message.file._id; }); await request diff --git a/apps/meteor/tests/end-to-end/api/chat.ts b/apps/meteor/tests/end-to-end/api/chat.ts index 779bb1f65a0..382df1f8cbf 100644 --- a/apps/meteor/tests/end-to-end/api/chat.ts +++ b/apps/meteor/tests/end-to-end/api/chat.ts @@ -7,7 +7,6 @@ import type { Response } from 'supertest'; import { getCredentials, api, request, credentials, apiUrl } from '../../data/api-data'; import { followMessage, sendSimpleMessage, deleteMessage } from '../../data/chat.helper'; -import { imgURL } from '../../data/interactions'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { addUserToRoom, createRoom, deleteRoom, getSubscriptionByRoomId } from '../../data/rooms.helper'; import { password } from '../../data/user'; @@ -1423,17 +1422,6 @@ describe('[Chat]', () => { .expect(statusCode) .expect(testCb); - await ( - customFields - ? request.post(api(`rooms.upload/${testChannel._id}`)).field('customFields', JSON.stringify(customFields)) - : request.post(api(`rooms.upload/${testChannel._id}`)) - ) - .set(credentials) - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(statusCode) - .expect(testCb); - await request .post(api('chat.postMessage')) .set(credentials) diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index f7382be0086..8ab8a3bb209 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -32,7 +32,6 @@ import { createUser, deleteUser, login } from '../../data/users.helper'; import { IS_EE } from '../../e2e/config/constants'; const lstURL = './tests/e2e/fixtures/files/lst-test.lst'; -const drawioURL = './tests/e2e/fixtures/files/diagram.drawio'; const svgLogoURL = './public/images/logo/logo.svg'; const svgLogoFileName = 'logo.svg'; @@ -100,320 +99,6 @@ describe('[Rooms]', () => { }); }); - describe('/rooms.upload', () => { - let testChannel: IRoom; - let user: TestUser; - let userCredentials: Credentials; - const testChannelName = `channel.test.upload.${Date.now()}-${Math.random()}`; - let blockedMediaTypes: SettingValue; - let testPrivateChannel: IRoom; - - before(async () => { - user = await createUser({ joinDefaultChannels: false }); - userCredentials = await login(user.username, password); - testChannel = (await createRoom({ type: 'c', name: testChannelName })).body.channel; - testPrivateChannel = (await createRoom({ type: 'p', name: `channel.test.private.${Date.now()}-${Math.random()}` })).body.group; - blockedMediaTypes = await getSettingValueById('FileUpload_MediaTypeBlackList'); - const newBlockedMediaTypes = (blockedMediaTypes as string) - .split(',') - .filter((type) => type !== 'image/svg+xml') - .join(','); - await updateSetting('FileUpload_MediaTypeBlackList', newBlockedMediaTypes); - }); - - after(() => - Promise.all([ - deleteRoom({ type: 'c', roomId: testChannel._id }), - deleteUser(user), - updateSetting('FileUpload_Restrict_to_room_members', true), - updateSetting('FileUpload_Restrict_to_users_who_can_access_room', false), - updateSetting('FileUpload_ProtectFiles', true), - updateSetting('FileUpload_MediaTypeBlackList', blockedMediaTypes), - deleteRoom({ roomId: testPrivateChannel._id, type: 'p' }), - ]), - ); - - it("don't upload a file to room with file field other than file", (done) => { - void request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('test', imgURL) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', '[invalid-field]'); - expect(res.body).to.have.property('errorType', 'invalid-field'); - }) - .end(done); - }); - it("don't upload a file to room with empty file", (done) => { - void request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', '') - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', res.body.error); - }) - .end(done); - }); - it("don't upload a file to room with more than 1 file", (done) => { - void request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', imgURL) - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'Just 1 file is allowed'); - }) - .end(done); - }); - - let fileNewUrl: string; - let fileOldUrl: string; - it('should upload a PNG file to room', async () => { - await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - const message = res.body.message as IMessage; - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('message'); - expect(res.body.message).to.have.property('attachments'); - expect(res.body.message.attachments).to.be.an('array').of.length(1); - expect(res.body.message.attachments[0]).to.have.property('image_type', 'image/png'); - expect(res.body.message.attachments[0]).to.have.property('title', '1024x1024.png'); - expect(res.body.message).to.have.property('files'); - expect(res.body.message.files).to.be.an('array').of.length(2); - expect(res.body.message.files[0]).to.have.property('type', 'image/png'); - expect(res.body.message.files[0]).to.have.property('name', '1024x1024.png'); - - assert.isDefined(message.file); - fileNewUrl = `/file-upload/${message.file._id}/${message.file.name}`; - fileOldUrl = `/ufs/GridFS:Uploads/${message.file._id}/${message.file.name}`; - }); - }); - - it('should upload a LST file to room', () => { - return request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', lstURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('message'); - expect(res.body.message).to.have.property('attachments'); - expect(res.body.message.attachments).to.be.an('array').of.length(1); - expect(res.body.message.attachments[0]).to.have.property('format', 'LST'); - expect(res.body.message.attachments[0]).to.have.property('title', 'lst-test.lst'); - expect(res.body.message).to.have.property('files'); - expect(res.body.message.files).to.be.an('array').of.length(1); - expect(res.body.message.files[0]).to.have.property('name', 'lst-test.lst'); - expect(res.body.message.files[0]).to.have.property('type', 'text/plain'); - }); - }); - - it('should upload a DRAWIO file (unknown media type) to room', () => { - return request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', drawioURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('message'); - expect(res.body.message).to.have.property('attachments'); - expect(res.body.message.attachments).to.be.an('array').of.length(1); - expect(res.body.message.attachments[0]).to.have.property('format', 'DRAWIO'); - expect(res.body.message.attachments[0]).to.have.property('title', 'diagram.drawio'); - expect(res.body.message).to.have.property('files'); - expect(res.body.message.files).to.be.an('array').of.length(1); - expect(res.body.message.files[0]).to.have.property('name', 'diagram.drawio'); - expect(res.body.message.files[0]).to.have.property('type', 'application/octet-stream'); - }); - }); - - it('should not allow uploading a blocked media type to a room', async () => { - await updateSetting('FileUpload_MediaTypeBlackList', 'text/plain'); - await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', lstURL) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-invalid-file-type'); - }); - }); - - it('should not allow uploading an unknown media type to a room if the default one is blocked', async () => { - await updateSetting('FileUpload_MediaTypeBlackList', 'application/octet-stream'); - await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', drawioURL) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-invalid-file-type'); - }); - }); - - it('should be able to get the file', async () => { - await request.get(fileNewUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - }); - - it('should be able to get the file when no access to the room if setting allows it', async () => { - await updateSetting('FileUpload_Restrict_to_room_members', false); - await updateSetting('FileUpload_Restrict_to_users_who_can_access_room', false); - await request.get(fileNewUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); - }); - - it('should not be able to get the file when no access to the room if setting blocks', async () => { - await updateSetting('FileUpload_Restrict_to_room_members', true); - await request.get(fileNewUrl).set(userCredentials).expect(403); - await request.get(fileOldUrl).set(userCredentials).expect(403); - }); - - it('should be able to get the file if member and setting blocks outside access', async () => { - await updateSetting('FileUpload_Restrict_to_room_members', true); - await request.get(fileNewUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - }); - - it('should be able to get the file if not member but can access room if setting allows', async () => { - await updateSetting('FileUpload_Restrict_to_room_members', false); - await updateSetting('FileUpload_Restrict_to_users_who_can_access_room', true); - - await request.get(fileNewUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); - }); - - it('should not be able to get the file if not member and cannot access room', async () => { - const { body } = await request - .post(api(`rooms.upload/${testPrivateChannel._id}`)) - .set(credentials) - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(200); - - const fileUrl = `/file-upload/${body.message.file._id}/${body.message.file.name}`; - - await request.get(fileUrl).set(userCredentials).expect(403); - }); - - it('should respect the setting with less permissions when both are true', async () => { - await updateSetting('FileUpload_ProtectFiles', true); - await updateSetting('FileUpload_Restrict_to_room_members', true); - await updateSetting('FileUpload_Restrict_to_users_who_can_access_room', true); - await request.get(fileNewUrl).set(userCredentials).expect(403); - await request.get(fileOldUrl).set(userCredentials).expect(403); - }); - - it('should not be able to get the file without credentials', async () => { - await request.get(fileNewUrl).attach('file', imgURL).expect(403); - await request.get(fileOldUrl).attach('file', imgURL).expect(403); - }); - - it('should be able to get the file without credentials if setting allows', async () => { - await updateSetting('FileUpload_ProtectFiles', false); - await request.get(fileNewUrl).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).expect('Content-Type', 'image/png').expect(200); - }); - - it('should generate thumbnail for SVG files correctly', async () => { - const expectedFileName = `thumb-${svgLogoFileName}`; - - const res = await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', svgLogoURL) - .expect('Content-Type', 'application/json') - .expect(200); - - const message = res.body.message as IMessage; - const { files, attachments } = message; - - expect(files).to.be.an('array'); - const hasThumbFile = files?.some((file) => file.type === 'image/png' && file.name === expectedFileName); - expect(hasThumbFile).to.be.true; - - expect(attachments).to.be.an('array'); - const thumbAttachment = attachments?.find((attachment) => attachment.title === svgLogoFileName); - assert.isDefined(thumbAttachment); - expect(thumbAttachment).to.be.an('object'); - const thumbUrl = (thumbAttachment as ImageAttachmentProps).image_url; - - await request.get(thumbUrl).set(credentials).expect('Content-Type', 'image/png'); - }); - - it('should generate thumbnail for JPEG files correctly', async () => { - const expectedFileName = `thumb-sample-jpeg.jpg`; - const res = await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .attach('file', fs.createReadStream(path.join(__dirname, '../../mocks/files/sample-jpeg.jpg'))) - .expect('Content-Type', 'application/json') - .expect(200); - - const message = res.body.message as IMessage; - const { files, attachments } = message; - - expect(files).to.be.an('array'); - assert.isDefined(files); - const hasThumbFile = files.some((file) => file.type === 'image/jpeg' && file.name === expectedFileName); - expect(hasThumbFile).to.be.true; - - expect(attachments).to.be.an('array'); - assert.isDefined(attachments); - const thumbAttachment = attachments.find((attachment) => attachment.title === `sample-jpeg.jpg`); - expect(thumbAttachment).to.be.an('object'); - const thumbUrl = (thumbAttachment as ImageAttachmentProps).image_url; - - await request.get(thumbUrl).set(credentials).expect('Content-Type', 'image/jpeg'); - }); - - // Support legacy behavior (not encrypting file) - it('should correctly save file description and properties with type e2e', async () => { - await request - .post(api(`rooms.upload/${testChannel._id}`)) - .set(credentials) - .field('description', 'some_file_description') - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('message'); - expect(res.body.message).to.have.property('attachments'); - expect(res.body.message.attachments).to.be.an('array').of.length(1); - expect(res.body.message.attachments[0]).to.have.property('image_type', 'image/png'); - expect(res.body.message.attachments[0]).to.have.property('title', '1024x1024.png'); - expect(res.body.message).to.have.property('files'); - expect(res.body.message.files).to.be.an('array').of.length(2); - expect(res.body.message.files[0]).to.have.property('type', 'image/png'); - expect(res.body.message.files[0]).to.have.property('name', '1024x1024.png'); - expect(res.body.message.attachments[0]).to.have.property('description', 'some_file_description'); - }); - }); - }); - describe('/rooms.media', () => { let testChannel: IRoom; let user: TestUser; @@ -1142,7 +827,7 @@ describe('[Rooms]', () => { }); it('should successfully delete an image and thumbnail from public channel', (done) => { void request - .post(api(`rooms.upload/${publicChannel._id}`)) + .post(api(`rooms.media/${publicChannel._id}`)) .set(credentials) .attach('file', imgURL) .expect('Content-Type', 'application/json') @@ -2962,14 +2647,29 @@ describe('[Rooms]', () => { }); const uploadFile = async ({ roomId, file }: { roomId: IRoom['_id']; file: Buffer | fs.ReadStream | string | boolean | number }) => { - const { body } = await request - .post(api(`rooms.upload/${roomId}`)) + let fileId; + await request + .post(api(`rooms.media/${roomId}`)) .set(credentials) .attach('file', file) .expect('Content-Type', 'application/json') - .expect(200); + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('file'); + expect(res.body.file).to.have.property('_id'); + fileId = res.body.file._id; + }); + + const res = await request + .post(api(`rooms.mediaConfirm/${roomId}/${fileId}`)) + .set(credentials) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); - return body.message.attachments[0]; + return res.body.message.attachments[0]; }; const getIdFromImgPath = (link: string) => { diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 35400e04052..c8d77253ecf 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -782,20 +782,6 @@ export type RoomsEndpoints = { }; }; - '/v1/rooms.upload/:rid': { - POST: (params: { - file: File; - description?: string; - avatar?: string; - emoji?: string; - alias?: string; - groupable?: boolean; - msg?: string; - tmid?: string; - customFields?: string; - }) => { message: IMessage | null }; - }; - '/v1/rooms.media/:rid': { POST: (params: { file: File }) => { file: { url: string } }; };