-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Expand file tree
/
Copy pathsendMessage.ts
More file actions
184 lines (156 loc) · 5.86 KB
/
sendMessage.ts
File metadata and controls
184 lines (156 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import { api } from '@rocket.chat/core-services';
import type { AtLeast, IMessage, IUser, IUpload } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Messages, Uploads, Users } from '@rocket.chat/models';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';
import { i18n } from '../../../../server/lib/i18n';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { API } from '../../../api/server/api';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { sendFileMessage } from '../../../file-upload/server/methods/sendFileMessage';
import { metrics } from '../../../metrics/server';
import { settings } from '../../../settings/server';
import { MessageTypes } from '../../../ui-utils/server';
import { sendMessage } from '../functions/sendMessage';
import { RateLimiter } from '../lib';
export async function executeSendMessage(uid: IUser['_id'], message: AtLeast<IMessage, 'rid'>, previewUrls?: string[]) {
if (message.tshow && !message.tmid) {
throw new Meteor.Error('invalid-params', 'tshow provided but missing tmid', {
method: 'sendMessage',
});
}
if (message.tmid && !settings.get('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'not-allowed', {
method: 'sendMessage',
});
}
if (message.ts) {
const tsDiff = Math.abs(moment(message.ts).diff(Date.now()));
if (tsDiff > 60000) {
throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', {
method: 'sendMessage',
message_ts: message.ts,
server_ts: new Date().getTime(),
});
} else if (tsDiff > 10000) {
message.ts = new Date();
}
} else {
message.ts = new Date();
}
if (message.msg) {
if (message.msg.length > (settings.get<number>('Message_MaxAllowedSize') ?? 0)) {
throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', {
method: 'sendMessage',
});
}
}
const user = await Users.findOneById(uid, {
projection: {
username: 1,
type: 1,
name: 1,
},
});
if (!user?.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
let { rid } = message;
// do not allow nested threads
if (message.tmid) {
const parentMessage = await Messages.findOneById(message.tmid, { projection: { rid: 1, tmid: 1 } });
message.tmid = parentMessage?.tmid || message.tmid;
if (parentMessage?.rid) {
rid = parentMessage?.rid;
}
}
if (!rid) {
throw new Error("The 'rid' property on the message object is missing.");
}
check(rid, String);
try {
const room = await canSendMessageAsync(rid, { uid, username: user.username, type: user.type });
if (room.encrypted && settings.get<boolean>('E2E_Enable') && !settings.get<boolean>('E2E_Allow_Unencrypted_Messages')) {
if (message.t !== 'e2e') {
throw new Meteor.Error('error-not-allowed', 'Not allowed to send un-encrypted messages in an encrypted room', {
method: 'sendMessage',
});
}
}
metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
return await sendMessage(user, message, room, false, previewUrls);
} catch (err: any) {
SystemLogger.error({ msg: 'Error sending message:', err });
const errorMessage = typeof err === 'string' ? err : err.error || err.message;
const errorContext = err.details ?? {};
void api.broadcast('notify.ephemeralMessage', uid, message.rid, {
msg: i18n.t(errorMessage, errorContext, user.language),
});
if (typeof err === 'string') {
throw new Error(err);
}
throw err;
}
}
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
sendMessage(message: AtLeast<IMessage, '_id' | 'rid' | 'msg'>, previewUrls?: string[], filesToConfirm?: string[], msgData?: any): any;
}
}
Meteor.methods<ServerMethods>({
async sendMessage(message, previewUrls, filesToConfirm, msgData) {
check(message, Object);
const uid = Meteor.userId();
if (!uid) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'sendMessage',
});
}
if (MessageTypes.isSystemMessage(message)) {
throw new Error("Cannot send system messages using 'sendMessage'");
}
if (filesToConfirm !== undefined) {
if (!(await canAccessRoomIdAsync(message.rid, uid))) {
return API.v1.unauthorized();
}
const filesarray: Partial<IUpload>[] = await Promise.all(
filesToConfirm.map(async (fileid) => {
const file = await Uploads.findOneById(fileid);
if (!file) {
throw new Meteor.Error('invalid-file');
}
return file;
}),
);
await sendFileMessage(uid, { roomId: message.rid, file: filesarray, msgData }, { parseAttachmentsForE2EE: false });
await Promise.all(filesToConfirm.map((fileid) => Uploads.confirmTemporaryFile(fileid, uid)));
let resmessage;
if (filesarray[0] !== null && filesarray[0]._id !== undefined) {
resmessage = await Messages.getMessageByFileIdAndUsername(filesarray[0]._id, uid);
}
return API.v1.success({
resmessage,
});
}
try {
return await executeSendMessage(uid, message, previewUrls);
} catch (error: any) {
if ((error.error || error.message) === 'error-not-allowed') {
throw new Meteor.Error(error.error || error.message, error.reason, {
method: 'sendMessage',
});
}
}
},
});
// Limit a user, who does not have the "bot" role, to sending 5 msgs/second
RateLimiter.limitMethod('sendMessage', 5, 1000, {
async userId(userId: IUser['_id']) {
return !(await hasPermissionAsync(userId, 'send-many-messages'));
},
});