Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
569 lines
18 KiB
TypeScript
569 lines
18 KiB
TypeScript
import { Team } from '@rocket.chat/core-services';
|
|
import type { IRoom, IRoomWithRetentionPolicy, IUser, MessageTypesValues, ITeam, RequiredField } from '@rocket.chat/core-typings';
|
|
import { TeamType } from '@rocket.chat/core-typings';
|
|
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
|
import { Rooms, Users } from '@rocket.chat/models';
|
|
import { Match } from 'meteor/check';
|
|
import { Meteor } from 'meteor/meteor';
|
|
|
|
import { RoomSettingsEnum } from '../../../definition/IRoomTypeConfig';
|
|
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
|
import { isABACManagedRoom } from '../../lib/authorization/isABACManagedRoom';
|
|
import { SystemLogger } from '../../lib/logger/system';
|
|
import { updateAndNotifyParentRoomWithParentMessage } from '../../lib/messaging/discussions/updateAndNotifyParentRoomWithParentMessage';
|
|
import { notifyOnRoomChangedById } from '../../lib/notifyListener';
|
|
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
|
import { setRoomAvatar } from '../../lib/rooms/setRoomAvatar';
|
|
import { saveReactWhenReadOnly } from '../../lib/rooms/settings/saveReactWhenReadOnly';
|
|
import { saveRoomAnnouncement } from '../../lib/rooms/settings/saveRoomAnnouncement';
|
|
import { saveRoomCustomFields } from '../../lib/rooms/settings/saveRoomCustomFields';
|
|
import { saveRoomDescription } from '../../lib/rooms/settings/saveRoomDescription';
|
|
import { saveRoomEncrypted } from '../../lib/rooms/settings/saveRoomEncrypted';
|
|
import { saveRoomName } from '../../lib/rooms/settings/saveRoomName';
|
|
import { saveRoomReadOnly } from '../../lib/rooms/settings/saveRoomReadOnly';
|
|
import { saveRoomSystemMessages } from '../../lib/rooms/settings/saveRoomSystemMessages';
|
|
import { saveRoomTopic } from '../../lib/rooms/settings/saveRoomTopic';
|
|
import { saveRoomType } from '../../lib/rooms/settings/saveRoomType';
|
|
import { settings } from '../../settings';
|
|
|
|
type RoomSettings = {
|
|
roomAvatar: string;
|
|
featured: boolean;
|
|
roomName: string | undefined;
|
|
roomTopic: string;
|
|
roomAnnouncement: string;
|
|
roomCustomFields: Record<string, any>;
|
|
roomDescription: string;
|
|
roomType: IRoom['t'];
|
|
readOnly: boolean;
|
|
reactWhenReadOnly: boolean;
|
|
systemMessages: MessageTypesValues[];
|
|
default: boolean;
|
|
joinCode: string;
|
|
retentionEnabled: boolean;
|
|
retentionMaxAge: number;
|
|
retentionExcludePinned: boolean;
|
|
retentionFilesOnly: boolean;
|
|
retentionIgnoreThreads: boolean;
|
|
retentionOverrideGlobal: boolean;
|
|
encrypted: boolean;
|
|
favorite: {
|
|
favorite: boolean;
|
|
defaultValue: boolean;
|
|
};
|
|
};
|
|
|
|
type RoomSettingsValidators = {
|
|
[TRoomSetting in keyof RoomSettings]?: (params: {
|
|
userId: IUser['_id'];
|
|
value: RoomSettings[TRoomSetting];
|
|
room: IRoom;
|
|
rid: IRoom['_id'];
|
|
}) => Promise<void> | void;
|
|
};
|
|
|
|
const hasRetentionPolicy = (room: IRoom & { retention?: any }): room is IRoomWithRetentionPolicy =>
|
|
'retention' in room && room.retention !== undefined;
|
|
|
|
const isAbacManagedTeam = (team: Partial<ITeam> | null, teamRoom: IRoom): boolean => {
|
|
return (
|
|
team?.type === TeamType.PRIVATE &&
|
|
settings.get<boolean>('ABAC_Enabled') &&
|
|
Array.isArray(teamRoom?.abacAttributes) &&
|
|
teamRoom.abacAttributes.length > 0
|
|
);
|
|
};
|
|
|
|
const guardABACManagedField = (room: IRoom, value: string | undefined, current: string | undefined, fieldName: string): void => {
|
|
if (!value && !current) {
|
|
return;
|
|
}
|
|
if (value === current) {
|
|
return;
|
|
}
|
|
if (isABACManagedRoom(room)) {
|
|
throw new Meteor.Error('error-action-not-allowed', `Editing an ABAC managed room's ${fieldName} is not allowed`, {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
};
|
|
|
|
const validators: RoomSettingsValidators = {
|
|
async default({ userId, room, value }) {
|
|
if (!(await hasPermissionAsync(userId, 'view-room-administration'))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Viewing room administration is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Viewing_room_administration',
|
|
});
|
|
}
|
|
if (isABACManagedRoom(room) && value) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Setting an ABAC managed room as default is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Viewing_room_administration',
|
|
});
|
|
}
|
|
},
|
|
async featured({ userId }) {
|
|
if (!(await hasPermissionAsync(userId, 'view-room-administration'))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Viewing room administration is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Viewing_room_administration',
|
|
});
|
|
}
|
|
},
|
|
|
|
async roomType({ userId, room, value }) {
|
|
if (value === room.t) {
|
|
return;
|
|
}
|
|
|
|
if (value === 'c' && (!room.teamId || room.teamMain) && !(await hasPermissionAsync(userId, 'create-c'))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Changing a private group to a public channel is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
|
|
if (value === 'p' && (!room.teamId || room.teamMain) && !(await hasPermissionAsync(userId, 'create-p'))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Changing a public channel to a private room is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
|
|
if (isABACManagedRoom(room) && value !== 'p') {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Changing an ABAC managed private room to public is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
|
|
if (!room.teamId || room.teamMain) {
|
|
return;
|
|
}
|
|
const team = await Team.getInfoById(room.teamId);
|
|
|
|
if (value === 'c' && !(await hasPermissionAsync(userId, 'create-team-channel', team?.roomId))) {
|
|
throw new Meteor.Error('error-action-not-allowed', `Changing a team's private group to a public channel is not allowed`, {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
|
|
if (value === 'p' && !(await hasPermissionAsync(userId, 'create-team-group', team?.roomId))) {
|
|
throw new Meteor.Error('error-action-not-allowed', `Changing a team's public channel to a private room is not allowed`, {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
|
|
if (isAbacManagedTeam(team, room) && value !== 'p') {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Changing an ABAC managed private team room to public is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Type',
|
|
});
|
|
}
|
|
},
|
|
async roomAnnouncement({ room, value }) {
|
|
guardABACManagedField(room, value, room.announcement, 'announcement');
|
|
},
|
|
async roomTopic({ room, value }) {
|
|
guardABACManagedField(room, value, room.topic, 'topic');
|
|
},
|
|
async roomDescription({ room, value }) {
|
|
guardABACManagedField(room, value, room.description, 'description');
|
|
},
|
|
async encrypted({ userId, value, room, rid }) {
|
|
if (value !== room.encrypted) {
|
|
if (!(await roomCoordinator.getRoomDirectives(room.t).allowRoomSettingChange(room, RoomSettingsEnum.E2E))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Only groups or direct channels can enable encryption', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Encrypted',
|
|
});
|
|
}
|
|
|
|
if (room.t !== 'd' && !(await hasPermissionAsync(userId, 'toggle-room-e2e-encryption', rid))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'You do not have permission to toggle E2E encryption', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Change_Room_Encrypted',
|
|
});
|
|
}
|
|
}
|
|
},
|
|
async retentionEnabled({ userId, value, room, rid }) {
|
|
if (
|
|
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
|
|
(!hasRetentionPolicy(room) || value !== room.retention.enabled)
|
|
) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
async retentionMaxAge({ userId, value, room, rid }) {
|
|
if (
|
|
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
|
|
(!hasRetentionPolicy(room) || value !== room.retention.maxAge)
|
|
) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
async retentionExcludePinned({ userId, value, room, rid }) {
|
|
if (
|
|
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
|
|
(!hasRetentionPolicy(room) || value !== room.retention.excludePinned)
|
|
) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
async retentionFilesOnly({ userId, value, room, rid }) {
|
|
if (
|
|
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
|
|
(!hasRetentionPolicy(room) || value !== room.retention.filesOnly)
|
|
) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
async retentionIgnoreThreads({ userId, value, room, rid }) {
|
|
if (
|
|
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
|
|
(!hasRetentionPolicy(room) || value !== room.retention.ignoreThreads)
|
|
) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
async roomAvatar({ userId, rid }) {
|
|
if (!(await hasPermissionAsync(userId, 'edit-room-avatar', rid))) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing a room avatar is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
},
|
|
};
|
|
|
|
type RoomSettingsSavers = {
|
|
[TRoomSetting in keyof RoomSettings]?: (params: {
|
|
userId: IUser['_id'];
|
|
user: RequiredField<IUser, 'username' | 'name'>;
|
|
value: RoomSettings[TRoomSetting];
|
|
room: IRoom;
|
|
rid: IRoom['_id'];
|
|
}) => void | Promise<void>;
|
|
};
|
|
|
|
const settingSavers: RoomSettingsSavers = {
|
|
async roomName({ value, rid, user, room }) {
|
|
if (!(await saveRoomName(rid, value, user))) {
|
|
return;
|
|
}
|
|
|
|
if (room.teamId && room.teamMain) {
|
|
void Team.update(user._id, room.teamId, {
|
|
type: room.t === 'c' ? TeamType.PUBLIC : TeamType.PRIVATE,
|
|
name: value,
|
|
updateRoom: false,
|
|
});
|
|
}
|
|
},
|
|
async roomTopic({ value, room, rid, user }) {
|
|
if (!value && !room.topic) {
|
|
return;
|
|
}
|
|
if (value !== room.topic) {
|
|
await saveRoomTopic(rid, value, user);
|
|
}
|
|
},
|
|
async roomAnnouncement({ value, room, rid, user }) {
|
|
if (!value && !room.announcement) {
|
|
return;
|
|
}
|
|
if (value !== room.announcement) {
|
|
await saveRoomAnnouncement(rid, value, user);
|
|
}
|
|
},
|
|
async roomCustomFields({ value, room, rid }) {
|
|
if (value !== room.customFields) {
|
|
await saveRoomCustomFields(rid, value);
|
|
}
|
|
},
|
|
async roomDescription({ value, room, rid, user }) {
|
|
if (!value && !room.description) {
|
|
return;
|
|
}
|
|
if (value !== room.description) {
|
|
await saveRoomDescription(rid, value, user);
|
|
}
|
|
},
|
|
async roomType({ value, room, rid, user }) {
|
|
if (value === room.t) {
|
|
return;
|
|
}
|
|
|
|
if (!(await saveRoomType(rid, value, user))) {
|
|
return;
|
|
}
|
|
|
|
if (room.teamId && room.teamMain) {
|
|
const type = value === 'c' ? TeamType.PUBLIC : TeamType.PRIVATE;
|
|
void Team.update(user._id, room.teamId, { type, updateRoom: false });
|
|
}
|
|
},
|
|
async readOnly({ value, room, rid, user }) {
|
|
if (value !== room.ro) {
|
|
await saveRoomReadOnly(rid, value, user);
|
|
}
|
|
},
|
|
async reactWhenReadOnly({ value, room, rid, user }) {
|
|
if (value !== room.reactWhenReadOnly) {
|
|
await saveReactWhenReadOnly(rid, value, user);
|
|
}
|
|
},
|
|
async systemMessages({ value, room, rid }) {
|
|
if (JSON.stringify(value) !== JSON.stringify(room.sysMes)) {
|
|
await saveRoomSystemMessages(rid, value);
|
|
if (room?.prid) {
|
|
try {
|
|
await updateAndNotifyParentRoomWithParentMessage({ ...room, sysMes: value });
|
|
} catch (err) {
|
|
SystemLogger.error({ msg: 'Failed to propagate discussion metadata', err, rid });
|
|
}
|
|
}
|
|
}
|
|
},
|
|
async joinCode({ value, rid }) {
|
|
await Rooms.setJoinCodeById(rid, String(value));
|
|
},
|
|
async default({ value, rid }) {
|
|
await Rooms.saveDefaultById(rid, value);
|
|
},
|
|
async featured({ value, rid }) {
|
|
await Rooms.saveFeaturedById(rid, value);
|
|
},
|
|
async retentionEnabled({ value, rid }) {
|
|
await Rooms.saveRetentionEnabledById(rid, value);
|
|
},
|
|
async retentionMaxAge({ value, rid }) {
|
|
await Rooms.saveRetentionMaxAgeById(rid, value);
|
|
},
|
|
async retentionExcludePinned({ value, rid }) {
|
|
await Rooms.saveRetentionExcludePinnedById(rid, value);
|
|
},
|
|
async retentionFilesOnly({ value, rid }) {
|
|
await Rooms.saveRetentionFilesOnlyById(rid, value);
|
|
},
|
|
async retentionIgnoreThreads({ value, rid }) {
|
|
await Rooms.saveRetentionIgnoreThreadsById(rid, value);
|
|
},
|
|
async retentionOverrideGlobal({ value, rid }) {
|
|
await Rooms.saveRetentionOverrideGlobalById(rid, value);
|
|
},
|
|
async encrypted({ value, room, rid, user }) {
|
|
await saveRoomEncrypted(rid, value, user, Boolean(room.encrypted) !== Boolean(value));
|
|
},
|
|
async favorite({ value, rid }) {
|
|
await Rooms.saveFavoriteById(rid, value.favorite, value.defaultValue);
|
|
},
|
|
async roomAvatar({ value, rid, user }) {
|
|
await setRoomAvatar(rid, value, user);
|
|
},
|
|
};
|
|
|
|
declare module '@rocket.chat/ddp-client' {
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
interface ServerMethods {
|
|
saveRoomSettings(rid: IRoom['_id'], settings: Partial<RoomSettings>): Promise<{ result: true; rid: IRoom['_id'] }>;
|
|
saveRoomSettings<RoomSettingName extends keyof RoomSettings>(
|
|
rid: IRoom['_id'],
|
|
setting: RoomSettingName,
|
|
value: RoomSettings[RoomSettingName],
|
|
): Promise<{ result: true; rid: IRoom['_id'] }>;
|
|
}
|
|
}
|
|
|
|
const fields: (keyof RoomSettings)[] = [
|
|
'roomAvatar',
|
|
'featured',
|
|
'roomName',
|
|
'roomTopic',
|
|
'roomAnnouncement',
|
|
'roomCustomFields',
|
|
'roomDescription',
|
|
'roomType',
|
|
'readOnly',
|
|
'reactWhenReadOnly',
|
|
'systemMessages',
|
|
'default',
|
|
'joinCode',
|
|
'retentionEnabled',
|
|
'retentionMaxAge',
|
|
'retentionExcludePinned',
|
|
'retentionFilesOnly',
|
|
'retentionIgnoreThreads',
|
|
'retentionOverrideGlobal',
|
|
'encrypted',
|
|
'favorite',
|
|
];
|
|
|
|
const validate = <TRoomSetting extends keyof RoomSettings>(
|
|
setting: TRoomSetting,
|
|
params: {
|
|
userId: IUser['_id'];
|
|
value: RoomSettings[TRoomSetting];
|
|
room: IRoom;
|
|
rid: IRoom['_id'];
|
|
},
|
|
) => {
|
|
const validator = validators[setting];
|
|
return validator?.(params);
|
|
};
|
|
|
|
async function save<TRoomSetting extends keyof RoomSettings>(
|
|
setting: TRoomSetting,
|
|
params: {
|
|
userId: IUser['_id'];
|
|
user: RequiredField<IUser, 'username' | 'name'>;
|
|
value: RoomSettings[TRoomSetting];
|
|
room: IRoom;
|
|
rid: IRoom['_id'];
|
|
},
|
|
) {
|
|
const saver = settingSavers[setting];
|
|
await saver?.(params);
|
|
}
|
|
|
|
export async function saveRoomSettings(
|
|
userId: IUser['_id'],
|
|
rid: IRoom['_id'],
|
|
settings: Partial<RoomSettings>,
|
|
): Promise<{ result: true; rid: IRoom['_id'] }>;
|
|
export async function saveRoomSettings<RoomSettingName extends keyof RoomSettings>(
|
|
userId: IUser['_id'],
|
|
rid: IRoom['_id'],
|
|
setting: RoomSettingName,
|
|
value: RoomSettings[RoomSettingName],
|
|
): Promise<{ result: true; rid: IRoom['_id'] }>;
|
|
export async function saveRoomSettings(
|
|
userId: IUser['_id'],
|
|
rid: IRoom['_id'],
|
|
settings: Partial<RoomSettings> | keyof RoomSettings,
|
|
value?: RoomSettings[keyof RoomSettings],
|
|
): Promise<{ result: true; rid: IRoom['_id'] }> {
|
|
if (!userId) {
|
|
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
|
function: 'RocketChat.saveRoomName',
|
|
});
|
|
}
|
|
if (!Match.test(rid, String)) {
|
|
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
|
|
method: 'saveRoomSettings',
|
|
});
|
|
}
|
|
|
|
if (typeof settings !== 'object') {
|
|
settings = {
|
|
[settings]: value,
|
|
};
|
|
}
|
|
|
|
if (!Object.keys(settings).every((key) => fields.includes(key as keyof typeof settings))) {
|
|
throw new Meteor.Error('error-invalid-settings', 'Invalid settings provided', {
|
|
method: 'saveRoomSettings',
|
|
});
|
|
}
|
|
|
|
const room = await Rooms.findOneById(rid);
|
|
|
|
if (!room) {
|
|
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
|
|
method: 'saveRoomSettings',
|
|
});
|
|
}
|
|
|
|
if (!(await hasPermissionAsync(userId, 'edit-room', rid))) {
|
|
if (!(Object.keys(settings).includes('encrypted') && room.t === 'd')) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing room is not allowed', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
settings = { encrypted: settings.encrypted };
|
|
}
|
|
|
|
if (room.broadcast && (settings.readOnly || settings.reactWhenReadOnly)) {
|
|
throw new Meteor.Error('error-action-not-allowed', 'Editing readOnly/reactWhenReadOnly are not allowed for broadcast rooms', {
|
|
method: 'saveRoomSettings',
|
|
action: 'Editing_room',
|
|
});
|
|
}
|
|
|
|
const user = await Users.findOneById(userId, { projection: { username: 1, name: 1 } });
|
|
if (!user) {
|
|
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
|
method: 'saveRoomSettings',
|
|
});
|
|
}
|
|
|
|
// validations
|
|
for (const setting of Object.keys(settings) as (keyof RoomSettings)[]) {
|
|
await validate(setting, {
|
|
userId,
|
|
value: settings[setting],
|
|
room,
|
|
rid,
|
|
});
|
|
|
|
if (setting === 'retentionOverrideGlobal' && settings.retentionOverrideGlobal === false) {
|
|
delete settings.retentionMaxAge;
|
|
delete settings.retentionExcludePinned;
|
|
delete settings.retentionFilesOnly;
|
|
delete settings.retentionIgnoreThreads;
|
|
}
|
|
}
|
|
|
|
// saving data
|
|
for (const setting of Object.keys(settings) as (keyof RoomSettings)[]) {
|
|
await save(setting, {
|
|
userId,
|
|
user: user as RequiredField<IUser, 'username' | 'name'>,
|
|
value: settings[setting],
|
|
room,
|
|
rid,
|
|
});
|
|
}
|
|
|
|
void notifyOnRoomChangedById(rid);
|
|
|
|
return {
|
|
result: true,
|
|
rid: room._id,
|
|
};
|
|
}
|
|
|
|
Meteor.methods<ServerMethods>({
|
|
saveRoomSettings: (...args) => {
|
|
const userId = Meteor.userId();
|
|
if (!userId) {
|
|
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
|
function: 'RocketChat.saveRoomName',
|
|
});
|
|
}
|
|
|
|
return saveRoomSettings(userId, ...args);
|
|
},
|
|
});
|