diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index e87febe6cef2c..eaeab9a9bb108 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -205,7 +205,7 @@ API.v1.addRoute( async get() { const findResult = await findChannelByIdOrName({ params: this.queryParams }); - const roles = await executeGetRoomRoles(findResult._id, this.userId); + const roles = await executeGetRoomRoles(findResult._id, this.user); return API.v1.success({ roles, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 2437813860836..dba48f1a9ffa9 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -67,7 +67,7 @@ async function getRoomFromParams(params: { roomId?: string } | { roomName?: stri } })(); - if (!room || room.t !== 'p') { + if (room?.t !== 'p') { throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group'); } @@ -273,7 +273,7 @@ API.v1.addRoute( room = await Rooms.findOneByName(params.roomName || ''); } - if (!room || room.t !== 'p') { + if (room?.t !== 'p') { throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group'); } @@ -791,7 +791,7 @@ API.v1.addRoute( rid: findResult.rid, ...parseIds(mentionIds, 'mentions._id'), ...parseIds(starredIds, 'starred._id'), - ...(pinned && pinned.toLowerCase() === 'true' ? { pinned: true } : {}), + ...(pinned?.toLowerCase() === 'true' ? { pinned: true } : {}), _hidden: { $ne: true }, }; @@ -1192,7 +1192,7 @@ API.v1.addRoute( userId: this.userId, }); - const roles = await executeGetRoomRoles(findResult.rid, this.userId); + const roles = await executeGetRoomRoles(findResult.rid, this.user); return API.v1.success({ roles, diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index f97bd6e7e9161..30b9f362bddca 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -155,11 +155,8 @@ const DmClosePropsSchema = { roomId: { type: 'string', }, - userId: { - type: 'string', - }, }, - required: ['roomId', 'userId'], + required: ['roomId'], additionalProperties: false, }; diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index e5f2fccc87270..fe1593cc3e7c7 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -18,6 +18,7 @@ import { isRoomsInviteProps, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, + validateForbiddenErrorResponse, } from '@rocket.chat/rest-typings'; import { isTruthy } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; @@ -455,7 +456,6 @@ API.v1.addRoute( { authRequired: true /* , validateParams: isRoomsCreateDiscussionProps */ }, { async post() { - // eslint-disable-next-line @typescript-eslint/naming-convention const { prid, pmid, reply, t_name, users, encrypted, topic } = this.bodyParams; if (!prid) { return API.v1.failure('Body parameter "prid" is required.'); @@ -550,7 +550,7 @@ API.v1.addRoute( const [files, total] = await Promise.all([cursor.toArray(), totalCount]); // If the initial image was not returned in the query, insert it as the first element of the list - if (initialImage && !files.find(({ _id }) => _id === (initialImage as IUpload)._id)) { + if (initialImage && !files.find(({ _id }) => _id === initialImage._id)) { files.splice(0, 0, initialImage); } @@ -767,7 +767,7 @@ API.v1.addRoute( void dataExport.sendFile( { rid, - format: format as 'html' | 'json', + format, dateFrom: convertedDateFrom, dateTo: convertedDateTo, }, @@ -815,7 +815,7 @@ API.v1.addRoute( const [room, user] = await Promise.all([ findRoomByIdOrName({ params: { roomId }, - }) as Promise, + }), Users.findOneByIdOrUsername(userId || username), ]); @@ -1070,11 +1070,14 @@ export const roomEndpoints = API.v1 }, required: ['roles'], }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, }, }, async function () { const { rid } = this.queryParams; - const roles = await executeGetRoomRoles(rid, this.userId); + const roles = await executeGetRoomRoles(rid, this.user); return API.v1.success({ roles, @@ -1246,7 +1249,6 @@ export const roomEndpoints = API.v1 ); type RoomEndpoints = ExtractRoutesFromAPI & - ExtractRoutesFromAPI & ExtractRoutesFromAPI & ExtractRoutesFromAPI; diff --git a/apps/meteor/app/lib/server/methods/getRoomRoles.ts b/apps/meteor/app/lib/server/methods/getRoomRoles.ts index 0e271e2d64f94..b929181ee301d 100644 --- a/apps/meteor/app/lib/server/methods/getRoomRoles.ts +++ b/apps/meteor/app/lib/server/methods/getRoomRoles.ts @@ -1,4 +1,4 @@ -import type { IRoom } from '@rocket.chat/core-typings'; +import type { IRoom, IUser } from '@rocket.chat/core-typings'; import { Rooms } from '@rocket.chat/models'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -15,10 +15,10 @@ declare module '@rocket.chat/ddp-client' { } } -export const executeGetRoomRoles = async (rid: IRoom['_id'], fromUserId?: string | null) => { +export const executeGetRoomRoles = async (rid: IRoom['_id'], fromUser?: IUser | null) => { check(rid, String); - if (!fromUserId && settings.get('Accounts_AllowAnonymousRead') === false) { + if (!fromUser && settings.get('Accounts_AllowAnonymousRead') === false) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomRoles' }); } @@ -27,7 +27,7 @@ export const executeGetRoomRoles = async (rid: IRoom['_id'], fromUserId?: string throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getRoomRoles' }); } - if (fromUserId && !(await canAccessRoomAsync(room, { _id: fromUserId }))) { + if (fromUser && !(await canAccessRoomAsync(room, fromUser))) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomRoles' }); } diff --git a/apps/meteor/server/methods/loadHistory.ts b/apps/meteor/server/methods/loadHistory.ts index 44f4e8e98e7c0..d2d7342fb712f 100644 --- a/apps/meteor/server/methods/loadHistory.ts +++ b/apps/meteor/server/methods/loadHistory.ts @@ -31,36 +31,35 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async loadHistory(rid, end, limit = 20, ls, showThreadMessages = true) { check(rid, String); + const fromUser = await Meteor.userAsync(); - if (!Meteor.userId() && settings.get('Accounts_AllowAnonymousRead') === false) { + if (!fromUser && settings.get('Accounts_AllowAnonymousRead') === false) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'loadHistory', }); } - const fromId = Meteor.userId(); - const room = await Rooms.findOneById(rid, { projection: { ...roomAccessAttributes, t: 1 } }); if (!room) { return false; } // this checks the Allow Anonymous Read setting, so no need to check again - if (!(await canAccessRoomAsync(room, fromId ? { _id: fromId } : undefined))) { + if (!(await canAccessRoomAsync(room, fromUser || undefined))) { return false; } // if fromId is undefined and it passed the previous check, the user is reading anonymously - if (!fromId) { + if (!fromUser) { return loadMessageHistory({ rid, end, limit, ls, showThreadMessages }); } - const canPreview = await hasPermissionAsync(fromId, 'preview-c-room'); + const canPreview = await hasPermissionAsync(fromUser._id, 'preview-c-room'); - if (room.t === 'c' && !canPreview && !(await Subscriptions.findOneByRoomIdAndUserId(rid, fromId, { projection: { _id: 1 } }))) { + if (room.t === 'c' && !canPreview && !(await Subscriptions.findOneByRoomIdAndUserId(rid, fromUser._id, { projection: { _id: 1 } }))) { return false; } - return loadMessageHistory({ userId: fromId, rid, end, limit, ls, showThreadMessages }); + return loadMessageHistory({ userId: fromUser._id, rid, end, limit, ls, showThreadMessages }); }, }); diff --git a/apps/meteor/server/publications/room/index.ts b/apps/meteor/server/publications/room/index.ts index e3cbfdcc9a8ca..9d143a696aca4 100644 --- a/apps/meteor/server/publications/room/index.ts +++ b/apps/meteor/server/publications/room/index.ts @@ -57,8 +57,8 @@ Meteor.methods({ }); } - const userId = Meteor.userId(); - const isAnonymous = !userId; + const user = await Meteor.userAsync(); + const isAnonymous = !user?._id; if (isAnonymous) { const allowAnon = settings.get('Accounts_AllowAnonymousRead'); @@ -80,21 +80,17 @@ Meteor.methods({ } if ( - userId && - !(await canAccessRoomAsync( - room, - { _id: userId }, - { - includeInvitations: true, - }, - )) + user && + !(await canAccessRoomAsync(room, user, { + includeInvitations: true, + })) ) { throw new Meteor.Error('error-no-permission', 'No permission', { method: 'getRoomByTypeAndName', }); } - if (settings.get('Store_Last_Message') && userId && !(await hasPermissionAsync(userId, 'preview-c-room'))) { + if (settings.get('Store_Last_Message') && user && !(await hasPermissionAsync(user._id, 'preview-c-room'))) { delete room.lastMessage; } diff --git a/apps/meteor/server/services/authorization/canAccessRoom.ts b/apps/meteor/server/services/authorization/canAccessRoom.ts index 684d52ac477a6..57d348e3a1259 100644 --- a/apps/meteor/server/services/authorization/canAccessRoom.ts +++ b/apps/meteor/server/services/authorization/canAccessRoom.ts @@ -95,7 +95,7 @@ const roomAccessValidators: RoomAccessValidatorConverted[] = [ canAccessRoomLivechat, ]; -const isPartialUser = (user: IUser | Pick | undefined): user is Pick => { +export const isPartialUser = (user: IUser | Pick | undefined): user is Pick => { return Boolean(user && Object.keys(user).length === 1 && '_id' in user); }; diff --git a/apps/meteor/server/services/authorization/canReadRoom.ts b/apps/meteor/server/services/authorization/canReadRoom.ts index 73e4dd86e5bd7..0d0cfee8d4295 100644 --- a/apps/meteor/server/services/authorization/canReadRoom.ts +++ b/apps/meteor/server/services/authorization/canReadRoom.ts @@ -1,21 +1,22 @@ import type { RoomAccessValidator } from '@rocket.chat/core-services'; import { Authorization } from '@rocket.chat/core-services'; -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Users } from '@rocket.chat/models'; -import { canAccessRoom } from './canAccessRoom'; +import { canAccessRoom, isPartialUser } from './canAccessRoom'; export const canReadRoom: RoomAccessValidator = async (...args) => { + const [room, user] = args; + const userArgument = isPartialUser(user) ? await Users.findOneById(user._id) : user; + if (!(await canAccessRoom(...args))) { return false; } - const [room, user] = args; - if ( - user?._id && + userArgument && room?.t === 'c' && - !(await Authorization.hasPermission(user._id, 'preview-c-room')) && - !(await Subscriptions.findOneByRoomIdAndUserId(room?._id, user._id, { projection: { _id: 1 } })) + !(await Authorization.hasPermission(userArgument, 'preview-c-room')) && + !(await Subscriptions.findOneByRoomIdAndUserId(room?._id, userArgument._id, { projection: { _id: 1 } })) ) { return false; } diff --git a/apps/meteor/tests/end-to-end/api/direct-message.ts b/apps/meteor/tests/end-to-end/api/direct-message.ts index 67ce007aaadab..a5774325b20a2 100644 --- a/apps/meteor/tests/end-to-end/api/direct-message.ts +++ b/apps/meteor/tests/end-to-end/api/direct-message.ts @@ -959,7 +959,6 @@ describe('[Direct Messages]', () => { .set(credentials) .send({ roomId: directMessage._id, - userId: user._id, }) .expect('Content-Type', 'application/json') .expect(200)