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:
47
apps/meteor/server/lib/roles/addUserRoles.ts
Normal file
47
apps/meteor/server/lib/roles/addUserRoles.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { MeteorError } from '@rocket.chat/core-services';
|
||||
import type { IRole, IUser, IRoom } from '@rocket.chat/core-typings';
|
||||
import { Roles, Subscriptions, Users } from '@rocket.chat/models';
|
||||
|
||||
import { syncRoomRolePriorityForUserAndRoom } from './syncRoomRolePriority';
|
||||
import { validateRoleList } from './validateRoleList';
|
||||
import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../notifyListener';
|
||||
|
||||
export const addUserRolesAsync = async (userId: IUser['_id'], roles: IRole['_id'][], scope?: IRoom['_id']): Promise<boolean> => {
|
||||
if (!userId || !roles?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await validateRoleList(roles))) {
|
||||
throw new MeteorError('error-invalid-role', 'Invalid role');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) {
|
||||
throw new Error('Roles.addUserRoles method received a role scope instead of a scope value.');
|
||||
}
|
||||
|
||||
if (!Array.isArray(roles)) {
|
||||
roles = [roles];
|
||||
process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.addUserRoles: roles should be an array');
|
||||
}
|
||||
|
||||
for await (const roleId of roles) {
|
||||
const role = await Roles.findOneById<Pick<IRole, '_id' | 'scope'>>(roleId, { projection: { scope: 1 } });
|
||||
|
||||
if (!role) {
|
||||
process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${roleId} not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role.scope === 'Subscriptions' && scope) {
|
||||
const addRolesResponse = await Subscriptions.addRolesByUserId(userId, [role._id], scope);
|
||||
await syncRoomRolePriorityForUserAndRoom(userId, scope);
|
||||
if (addRolesResponse.modifiedCount) {
|
||||
void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId);
|
||||
}
|
||||
} else {
|
||||
await Users.addRolesByUserId(userId, [role._id]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
32
apps/meteor/server/lib/roles/createOrUpdateProtectedRole.ts
Normal file
32
apps/meteor/server/lib/roles/createOrUpdateProtectedRole.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { IRole, AtLeast } from '@rocket.chat/core-typings';
|
||||
import { Roles } from '@rocket.chat/models';
|
||||
|
||||
export const createOrUpdateProtectedRoleAsync = async (
|
||||
roleId: string,
|
||||
roleData: AtLeast<Omit<IRole, '_id' | 'protected'>, 'name'>,
|
||||
): Promise<void> => {
|
||||
const role = await Roles.findOneById<Pick<IRole, '_id' | 'name' | 'scope' | 'description' | 'mandatory2fa'>>(roleId, {
|
||||
projection: { name: 1, scope: 1, description: 1, mandatory2fa: 1 },
|
||||
});
|
||||
|
||||
if (role) {
|
||||
await Roles.updateById(
|
||||
roleId,
|
||||
roleData.name || role.name,
|
||||
roleData.scope || role.scope,
|
||||
roleData.description || role.description,
|
||||
roleData.mandatory2fa || role.mandatory2fa,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await Roles.insertOne({
|
||||
_id: roleId,
|
||||
scope: 'Users',
|
||||
description: '',
|
||||
mandatory2fa: false,
|
||||
...roleData,
|
||||
protected: true,
|
||||
});
|
||||
};
|
||||
43
apps/meteor/server/lib/roles/getRoomRoles.ts
Normal file
43
apps/meteor/server/lib/roles/getRoomRoles.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import { Roles, Subscriptions, Users } from '@rocket.chat/models';
|
||||
import _ from 'underscore';
|
||||
|
||||
import { settings } from '../../settings';
|
||||
|
||||
export type RoomRoles = {
|
||||
rid: IRoom['_id'];
|
||||
u: {
|
||||
_id: string;
|
||||
username: string;
|
||||
name?: string;
|
||||
};
|
||||
roles: string[];
|
||||
};
|
||||
|
||||
export async function getRoomRoles(rid: IRoom['_id']): Promise<RoomRoles[]> {
|
||||
const useRealName = settings.get('UI_Use_Real_Name') === true;
|
||||
|
||||
const roles = await Roles.find({ scope: 'Subscriptions', description: { $exists: true, $ne: '' } }).toArray();
|
||||
const subscriptions = await Subscriptions.findByRoomIdAndRoles<RoomRoles>(rid, _.pluck(roles, '_id'), {
|
||||
sort: {
|
||||
'u.username': 1 as const,
|
||||
},
|
||||
projection: {
|
||||
rid: 1,
|
||||
u: 1,
|
||||
roles: 1,
|
||||
},
|
||||
}).toArray();
|
||||
|
||||
if (!useRealName) {
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
subscriptions.map(async (subscription) => {
|
||||
const user = await Users.findOneById(subscription.u._id);
|
||||
subscription.u.name = user?.name;
|
||||
return subscription;
|
||||
}),
|
||||
);
|
||||
}
|
||||
45
apps/meteor/server/lib/roles/removeUserFromRoles.ts
Normal file
45
apps/meteor/server/lib/roles/removeUserFromRoles.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { MeteorError } from '@rocket.chat/core-services';
|
||||
import type { IRole, IUser, IRoom } from '@rocket.chat/core-typings';
|
||||
import { Users, Subscriptions, Roles } from '@rocket.chat/models';
|
||||
|
||||
import { syncRoomRolePriorityForUserAndRoom } from './syncRoomRolePriority';
|
||||
import { validateRoleList } from './validateRoleList';
|
||||
import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../notifyListener';
|
||||
|
||||
export const removeUserFromRolesAsync = async (userId: IUser['_id'], roles: IRole['_id'][], scope?: IRoom['_id']): Promise<boolean> => {
|
||||
if (!userId || !roles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const user = await Users.findOneById(userId, { projection: { _id: 1 } });
|
||||
if (!user) {
|
||||
throw new MeteorError('error-invalid-user', 'Invalid user');
|
||||
}
|
||||
|
||||
if (!(await validateRoleList(roles))) {
|
||||
throw new MeteorError('error-invalid-role', 'Invalid role');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) {
|
||||
throw new Error('Roles.removeUserRoles method received a role scope instead of a scope value.');
|
||||
}
|
||||
|
||||
for await (const roleId of roles) {
|
||||
const role = await Roles.findOneById<Pick<IRole, '_id' | 'scope'>>(roleId, { projection: { scope: 1 } });
|
||||
if (!role) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role.scope === 'Subscriptions' && scope) {
|
||||
const removeRolesResponse = await Subscriptions.removeRolesByUserId(userId, [roleId], scope);
|
||||
await syncRoomRolePriorityForUserAndRoom(userId, scope);
|
||||
if (removeRolesResponse.modifiedCount) {
|
||||
void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId);
|
||||
}
|
||||
} else {
|
||||
await Users.removeRolesByUserId(userId, [roleId]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
36
apps/meteor/server/lib/roles/syncRoomRolePriority.ts
Normal file
36
apps/meteor/server/lib/roles/syncRoomRolePriority.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { IRole, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { Subscriptions, Users } from '@rocket.chat/models';
|
||||
|
||||
import { calculateRoomRolePriorityFromRoles } from '../../../lib/roles/calculateRoomRolePriorityFromRoles';
|
||||
|
||||
/**
|
||||
* Updates the room role priority for a user in a given room.
|
||||
* If roles are provided, it uses them directly; otherwise, it fetches the subscription for the user and room.
|
||||
* @param userId The user's ID.
|
||||
* @param rid The room's ID.
|
||||
* @param roles Optional roles array. If not provided, the roles will be fetched.
|
||||
*/
|
||||
export const syncRoomRolePriorityForUserAndRoom = async (
|
||||
userId: IUser['_id'],
|
||||
rid: IRoom['_id'],
|
||||
roles?: IRole['_id'][],
|
||||
): Promise<void> => {
|
||||
const updateRolePriority = async (userId: IUser['_id'], rid: IRoom['_id'], roles: IRole['_id'][]): Promise<void> => {
|
||||
const rolePriority = calculateRoomRolePriorityFromRoles(roles);
|
||||
await Users.addRoomRolePriorityByUserId(userId, rid, rolePriority);
|
||||
};
|
||||
|
||||
if (roles) {
|
||||
return updateRolePriority(userId, rid, roles);
|
||||
}
|
||||
|
||||
const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, {
|
||||
projection: { roles: 1, u: 1, rid: 1 },
|
||||
});
|
||||
|
||||
if (!subscription?.roles) {
|
||||
return;
|
||||
}
|
||||
|
||||
return updateRolePriority(subscription.u._id, subscription.rid, subscription.roles);
|
||||
};
|
||||
13
apps/meteor/server/lib/roles/validateRoleList.ts
Normal file
13
apps/meteor/server/lib/roles/validateRoleList.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { IRole } from '@rocket.chat/core-typings';
|
||||
import { Roles } from '@rocket.chat/models';
|
||||
|
||||
export const validateRoleList = async (roleIds: IRole['_id'][]): Promise<boolean> => {
|
||||
const options = {
|
||||
projection: {
|
||||
_id: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingRoleIds = (await Roles.findInIds<Pick<IRole, '_id'>>(roleIds, options).toArray()).map(({ _id }) => _id);
|
||||
return !roleIds.find((_id) => !existingRoleIds.includes(_id));
|
||||
};
|
||||
Reference in New Issue
Block a user