snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
568
apps/meteor/server/meteor-methods/rooms/saveRoomSettings.ts
Normal file
568
apps/meteor/server/meteor-methods/rooms/saveRoomSettings.ts
Normal file
@@ -0,0 +1,568 @@
|
||||
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);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user