fix: imported fixes 2025-12-19 (#37874) · RocketChat/Rocket.Chat@56f1239 · GitHub
Skip to content

Commit 56f1239

Browse files
fix: imported fixes 2025-12-19 (#37874)
Co-authored-by: Ricardo Garim <rswarovsky@gmail.com>
1 parent c732149 commit 56f1239

5 files changed

Lines changed: 46 additions & 15 deletions

File tree

‎.changeset/fast-ligers-unite.md‎

Lines changed: 5 additions & 0 deletions

‎apps/meteor/app/api/server/lib/isValidQuery.ts‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@ export const isValidQuery: {
2323
const verifyQuery = (query: Query, allowedAttributes: string[], allowedOperations: string[], parent = ''): boolean => {
2424
return Object.entries(removeDangerousProps(query)).every(([key, value]) => {
2525
const path = parent ? `${parent}.${key}` : key;
26-
if (parent === '' && path.startsWith('$')) {
27-
if (!allowedOperations.includes(path)) {
28-
isValidQuery.errors.push(`Invalid operation: ${path}`);
26+
if (key.startsWith('$')) {
27+
if (!allowedOperations.includes(key)) {
28+
isValidQuery.errors.push(`Invalid operation: ${key}`);
2929
return false;
3030
}
31-
if (!Array.isArray(value)) {
32-
isValidQuery.errors.push(`Invalid parameter for operation: ${path} : ${value}`);
33-
return false;
31+
32+
if (Array.isArray(value)) {
33+
return value.every((v) => verifyQuery(v, allowedAttributes, allowedOperations));
3434
}
35-
return value.every((v) => verifyQuery(v, allowedAttributes, allowedOperations));
35+
36+
if (value instanceof Object) {
37+
return verifyQuery(value, allowedAttributes, allowedOperations, path);
38+
}
39+
40+
// handles primitive values (strings, numbers, booleans, etc.)
41+
return true;
3642
}
3743

3844
if (

‎apps/meteor/app/api/server/v1/users.ts‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import {
5050
} from '../../../lib/server/functions/checkUsernameAvailability';
5151
import { deleteUser } from '../../../lib/server/functions/deleteUser';
5252
import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser';
53-
import { getFullUserDataByIdOrUsernameOrImportId } from '../../../lib/server/functions/getFullUserData';
53+
import { getFullUserDataByIdOrUsernameOrImportId, defaultFields, fullFields } from '../../../lib/server/functions/getFullUserData';
5454
import { generateUsernameSuggestion } from '../../../lib/server/functions/getUsernameSuggestion';
5555
import { saveCustomFields } from '../../../lib/server/functions/saveCustomFields';
5656
import { saveCustomFieldsWithoutValidation } from '../../../lib/server/functions/saveCustomFieldsWithoutValidation';
@@ -503,13 +503,18 @@ API.v1.addRoute(
503503
const { offset, count } = await getPaginationItems(this.queryParams);
504504
const { sort, fields, query } = await this.parseJsonQuery();
505505

506-
const nonEmptyQuery = getNonEmptyQuery(query, await hasPermissionAsync(this.userId, 'view-full-other-user-info'));
507506
const nonEmptyFields = getNonEmptyFields(fields);
508507

509508
const inclusiveFields = getInclusiveFields(nonEmptyFields);
510509

511510
const inclusiveFieldsKeys = Object.keys(inclusiveFields);
512511

512+
const hasUserQuery = query && Object.keys(query).length > 0;
513+
514+
const nonEmptyQuery = getNonEmptyQuery(query, await hasPermissionAsync(this.userId, 'view-full-other-user-info'));
515+
516+
// if user provided a query, validate it with their allowed operators
517+
// otherwise we use the default query (with $regex and $options)
513518
if (
514519
!isValidQuery(
515520
nonEmptyQuery,
@@ -521,7 +526,7 @@ API.v1.addRoute(
521526
inclusiveFieldsKeys.includes('type') && 'type.*',
522527
inclusiveFieldsKeys.includes('customFields') && 'customFields.*',
523528
].filter(Boolean) as string[],
524-
this.queryOperations,
529+
hasUserQuery ? this.queryOperations : [...this.queryOperations, '$regex', '$options'],
525530
)
526531
) {
527532
throw new Meteor.Error('error-invalid-query', isValidQuery.errors.join('\n'));
@@ -1145,8 +1150,13 @@ API.v1.addRoute(
11451150
const selector: { exceptions: Required<IUser>['username'][]; conditions: Filter<IUser>; term: string } = JSON.parse(selectorRaw);
11461151

11471152
try {
1148-
if (selector?.conditions && !isValidQuery(selector.conditions, ['*'], ['$or', '$and'])) {
1149-
throw new Error('error-invalid-query');
1153+
if (selector?.conditions) {
1154+
const canViewFullInfo = await hasPermissionAsync(this.userId, 'view-full-other-user-info');
1155+
const allowedFields = canViewFullInfo ? [...Object.keys(defaultFields), ...Object.keys(fullFields)] : Object.keys(defaultFields);
1156+
1157+
if (!isValidQuery(selector.conditions, allowedFields, ['$and', '$ne', '$exists'])) {
1158+
throw new Error('error-invalid-query');
1159+
}
11501160
}
11511161
} catch (e) {
11521162
return API.v1.failure(e);

‎apps/meteor/app/lib/server/functions/getFullUserData.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { settings } from '../../../settings/server';
77

88
const logger = new Logger('getFullUserData');
99

10-
const defaultFields = {
10+
export const defaultFields = {
1111
name: 1,
1212
username: 1,
1313
nickname: 1,
@@ -25,7 +25,7 @@ const defaultFields = {
2525
abacAttributes: 1,
2626
} as const;
2727

28-
const fullFields = {
28+
export const fullFields = {
2929
emails: 1,
3030
phone: 1,
3131
statusConnection: 1,

‎apps/meteor/tests/unit/app/api/server/v1/lib/isValidQuery.spec.ts‎

Lines changed: 11 additions & 1 deletion

0 commit comments

Comments
 (0)