diff --git a/.changeset/angry-poems-build.md b/.changeset/angry-poems-build.md new file mode 100644 index 00000000000..46b9abc38d8 --- /dev/null +++ b/.changeset/angry-poems-build.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes incorrect message sender for incoming webhooks when "Post As" field is updated by ensuring both username and userId are synced to reflect the selected user. diff --git a/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts b/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts index 2f45a6c9f72..774d7a0d597 100644 --- a/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts @@ -23,18 +23,14 @@ declare module '@rocket.chat/ddp-client' { } } -export const updateIncomingIntegration = async ( - userId: string, - integrationId: string, - integration: INewIncomingIntegration | IUpdateIncomingIntegration, -): Promise => { - if (!integration.channel || typeof integration.channel.valueOf() !== 'string' || integration.channel.trim() === '') { +function validateChannels(channelString: string | undefined): string[] { + if (!channelString || typeof channelString.valueOf() !== 'string' || channelString.trim() === '') { throw new Meteor.Error('error-invalid-channel', 'Invalid channel', { method: 'updateIncomingIntegration', }); } - const channels = integration.channel.split(',').map((channel) => channel.trim()); + const channels = channelString.split(',').map((channel) => channel.trim()); for (const channel of channels) { if (!validChannelChars.includes(channel[0])) { @@ -44,6 +40,16 @@ export const updateIncomingIntegration = async ( } } + return channels; +} + +export const updateIncomingIntegration = async ( + userId: string, + integrationId: string, + integration: INewIncomingIntegration | IUpdateIncomingIntegration, +): Promise => { + const channels = validateChannels(integration.channel); + let currentIntegration; if (await hasPermissionAsync(userId, 'manage-incoming-integrations')) { @@ -153,7 +159,8 @@ export const updateIncomingIntegration = async ( } } - const user = await Users.findOne({ username: currentIntegration.username }); + const username = 'username' in integration ? integration.username : currentIntegration.username; + const user = await Users.findOne({ username }); if (!user?._id) { throw new Meteor.Error('error-invalid-post-as-user', 'Invalid Post As User', { @@ -173,7 +180,7 @@ export const updateIncomingIntegration = async ( emoji: integration.emoji, alias: integration.alias, channel: channels, - ...('username' in integration && { username: integration.username }), + ...('username' in integration && { username: user.username, userId: user._id }), ...(isFrozen ? {} : { @@ -188,6 +195,7 @@ export const updateIncomingIntegration = async ( _updatedBy: await Users.findOne({ _id: userId }, { projection: { username: 1 } }), }, }, + { returnDocument: 'after' }, ); if (updatedIntegration) { diff --git a/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts b/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts index 2df3dfcb13f..29b150a6001 100644 --- a/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts @@ -106,6 +106,7 @@ export const updateOutgoingIntegration = async ( }, }), }, + { returnDocument: 'after' }, ); if (updatedIntegration) { diff --git a/apps/meteor/tests/end-to-end/api/incoming-integrations.ts b/apps/meteor/tests/end-to-end/api/incoming-integrations.ts index f745bf34cc3..3e8769adda0 100644 --- a/apps/meteor/tests/end-to-end/api/incoming-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/incoming-integrations.ts @@ -1,5 +1,6 @@ import type { Credentials } from '@rocket.chat/api-client'; import type { IIntegration, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import { Random } from '@rocket.chat/random'; import { assert, expect } from 'chai'; import { after, before, describe, it } from 'mocha'; @@ -606,6 +607,16 @@ describe('[Incoming Integrations]', () => { }); describe('[/integrations.update]', () => { + let senderUser: IUser; + let sendUserCredentials: Credentials; + + before(async () => { + senderUser = await createUser(); + sendUserCredentials = await login(senderUser.username, password); + }); + + after(() => deleteUser(senderUser)); + it('should update an integration by id and return the new data', (done) => { void request .put(api('integrations.update')) @@ -629,6 +640,7 @@ describe('[Incoming Integrations]', () => { expect(res.body.integration._id).to.be.equal(integration._id); expect(res.body.integration.name).to.be.equal('Incoming test updated'); expect(res.body.integration.alias).to.be.equal('test updated'); + integration = res.body.integration; }) .end(done); }); @@ -649,6 +661,60 @@ describe('[Incoming Integrations]', () => { }) .end(done); }); + + it("should update an integration's username and associated userId correctly and return the new data", async () => { + await request + .put(api('integrations.update')) + .set(credentials) + .send({ + type: 'webhook-incoming', + name: 'Incoming test updated x2', + enabled: true, + alias: 'test updated x2', + username: senderUser.username, + scriptEnabled: true, + overrideDestinationChannelEnabled: true, + channel: '#general', + integrationId: integration._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integration'); + expect(res.body.integration._id).to.be.equal(integration._id); + expect(res.body.integration.name).to.be.equal('Incoming test updated x2'); + expect(res.body.integration.alias).to.be.equal('test updated x2'); + expect(res.body.integration.username).to.be.equal(senderUser.username); + expect(res.body.integration.userId).to.be.equal(sendUserCredentials['X-User-Id']); + integration = res.body.integration; + }); + }); + + it('should send messages to the channel under the updated username', async () => { + const successfulMesssage = `Message sent successfully at #${Random.id()}`; + await request + .post(`/hooks/${integration._id}/${integration.token}`) + .send({ + text: successfulMesssage, + }) + .expect(200); + + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: 'GENERAL', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array'); + const message = (res.body.messages as IMessage[]).find((m) => m.msg === successfulMesssage); + expect(message?.u).have.property('username', senderUser.username); + }); + }); }); describe('[/integrations.remove]', () => {