diff --git a/README.md b/README.md index 5c8f065f1849b..9ed43a81970e0 100644 --- a/README.md +++ b/README.md @@ -121,3 +121,24 @@ We're hiring developers, technical support, and product managers all the time. C # 🗒️ Credits - Emoji provided graciously by [JoyPixels](https://www.joypixels.com). +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC +ABC diff --git a/apps/meteor/app/api/server/v1/media.ts b/apps/meteor/app/api/server/v1/media.ts new file mode 100644 index 0000000000000..b6c8445709d44 --- /dev/null +++ b/apps/meteor/app/api/server/v1/media.ts @@ -0,0 +1,70 @@ +import { Uploads } from '@rocket.chat/models'; +import { ajv, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, validateForbiddenErrorResponse, validateNotFoundErrorResponse } from '@rocket.chat/rest-typings'; + +import type { ExtractRoutesFromAPI } from '../ApiClass'; +import { API } from '../api'; +import { convertAudioFile } from '../../../../server/lib/audioConverter'; + +type MediaConvertParams = { + fileId: string; + format: string; + bitrate?: number; +}; + +const isMediaConvertParams = ajv.compile({ + type: 'object', + properties: { + fileId: { type: 'string' }, + format: { type: 'string' }, + bitrate: { type: 'number' }, + }, + required: ['fileId', 'format'], + additionalProperties: false, +}); + +const mediaEndpoints = API.v1.post( + 'media.convertAudio', + { + authRequired: true, + body: isMediaConvertParams, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + 404: validateNotFoundErrorResponse, + 200: ajv.compile<{ outputPath: string; format: string; success: boolean }>({ + type: 'object', + properties: { + outputPath: { type: 'string' }, + format: { type: 'string' }, + success: { type: 'boolean' }, + }, + required: ['outputPath', 'format', 'success'], + additionalProperties: false, + }), + }, + }, + async function action() { + const { fileId, format, bitrate } = this.bodyParams; + + const file = await Uploads.findOneById(fileId); + if (!file?.path) { + return API.v1.notFound('File not found.'); + } + + if (file.userId !== this.userId) { + return API.v1.forbidden('forbidden'); + } + + const result = await convertAudioFile(file.path, format, { bitrate }); + + return API.v1.success({ ...result, success: true }); + }, +); + +export type MediaEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends MediaEndpoints {} +} diff --git a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts index 0a9b282160619..243f19bf1ded7 100644 --- a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts +++ b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts @@ -171,7 +171,7 @@ const validators: RoomSettingsValidators = { async retentionEnabled({ userId, value, room, rid }) { if ( !(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && - (!hasRetentionPolicy(room) || value !== room.retention.enabled) + (!hasRetentionPolicy(room) && value !== room.retention.enabled) ) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', diff --git a/apps/meteor/client/components/message/content/urlPreviews/OEmbedHtmlPreview.tsx b/apps/meteor/client/components/message/content/urlPreviews/OEmbedHtmlPreview.tsx index be1b8e93608ff..d7ee6b2b98cd7 100644 --- a/apps/meteor/client/components/message/content/urlPreviews/OEmbedHtmlPreview.tsx +++ b/apps/meteor/client/components/message/content/urlPreviews/OEmbedHtmlPreview.tsx @@ -7,7 +7,7 @@ import type { OEmbedPreviewMetadata } from './OEmbedPreviewMetadata'; const purifyOptions = { ADD_TAGS: ['iframe'], - ADD_ATTR: ['frameborder', 'allow', 'allowfullscreen', 'scrolling', 'src', 'style', 'referrerpolicy'], + ADD_ATTR: ['frameborder', 'allow', 'allowfullscreen', 'scrolling', 'src', 'style', 'referrerpolicy', 'onload'], ALLOW_UNKNOWN_PROTOCOLS: true, }; diff --git a/apps/meteor/server/lib/parseMessageSearchQuery.ts b/apps/meteor/server/lib/parseMessageSearchQuery.ts index 0365b6e2aecd1..9d7f26f68e086 100644 --- a/apps/meteor/server/lib/parseMessageSearchQuery.ts +++ b/apps/meteor/server/lib/parseMessageSearchQuery.ts @@ -248,7 +248,7 @@ class MessageSearchQueryParser { return text; } - if (/^\/.+\/[imxs]*$/.test(text)) { + if (/^\/.+\/[imxs]*/.test(text)) { const r = text.split('/'); this.query.msg = { $regex: r[1], diff --git a/apps/meteor/server/methods/addRoomModerator.ts b/apps/meteor/server/methods/addRoomModerator.ts index 505942180396b..66bd4e3b9ea98 100644 --- a/apps/meteor/server/methods/addRoomModerator.ts +++ b/apps/meteor/server/methods/addRoomModerator.ts @@ -33,7 +33,7 @@ export const addRoomModerator = async (fromUserId: IUser['_id'], rid: IRoom['_id const isFederated = isRoomFederated(room); - if (!(await hasPermissionAsync(fromUserId, 'set-moderator', rid)) && !isFederated) { + if (!(await hasPermissionAsync(fromUserId, 'set-moderator', rid)) && isFederated) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'addRoomModerator', }); diff --git a/apps/meteor/server/methods/deleteUser.ts b/apps/meteor/server/methods/deleteUser.ts index ac542f12fa35c..c14a7807c9b82 100644 --- a/apps/meteor/server/methods/deleteUser.ts +++ b/apps/meteor/server/methods/deleteUser.ts @@ -55,7 +55,7 @@ Meteor.methods({ }); } - if ((await hasPermissionAsync(uid, 'delete-user')) !== true) { + if ((await hasPermissionAsync(uid, 'delete-message')) !== true) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteUser', }); diff --git a/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts b/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts index 2c1144d1f6904..df450a6920c04 100644 --- a/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts +++ b/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts @@ -327,7 +327,7 @@ const getRelevantMetaTags = function (metaObj: OEmbedMeta): Record - oembedHtml?.replace('iframe', 'iframe style="max-width: 100%;width:400px;height:225px"'); + oembedHtml?.replace(/( { log.debug({ msg: 'Parsing message URLs' }); diff --git a/packages/gazzodown/src/emoji/EmojiRenderer.tsx b/packages/gazzodown/src/emoji/EmojiRenderer.tsx index 3ef81ad6887c2..d37d2a9676813 100644 --- a/packages/gazzodown/src/emoji/EmojiRenderer.tsx +++ b/packages/gazzodown/src/emoji/EmojiRenderer.tsx @@ -1,6 +1,5 @@ import { MessageEmoji, ThreadMessageEmoji } from '@rocket.chat/fuselage'; import type * as MessageParser from '@rocket.chat/message-parser'; -import DOMPurify from 'dompurify'; import type { ReactElement } from 'react'; import { useMemo, useContext, memo } from 'react'; @@ -16,12 +15,10 @@ const EmojiRenderer = ({ big = false, preview = false, ...emoji }: EmojiProps): const fallback = useMemo(() => ('unicode' in emoji ? emoji.unicode : `:${emoji.shortCode ?? emoji.value.value}:`), [emoji]); - const sanitizedFallback = DOMPurify.sanitize(fallback); - const descriptors = useMemo(() => { - const detected = detectEmoji?.(sanitizedFallback); + const detected = detectEmoji?.(fallback); return detected?.length !== 0 ? detected : undefined; - }, [detectEmoji, sanitizedFallback]); + }, [detectEmoji, fallback]); return ( <> @@ -38,8 +35,8 @@ const EmojiRenderer = ({ big = false, preview = false, ...emoji }: EmojiProps): )} )) ?? ( - - {sanitizedFallback} + + {fallback} )}