Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 6.4.1 #30552

Merged
merged 12 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bump-patch-1696276280745.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Bump @rocket.chat/meteor version.
5 changes: 5 additions & 0 deletions .changeset/dull-trainers-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

fix: Remove model-level query restrictions for monitors
5 changes: 5 additions & 0 deletions .changeset/large-pandas-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

New setting to automatically enable autotranslate when joining rooms
5 changes: 5 additions & 0 deletions .changeset/selfish-hounds-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

fix: Monitors now able to forward a chat without taking it first
5 changes: 5 additions & 0 deletions .changeset/seven-carpets-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Add new permission to allow kick users from rooms without being a member
5 changes: 5 additions & 0 deletions .changeset/soft-cows-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

download translation files through CDN
5 changes: 5 additions & 0 deletions .changeset/sweet-feet-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

fix: user dropdown menu position on RTL layout
5 changes: 5 additions & 0 deletions .changeset/wicked-humans-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Improve cache of static files
10 changes: 9 additions & 1 deletion apps/meteor/app/api/server/v1/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,14 @@ async function createChannelValidator(params: {

async function createChannel(
userId: string,
params: { name?: string; members?: string[]; customFields?: Record<string, any>; extraData?: Record<string, any>; readOnly?: boolean },
params: {
name?: string;
members?: string[];
customFields?: Record<string, any>;
extraData?: Record<string, any>;
readOnly?: boolean;
excludeSelf?: boolean;
},
): Promise<{ channel: IRoom }> {
const readOnly = typeof params.readOnly !== 'undefined' ? params.readOnly : false;
const id = await createChannelMethod(
Expand All @@ -681,6 +688,7 @@ async function createChannel(
readOnly,
params.customFields,
params.extraData,
params.excludeSelf,
);

return {
Expand Down
118 changes: 64 additions & 54 deletions apps/meteor/app/api/server/v1/groups.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Team } from '@rocket.chat/core-services';
import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { check, Match } from 'meteor/check';
Expand Down Expand Up @@ -26,29 +26,7 @@ import { getLoggedInUser } from '../helpers/getLoggedInUser';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams';

// Returns the private group subscription IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
async function findPrivateGroupByIdOrName({
params,
checkedArchived = true,
userId,
}: {
params:
| {
roomId?: string;
}
| {
roomName?: string;
};
userId: string;
checkedArchived?: boolean;
}): Promise<{
rid: string;
open: boolean;
ro: boolean;
t: string;
name: string;
broadcast: boolean;
}> {
async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise<IRoom> {
if (
(!('roomId' in params) && !('roomName' in params)) ||
('roomId' in params && !(params as { roomId?: string }).roomId && 'roomName' in params && !(params as { roomName?: string }).roomName)
Expand All @@ -68,17 +46,48 @@ async function findPrivateGroupByIdOrName({
broadcast: 1,
},
};
let room: IRoom | null = null;
if ('roomId' in params) {
room = await Rooms.findOneById(params.roomId || '', roomOptions);
} else if ('roomName' in params) {
room = await Rooms.findOneByName(params.roomName || '', roomOptions);
}

const room = await (() => {
if ('roomId' in params) {
return Rooms.findOneById(params.roomId || '', roomOptions);
}
if ('roomName' in params) {
return Rooms.findOneByName(params.roomName || '', roomOptions);
}
})();

if (!room || room.t !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

return room;
}

// Returns the private group subscription IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
async function findPrivateGroupByIdOrName({
params,
checkedArchived = true,
userId,
}: {
params:
| {
roomId?: string;
}
| {
roomName?: string;
};
userId: string;
checkedArchived?: boolean;
}): Promise<{
rid: string;
open: boolean;
ro: boolean;
t: string;
name: string;
broadcast: boolean;
}> {
const room = await getRoomFromParams(params);

const user = await Users.findOneById(userId, { projections: { username: 1 } });

if (!room || !user || !(await canAccessRoomAsync(room, user))) {
Expand Down Expand Up @@ -302,10 +311,6 @@ API.v1.addRoute(
{ authRequired: true },
{
async post() {
if (!(await hasPermissionAsync(this.userId, 'create-p'))) {
return API.v1.unauthorized();
}

if (!this.bodyParams.name) {
return API.v1.failure('Body param "name" is required');
}
Expand All @@ -323,24 +328,32 @@ API.v1.addRoute(

const readOnly = typeof this.bodyParams.readOnly !== 'undefined' ? this.bodyParams.readOnly : false;

const result = await createPrivateGroupMethod(
this.userId,
this.bodyParams.name,
this.bodyParams.members ? this.bodyParams.members : [],
readOnly,
this.bodyParams.customFields,
this.bodyParams.extraData,
);

const room = await Rooms.findOneById(result.rid, { projection: API.v1.defaultFieldsToExclude });
try {
const result = await createPrivateGroupMethod(
this.user,
this.bodyParams.name,
this.bodyParams.members ? this.bodyParams.members : [],
readOnly,
this.bodyParams.customFields,
this.bodyParams.extraData,
this.bodyParams.excludeSelf ?? false,
);

const room = await Rooms.findOneById(result.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
} catch (error: unknown) {
if (isMeteorError(error) && error.reason === 'error-not-allowed') {
return API.v1.unauthorized();
}
}

return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
return API.v1.internalError();
},
},
);
Expand Down Expand Up @@ -581,17 +594,14 @@ API.v1.addRoute(
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const room = await getRoomFromParams(this.bodyParams);

const user = await getUserFromParams(this.bodyParams);
if (!user?.username) {
return API.v1.failure('Invalid user');
}

await removeUserFromRoomMethod(this.userId, { rid: findResult.rid, username: user.username });
await removeUserFromRoomMethod(this.userId, { rid: room._id, username: user.username });

return API.v1.success();
},
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/apps/server/bridges/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export class AppRoomBridge extends RoomBridge {
}

private async createPrivateGroup(userId: string, room: ICoreRoom, members: string[]): Promise<string> {
return (await createPrivateGroupMethod(userId, room.name || '', members, room.ro, room.customFields, this.prepareExtraData(room))).rid;
const user = await Users.findOneById(userId);
if (!user) {
throw new Error('Invalid user');
}
return (await createPrivateGroupMethod(user, room.name || '', members, room.ro, room.customFields, this.prepareExtraData(room))).rid;
}

protected async getById(roomId: string, appId: string): Promise<IRoom> {
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/authorization/server/constant/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const permissions = [
{ _id: 'add-user-to-joined-room', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'add-user-to-any-c-room', roles: ['admin'] },
{ _id: 'add-user-to-any-p-room', roles: [] },
{ _id: 'kick-user-from-any-c-room', roles: ['admin'] },
{ _id: 'kick-user-from-any-p-room', roles: [] },
{ _id: 'api-bypass-rate-limit', roles: ['admin', 'bot', 'app'] },
{ _id: 'archive-room', roles: ['admin', 'owner'] },
{ _id: 'assign-admin-role', roles: ['admin'] },
Expand Down
41 changes: 41 additions & 0 deletions apps/meteor/app/cors/server/cors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createHash } from 'crypto';
import type http from 'http';
import type { UrlWithParsedQuery } from 'url';
import url from 'url';

import { Logger } from '@rocket.chat/logger';
Expand Down Expand Up @@ -77,6 +79,19 @@ WebApp.rawConnectHandlers.use((_req: http.IncomingMessage, res: http.ServerRespo
});

const _staticFilesMiddleware = WebAppInternals.staticFilesMiddleware;
declare module 'meteor/webapp' {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace WebApp {
function categorizeRequest(
req: http.IncomingMessage,
): { arch: string; path: string; url: UrlWithParsedQuery } & Record<string, unknown>;
}
}

// These routes already handle cache control on their own
const cacheControlledRoutes: Array<RegExp> = ['/assets', '/custom-sounds', '/emoji-custom', '/avatar', '/file-upload'].map(
(route) => new RegExp(`^${route}`, 'i'),
);

// @ts-expect-error - accessing internal property of webapp
WebAppInternals.staticFilesMiddleware = function (
Expand All @@ -86,6 +101,32 @@ WebAppInternals.staticFilesMiddleware = function (
next: NextFunction,
) {
res.setHeader('Access-Control-Allow-Origin', '*');
const { arch, path, url } = WebApp.categorizeRequest(req);

if (Meteor.isProduction && !cacheControlledRoutes.some((regexp) => regexp.test(path))) {
res.setHeader('Cache-Control', 'public, max-age=31536000');
}

// Prevent meteor_runtime_config.js to load from a different expected hash possibly causing
// a cache of the file for the wrong hash and start a client loop due to the mismatch
// of the hashes of ui versions which would be checked against a websocket response
if (path === '/meteor_runtime_config.js') {
const program = WebApp.clientPrograms[arch] as (typeof WebApp.clientPrograms)[string] & {
meteorRuntimeConfigHash?: string;
meteorRuntimeConfig: string;
};

if (!program?.meteorRuntimeConfigHash) {
program.meteorRuntimeConfigHash = createHash('sha1')
.update(JSON.stringify(encodeURIComponent(program.meteorRuntimeConfig)))
.digest('hex');
}

if (program.meteorRuntimeConfigHash !== url.query.hash) {
res.writeHead(404);
return res.end();
}
}
return _staticFilesMiddleware(staticFiles, req, res, next);
};

Expand Down
29 changes: 15 additions & 14 deletions apps/meteor/app/custom-sounds/client/lib/CustomSounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import { getURL } from '../../../utils/client';
import { sdk } from '../../../utils/client/lib/SDKClient';

const getCustomSoundId = (soundId: ICustomSound['_id']) => `custom-sound-${soundId}`;
const getAssetUrl = (asset: string, params?: Record<string, any>) => getURL(asset, params, undefined, true);

const defaultSounds = [
{ _id: 'chime', name: 'Chime', extension: 'mp3', src: getURL('sounds/chime.mp3') },
{ _id: 'door', name: 'Door', extension: 'mp3', src: getURL('sounds/door.mp3') },
{ _id: 'beep', name: 'Beep', extension: 'mp3', src: getURL('sounds/beep.mp3') },
{ _id: 'chelle', name: 'Chelle', extension: 'mp3', src: getURL('sounds/chelle.mp3') },
{ _id: 'ding', name: 'Ding', extension: 'mp3', src: getURL('sounds/ding.mp3') },
{ _id: 'droplet', name: 'Droplet', extension: 'mp3', src: getURL('sounds/droplet.mp3') },
{ _id: 'highbell', name: 'Highbell', extension: 'mp3', src: getURL('sounds/highbell.mp3') },
{ _id: 'seasons', name: 'Seasons', extension: 'mp3', src: getURL('sounds/seasons.mp3') },
{ _id: 'telephone', name: 'Telephone', extension: 'mp3', src: getURL('sounds/telephone.mp3') },
{ _id: 'outbound-call-ringing', name: 'Outbound Call Ringing', extension: 'mp3', src: getURL('sounds/outbound-call-ringing.mp3') },
{ _id: 'call-ended', name: 'Call Ended', extension: 'mp3', src: getURL('sounds/call-ended.mp3') },
{ _id: 'dialtone', name: 'Dialtone', extension: 'mp3', src: getURL('sounds/dialtone.mp3') },
{ _id: 'ringtone', name: 'Ringtone', extension: 'mp3', src: getURL('sounds/ringtone.mp3') },
{ _id: 'chime', name: 'Chime', extension: 'mp3', src: getAssetUrl('sounds/chime.mp3') },
{ _id: 'door', name: 'Door', extension: 'mp3', src: getAssetUrl('sounds/door.mp3') },
{ _id: 'beep', name: 'Beep', extension: 'mp3', src: getAssetUrl('sounds/beep.mp3') },
{ _id: 'chelle', name: 'Chelle', extension: 'mp3', src: getAssetUrl('sounds/chelle.mp3') },
{ _id: 'ding', name: 'Ding', extension: 'mp3', src: getAssetUrl('sounds/ding.mp3') },
{ _id: 'droplet', name: 'Droplet', extension: 'mp3', src: getAssetUrl('sounds/droplet.mp3') },
{ _id: 'highbell', name: 'Highbell', extension: 'mp3', src: getAssetUrl('sounds/highbell.mp3') },
{ _id: 'seasons', name: 'Seasons', extension: 'mp3', src: getAssetUrl('sounds/seasons.mp3') },
{ _id: 'telephone', name: 'Telephone', extension: 'mp3', src: getAssetUrl('sounds/telephone.mp3') },
{ _id: 'outbound-call-ringing', name: 'Outbound Call Ringing', extension: 'mp3', src: getAssetUrl('sounds/outbound-call-ringing.mp3') },
{ _id: 'call-ended', name: 'Call Ended', extension: 'mp3', src: getAssetUrl('sounds/call-ended.mp3') },
{ _id: 'dialtone', name: 'Dialtone', extension: 'mp3', src: getAssetUrl('sounds/dialtone.mp3') },
{ _id: 'ringtone', name: 'Ringtone', extension: 'mp3', src: getAssetUrl('sounds/ringtone.mp3') },
];

class CustomSoundsClass {
Expand Down Expand Up @@ -85,7 +86,7 @@ class CustomSoundsClass {
}

getURL(sound: ICustomSound) {
return getURL(`/custom-sounds/${sound._id}.${sound.extension}?_dc=${sound.random || 0}`);
return getAssetUrl(`/custom-sounds/${sound._id}.${sound.extension}`, { _dc: sound.random || 0 });
}

getList() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ const create = async ({
const discussion = await createRoom(
type,
name,
user.username as string,
user,
[...new Set(invitedUsers)].filter(Boolean),
false,
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,11 @@ export class ImportDataConverter {
return;
}
if (roomData.t === 'p') {
roomInfo = await createPrivateGroupMethod(startedByUserId, roomData.name, members, false, {}, {}, true);
const user = await Users.findOneById(startedByUserId);
if (!user) {
throw new Error('importer-channel-invalid-creator');
}
roomInfo = await createPrivateGroupMethod(user, roomData.name, members, false, {}, {}, true);
} else {
roomInfo = await createChannelMethod(startedByUserId, roomData.name, members, false, {}, {}, true);
}
Expand Down
Loading
Loading