diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts index cfd62125a023..7f3b606fd2d2 100644 --- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts +++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts @@ -54,7 +54,8 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ } // TODO: Remove this once we have all routes migrated to the new API params - const hasSupportedRoutes = ['/api/v1/integrations.list', '/api/v1/custom-user-status.list'].includes(route); + const hasSupportedRoutes = ['/api/v1/channels.files', '/api/v1/integrations.list', '/api/v1/custom-user-status.list'].includes(route); + const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE'; const messageGenerator = ({ endpoint, version, parameter }: { endpoint: string; version: string; parameter: string }): string => `The usage of the "${parameter}" parameter in endpoint "${endpoint}" breaks the security of the API and can lead to data exposure. It has been deprecated and will be removed in the version ${version}.`; diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index f28805377366..de74cbba503a 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -18,6 +18,7 @@ import { isChannelsConvertToTeamProps, isChannelsSetReadOnlyProps, isChannelsDeleteProps, + isChannelsFilesListProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -763,11 +764,16 @@ API.v1.addRoute( API.v1.addRoute( 'channels.files', - { authRequired: true }, + { authRequired: true, validateParams: isChannelsFilesListProps }, { async get() { + const { typeGroup, name, roomId, roomName } = this.queryParams; + const findResult = await findChannelByIdOrName({ - params: this.queryParams, + params: { + ...(roomId ? { roomId } : {}), + ...(roomName ? { roomName } : {}), + }, checkedArchived: false, }); @@ -778,9 +784,14 @@ API.v1.addRoute( const { offset, count } = await getPaginationItems(this.queryParams); const { sort, fields, query } = await this.parseJsonQuery(); - const ourQuery = Object.assign({}, query, { rid: findResult._id }); + const filter = { + rid: findResult._id, + ...query, + ...(name ? { name: { $regex: name || '', $options: 'i' } } : {}), + ...(typeGroup ? { typeGroup } : {}), + }; - const { cursor, totalCount } = await Uploads.findPaginatedWithoutThumbs(ourQuery, { + const { cursor, totalCount } = await Uploads.findPaginatedWithoutThumbs(filter, { sort: sort || { name: 1 }, skip: offset, limit: count, diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts index f7b69e21f582..25bd59d002f7 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts @@ -53,11 +53,9 @@ export const useFilesList = ( offset: start, count: end, sort: JSON.stringify({ uploadedAt: -1 }), - query: JSON.stringify({ - name: { $regex: options.text || '', $options: 'i' }, - ...(options.type !== 'all' && { - typeGroup: options.type, - }), + ...(options.text ? { name: options.text } : {}), + ...(options.type !== 'all' && { + typeGroup: options.type, }), }); diff --git a/packages/rest-typings/src/v1/channels/ChannelsFilesListProps.ts b/packages/rest-typings/src/v1/channels/ChannelsFilesListProps.ts new file mode 100644 index 000000000000..20fb49885f6b --- /dev/null +++ b/packages/rest-typings/src/v1/channels/ChannelsFilesListProps.ts @@ -0,0 +1,58 @@ +import Ajv from 'ajv'; + +import type { PaginatedRequest } from '../../helpers/PaginatedRequest'; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +export type ChannelsFilesListProps = PaginatedRequest< + ({ roomId: string; roomName?: string } | { roomId?: string; roomName: string }) & { + name?: string; + typeGroup?: string; + query?: string; + } +>; + +const channelsFilesListPropsSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + nullable: true, + }, + roomName: { + type: 'string', + nullable: true, + }, + offset: { + type: 'number', + nullable: true, + }, + count: { + type: 'number', + nullable: true, + }, + sort: { + type: 'string', + nullable: true, + }, + name: { + type: 'string', + nullable: true, + }, + typeGroup: { + type: 'string', + nullable: true, + }, + query: { + type: 'string', + nullable: true, + }, + }, + oneOf: [{ required: ['roomId'] }, { required: ['roomName'] }], + required: [], + additionalProperties: false, +}; + +export const isChannelsFilesListProps = ajv.compile(channelsFilesListPropsSchema); diff --git a/packages/rest-typings/src/v1/channels/ChannelsListProps.ts b/packages/rest-typings/src/v1/channels/ChannelsListProps.ts index 79ed1512f450..eff6a6c0b9e6 100644 --- a/packages/rest-typings/src/v1/channels/ChannelsListProps.ts +++ b/packages/rest-typings/src/v1/channels/ChannelsListProps.ts @@ -6,6 +6,18 @@ const ajv = new Ajv({ coerceTypes: true, }); -export type ChannelsListProps = PaginatedRequest; -const channelsListPropsSchema = {}; +export type ChannelsListProps = PaginatedRequest<{ query?: string }>; + +const channelsListPropsSchema = { + type: 'object', + properties: { + query: { + type: 'string', + nullable: true, + }, + }, + required: [], + additionalProperties: false, +}; + export const isChannelsListProps = ajv.compile(channelsListPropsSchema); diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index 04bda08bd186..6abe90a75b90 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -7,6 +7,7 @@ import type { ChannelsArchiveProps } from './ChannelsArchiveProps'; import type { ChannelsConvertToTeamProps } from './ChannelsConvertToTeamProps'; import type { ChannelsCreateProps } from './ChannelsCreateProps'; import type { ChannelsDeleteProps } from './ChannelsDeleteProps'; +import type { ChannelsFilesListProps } from './ChannelsFilesListProps'; import type { ChannelsGetAllUserMentionsByChannelProps } from './ChannelsGetAllUserMentionsByChannelProps'; import type { ChannelsGetIntegrationsProps } from './ChannelsGetIntegrationsProps'; import type { ChannelsHistoryProps } from './ChannelsHistoryProps'; @@ -32,9 +33,11 @@ import type { ChannelsSetTopicProps } from './ChannelsSetTopicProps'; import type { ChannelsSetTypeProps } from './ChannelsSetTypeProps'; import type { ChannelsUnarchiveProps } from './ChannelsUnarchiveProps'; +export * from './ChannelsFilesListProps'; + export type ChannelsEndpoints = { '/v1/channels.files': { - GET: (params: PaginatedRequest<{ roomId: string } | { roomName: string }>) => PaginatedResult<{ + GET: (params: ChannelsFilesListProps) => PaginatedResult<{ files: IUploadWithUser[]; }>; };