Skip to content
5 changes: 5 additions & 0 deletions .changeset/angry-poems-build.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@ declare module '@rocket.chat/ddp-client' {
}
}

export const updateIncomingIntegration = async (
userId: string,
integrationId: string,
integration: INewIncomingIntegration | IUpdateIncomingIntegration,
): Promise<IIntegration | null> => {
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])) {
Expand All @@ -44,6 +40,16 @@ export const updateIncomingIntegration = async (
}
}

return channels;
}

export const updateIncomingIntegration = async (
userId: string,
integrationId: string,
integration: INewIncomingIntegration | IUpdateIncomingIntegration,
): Promise<IIntegration | null> => {
const channels = validateChannels(integration.channel);

let currentIntegration;

if (await hasPermissionAsync(userId, 'manage-incoming-integrations')) {
Expand Down Expand Up @@ -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', {
Expand All @@ -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
? {}
: {
Expand All @@ -188,6 +195,7 @@ export const updateIncomingIntegration = async (
_updatedBy: await Users.findOne({ _id: userId }, { projection: { username: 1 } }),
},
},
{ returnDocument: 'after' },
);

if (updatedIntegration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const updateOutgoingIntegration = async (
},
}),
},
{ returnDocument: 'after' },
);

if (updatedIntegration) {
Expand Down
66 changes: 66 additions & 0 deletions apps/meteor/tests/end-to-end/api/incoming-integrations.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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'))
Expand All @@ -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);
});
Expand All @@ -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]', () => {
Expand Down
Loading