import type { Credentials } from '@rocket.chat/api-client';
import type { IMessage, IRoom, ISubscription, IThreadMessage, IUser } from '@rocket.chat/core-typings';
import { Random } from '@rocket.chat/random';
import { expect } from 'chai';
import { after, before, beforeEach, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { retry } from './helpers/retry';
import { sleep } from '../../../lib/utils/sleep';
import { getCredentials, api, request, credentials, apiUrl } from '../../data/api-data';
import { followMessage, sendMessage, sendSimpleMessage, deleteMessage, updateMessage } from '../../data/chat.helper';
import { imgURL } from '../../data/interactions';
import { mockServerHealthy, mockServerReset, mockServerSet } from '../../data/mock-server.helper';
import { updatePermission, updateSetting } from '../../data/permissions.helper';
import { addUserToRoom, createRoom, deleteRoom, getSubscriptionByRoomId } from '../../data/rooms.helper';
import { password } from '../../data/user';
import type { TestUser } from '../../data/users.helper';
import { createUser, deleteUser, login } from '../../data/users.helper';
const pinMessage = ({ msgId }: { msgId: IMessage['_id'] }) => {
if (!msgId) {
throw new Error('"msgId" is required in "pinMessage" test helper');
}
return request.post(api('chat.pinMessage')).set(credentials).send({
messageId: msgId,
});
};
describe('[Chat]', () => {
let testChannel: IRoom;
let message: { _id: IMessage['_id'] };
let protectedChannel: IRoom;
before((done) => getCredentials(done));
before(async () => {
testChannel = (await createRoom({ type: 'c', name: `chat.api-test-${Date.now()}` })).body.channel;
protectedChannel = (await createRoom({ type: 'c', name: `chat.api-protected-test-${Date.now()}` })).body.channel;
await request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: protectedChannel._id,
joinCode: 'super-secret-password',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
after(async () => {
await deleteRoom({ type: 'c', roomId: testChannel._id });
await deleteRoom({ type: 'c', roomId: protectedChannel._id });
});
describe('/chat.postMessage', () => {
it('should throw an error when at least one of required parameters(channel, roomId) is not sent', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should throw an error when it has some properties with the wrong type(attachments.title_link_download, attachments.fields, message_link)', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
alias: 'Gruggy',
text: 'Sample message',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 12,
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: 'https://youtube.com',
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: '',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
describe('should throw an error when the sensitive properties contain malicious XSS values', () => {
it('attachment.message_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
alias: 'Gruggy',
text: 'Sample message',
avatar: 'http://res.guggy.com/logo_128.png',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.author_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
author_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.title_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.action.url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'https://youtube.com',
actions: [
{
type: 'button',
text: 'Text',
url: 'javascript:alert("xss")',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('message.avatar', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
avatar: 'javascript:alert("xss")',
alias: 'Gruggy',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'https://youtube.com',
actions: [
{
type: 'button',
text: 'Text',
url: 'https://youtube.com',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.action.image_url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'https://youtube.com',
actions: [
{
type: 'button',
text: 'Text',
url: 'http://res.guggy.com/logo_128.png',
image_url: 'javascript:alert("xss")',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.thumb_url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
alias: 'Gruggy',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
thumb_url: 'javascript:alert("xss")',
title_link: 'http://res.guggy.com/logo_128.png',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.author_icon', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
alias: 'Gruggy',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
author_icon: 'javascript:alert("xss")',
title_link: 'http://res.guggy.com/logo_128.png',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.image_url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
image_url: 'javascript:alert("xss")',
title_link: 'http://res.guggy.com/logo_128.png',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.audio_url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
audio_url: 'javascript:alert("xss")',
title_link: 'http://res.guggy.com/logo_128.png',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.video_url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
alias: 'Gruggy',
text: 'Sample message',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
video_url: 'javascript:alert("xss")',
title_link: 'http://res.guggy.com/logo_128.png',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
});
it('should throw an error when the properties (attachments.fields.title, attachments.fields.value) are with the wrong type', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
alias: 'Gruggy',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
title: 12,
value: false,
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
it('should throw an error when the properties (attachments.fields.title) is missing', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
alias: 'Gruggy',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
value: 'This is attachment field value',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
it('should throw an error when the properties (attachments.fields.value) is missing', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
alias: 'Gruggy',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
title: 'This is attachment field title',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
it('attachment.fields should work fine when value and title are provided', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
attachments: [
{
text: 'This is attachment field',
color: '#764FA5',
fields: [{ short: true, value: 'This is value', title: 'This is title' }],
},
],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.property('error');
expect(res.body).to.have.nested.property('message.msg', 'Sample message');
expect(res.body).to.have.nested.property('message.attachments').to.be.an('array');
expect(res.body).to.have.nested.property('message.attachments[0].fields').to.be.an('array');
expect(res.body).to.have.nested.property('message.attachments[0].fields[0].short', true);
expect(res.body).to.have.nested.property('message.attachments[0].fields[0].value', 'This is value');
expect(res.body).to.have.nested.property('message.attachments[0].fields[0].title', 'This is title');
});
it('should allow forwarding a message into the same password protected room', async () => {
const postResponse = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
roomId: [protectedChannel._id],
text: 'Message to be forwarded',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(postResponse.body).to.have.property('success', true);
const originalMessageId = postResponse.body.message._id as IMessage['_id'];
const forwardResponse = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
roomId: [protectedChannel._id],
text: `[](http://localhost:3000/channel/${protectedChannel.name}?msg=${originalMessageId}`,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(forwardResponse.body).to.have.property('success', true);
expect(forwardResponse.body).to.have.nested.property('message.rid', protectedChannel._id);
});
it('should return statusCode 200 when postMessage successfully', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
title: 'Test',
value: 'Testing out something or other',
},
{
short: true,
title: 'Another Test',
value: '[Link](https://google.com/) something and this and that.',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', 'Sample message');
message = { _id: res.body.message._id };
});
it('should not parse urls when parseUrls=false is provided', async () => {
return request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: apiUrl,
parseUrls: false,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', apiUrl);
expect(res.body.message).to.not.have.property('urls');
});
});
it('should parse urls when parseUrls=true is provided', async () => {
return request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: apiUrl,
parseUrls: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', apiUrl);
expect(res.body.message).to.have.property('urls');
});
});
it('should parse urls when parseUrls is not provided', async () => {
return request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: apiUrl,
parseUrls: undefined,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', apiUrl);
expect(res.body.message).to.have.property('urls');
});
});
describe('text message allowed size', () => {
before(async () => {
await updateSetting('Message_MaxAllowedSize', 10);
});
after(async () => {
await updateSetting('Message_MaxAllowedSize', 5000);
});
it('should return an error if text parameter surpasses the maximum allowed size', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: '#general',
text: 'Text to test max limit allowed',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-message-size-exceeded');
});
it('should return an error if text parameter in the first attachment surpasses the maximum allowed size', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Yay!',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Text to test max limit allowed',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-message-size-exceeded');
});
it('should return an error if text parameter in any of the attachments surpasses the maximum allowed size', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Yay!',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Yay!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [],
},
{
color: '#ff0000',
text: 'Text to large to test max limit allowed',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-message-size-exceeded');
});
it('should pass if any text parameter length does not surpasses the maximum allowed size', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Sample',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
title: 'Test',
value: 'Testing out something or other',
},
{
short: true,
title: 'Another Test',
value: '[Link](https://google.com/) something and this and that.',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', 'Sample');
});
});
describe('Archived rooms', () => {
let archivedChannel: IRoom;
before(async () => {
archivedChannel = (await createRoom({ type: 'c', name: `chat.api-archived-post-test-${Date.now()}` })).body.channel;
await request.post(api('channels.archive')).set(credentials).send({ roomId: archivedChannel._id });
});
after(async () => {
await request.post(api('channels.unarchive')).set(credentials).send({ roomId: archivedChannel._id });
await deleteRoom({ type: 'c', roomId: archivedChannel._id });
});
it('should fail to post a message to an archived room', async () => {
const res = await request
.post(api('chat.postMessage'))
.set(credentials)
.send({
roomId: archivedChannel._id,
text: 'This message should not be posted',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'room_is_archived');
});
});
});
describe('/chat.getMessage', () => {
it('should retrieve the message successfully', async () => {
const res = await request
.get(api('chat.getMessage'))
.set(credentials)
.query({
msgId: message._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message._id', message._id);
});
});
describe('/chat.sendMessage', () => {
it("should throw an error when the required param 'rid' is not sent", async () => {
const res = await request
.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', "The 'rid' property on the message object is missing.");
});
describe('should throw an error when the sensitive properties contain malicious XSS values', () => {
it('attachment.message_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
alias: 'Gruggy',
text: 'Sample message',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.author_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
author_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.title_link', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'javascript:alert("xss")',
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
it('attachment.action.url', () =>
void request
.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
title: 'Attachment Example',
title_link: 'https://youtube.com',
actions: [
{
type: 'button',
text: 'Text',
url: 'javascript:alert("xss")',
},
],
},
],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
}));
});
it('should throw an error when it has some properties with the wrong type(attachments.title_link_download, attachments.fields, message_link)', async () => {
const res = await request
.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
channel: testChannel.name,
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 12,
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: 'https://youtube.com',
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: '',
},
],
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
it('should send a message successfully', (done) => {
message._id = `id-${Date.now()}`;
void request
.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
_id: message._id,
rid: testChannel._id,
msg: 'Sample message',
emoji: ':smirk:',
attachments: [
{
color: '#ff0000',
text: 'Yay for gruggy!',
ts: '2016-12-09T16:53:06.761Z',
thumb_url: 'http://res.guggy.com/logo_128.png',
message_link: 'https://google.com',
collapsed: false,
author_name: 'Bradley Hilton',
author_link: 'https://rocket.chat/',
author_icon: 'https://avatars.githubusercontent.com/u/850391?v=3',
title: 'Attachment Example',
title_link: 'https://youtube.com',
title_link_download: true,
image_url: 'http://res.guggy.com/logo_128.png',
audio_url: 'http://www.w3schools.com/tags/horse.mp3',
video_url: 'http://www.w3schools.com/tags/movie.mp4',
fields: [
{
short: true,
title: 'Test',
value: 'Testing out something or other',
},
{
short: true,
title: 'Another Test',
value: '[Link](https://google.com/) something and this and that.',
},
],
},
],
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', 'Sample message');
})
.end(done);
});
describe('Bad words filter', () => {
before(() =>
Promise.all([updateSetting('Message_AllowBadWordsFilter', true), updateSetting('Message_BadWordsFilterList', 'badword,badword2')]),
);
after(() => Promise.all([updateSetting('Message_AllowBadWordsFilter', false), updateSetting('Message_BadWordsFilterList', '')]));
it('should censor bad words on send', async () => {
const badMessage = {
_id: Random.id(),
rid: testChannel._id,
msg: 'This message has badword badword2',
};
await request
.post(api('chat.sendMessage'))
.set(credentials)
.send({ message: badMessage })
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('message');
const { message } = res.body;
expect(message).to.have.property('msg', 'This message has ******* ********');
expect(message).to.have.property('md').to.be.an('array').that.has.lengthOf(1);
const para = message.md[0];
expect(para).to.have.property('value').to.be.an('array').that.has.lengthOf(1);
const text = para.value[0];
expect(text).to.have.property('value', 'This message has ******* ********');
});
});
});
describe('Archived rooms', () => {
let archivedChannel: IRoom;
before(async () => {
archivedChannel = (await createRoom({ type: 'c', name: `chat.api-archived-test-${Date.now()}` })).body.channel;
await request.post(api('channels.archive')).set(credentials).send({ roomId: archivedChannel._id });
});
after(async () => {
await request.post(api('channels.unarchive')).set(credentials).send({ roomId: archivedChannel._id });
await deleteRoom({ type: 'c', roomId: archivedChannel._id });
});
it('should fail to send a message to an archived room', async () => {
const res = await request
.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
rid: archivedChannel._id,
msg: 'This message should not be sent',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'room_is_archived');
});
});
describe('oembed', () => {
// the TEST_MODE oembed provider (server/services/messages/lib/oembed/providers.ts) resolves /video/ URLs through GET /oembed on the mock-server
const mockVideoUrl = (id: string) => `http://mock-server.dev:8080/video/${id}`;
const mockPageUrl = (id: string) => `http://mock-server.dev:8080/page/${id}`;
const mockOembedEndpoint = { method: 'GET', path: '/oembed' };
// the whole oembed pipeline runs against the local mock-server, so a fixed wait suffices — exceeding it means a CI perf problem worth surfacing
const oembedProcessingMs = 500;
const youtubeOembedResponse = {
title: 'Rocket.Chat Demo',
author_name: 'Rocket.Chat',
author_url: 'https://www.youtube.com/@RocketChatApp',
type: 'video',
height: 150,
width: 200,
version: '1.0',
provider_name: 'YouTube',
provider_url: 'https://www.youtube.com/',
thumbnail_height: 360,
thumbnail_width: 480,
thumbnail_url: 'https://i.ytimg.com/vi/T2v29gK8fP4/hqdefault.jpg',
html: '',
};
const spotifyOembedResponse = {
html: '',
iframe_url: 'https://open.spotify.com/embed/track/4cOdK2wGLETKBW3PvgPWqT?utm_source=oembed',
width: 456,
height: 152,
version: '1.0',
provider_name: 'Spotify',
provider_url: 'https://spotify.com',
type: 'rich',
title: 'Never Gonna Give You Up',
thumbnail_url: 'https://image-cdn-ak.spotifycdn.com/image/ab67616d00001e0215ebbedaacef61af244262a8',
thumbnail_width: 300,
thumbnail_height: 300,
};
let ytEmbedMsgId: IMessage['_id'];
let imgUrlMsgId: IMessage['_id'];
before(async () => {
const healthy = await mockServerHealthy();
expect(healthy, 'mock-server is not reachable — ensure it is running').to.be.true;
await mockServerReset();
});
// programmed responses queue per key and an infinite (times: 0) entry never expires, so each test starts from a clean mock
afterEach(() => mockServerReset());
before(() =>
Promise.all([
updateSetting('API_EmbedIgnoredHosts', ''),
updateSetting('API_EmbedSafePorts', '80, 443, 3000, 8080'),
updateSetting('SSRF_Allowlist', '127.0.0.1:3000, mock-server.dev'),
]),
);
after(() =>
Promise.all([
updateSetting('API_EmbedIgnoredHosts', 'localhost, 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16'),
updateSetting('API_EmbedSafePorts', '80, 443'),
updateSetting('SSRF_Allowlist', ''),
]),
);
before(async () => {
await mockServerSet(mockOembedEndpoint.method, mockOembedEndpoint.path, youtubeOembedResponse);
const ytEmbedMsgPayload = {
_id: `id-${Date.now()}`,
rid: testChannel._id,
msg: mockVideoUrl('embed-max-width'),
emoji: ':smirk:',
};
const ytPostResponse = await request.post(api('chat.sendMessage')).set(credentials).send({ message: ytEmbedMsgPayload });
ytEmbedMsgId = ytPostResponse.body.message._id;
});
before(async () => {
const imgUrlMsgPayload = {
_id: `id-${Date.now()}1`,
rid: testChannel._id,
msg: 'http://127.0.0.1:3000/images/logo/logo.png',
emoji: ':smirk:',
};
const imgUrlResponse = await request.post(api('chat.sendMessage')).set(credentials).send({ message: imgUrlMsgPayload });
imgUrlMsgId = imgUrlResponse.body.message._id;
});
it('should have an iframe oembed with style max-width', async () => {
await retry(
'Oembed is generated async thats why the retry is required',
async () => {
await request
.get(api('chat.getMessage'))
.set(credentials)
.query({
msgId: ytEmbedMsgId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('message').to.have.property('urls').to.be.an('array').that.is.not.empty;
expect(res.body.message.urls[0])
.to.have.property('meta')
.to.have.property('oembedHtml')
.to.have.string('