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

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View 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;
};

View 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,
});
};

View 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;
}),
);
}

View 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;
};

View 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);
};

View 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));
};