From 168378b4a0c6f7d553bd555ceefcec9ee21928d2 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Sun, 1 May 2022 21:08:26 -0300 Subject: [PATCH 01/10] chore: convert assets endpoint to ts --- .../app/api/server/lib/getUploadFormData.js | 37 ---------- .../app/api/server/lib/getUploadFormData.ts | 50 ++++++++++++++ .../api/server/v1/{assets.js => assets.ts} | 9 +-- .../assets/server/{assets.js => assets.ts} | 68 ++++++++++++------- .../app/assets/server/{index.js => index.ts} | 0 .../core-typings/src/IRocketChatAssets.ts | 37 ++++++++++ packages/core-typings/src/index.ts | 1 + packages/rest-typings/src/index.ts | 2 + packages/rest-typings/src/v1/assets.ts | 11 +++ 9 files changed, 148 insertions(+), 67 deletions(-) delete mode 100644 apps/meteor/app/api/server/lib/getUploadFormData.js create mode 100644 apps/meteor/app/api/server/lib/getUploadFormData.ts rename apps/meteor/app/api/server/v1/{assets.js => assets.ts} (89%) rename apps/meteor/app/assets/server/{assets.js => assets.ts} (83%) rename apps/meteor/app/assets/server/{index.js => index.ts} (100%) create mode 100644 packages/core-typings/src/IRocketChatAssets.ts create mode 100644 packages/rest-typings/src/v1/assets.ts diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.js b/apps/meteor/app/api/server/lib/getUploadFormData.js deleted file mode 100644 index bbeaddfea3a0..000000000000 --- a/apps/meteor/app/api/server/lib/getUploadFormData.js +++ /dev/null @@ -1,37 +0,0 @@ -import busboy from 'busboy'; - -export const getUploadFormData = async ({ request }) => - new Promise((resolve, reject) => { - const bb = busboy({ headers: request.headers }); - - const fields = {}; - - bb.on('file', (fieldname, file, { filename, encoding, mimeType: mimetype }) => { - const fileData = []; - - console.log(file); - file.on('data', (data) => fileData.push(data)); - - file.on('end', () => { - if (fields.hasOwnProperty(fieldname)) { - return reject('Just 1 file is allowed'); - } - - fields[fieldname] = { - file, - filename, - encoding, - mimetype, - fileBuffer: Buffer.concat(fileData), - }; - }); - }); - - bb.on('field', (fieldname, value) => { - fields[fieldname] = value; - }); - - bb.on('finish', () => resolve(fields)); - - request.pipe(bb); - }); diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts new file mode 100644 index 000000000000..66be0bf0fa04 --- /dev/null +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -0,0 +1,50 @@ +import busboy from 'busboy'; +import { Request } from 'express'; + +export interface IFormDataFields { + file: any; + filename: string; + encoding: string; + mimetype: string; + fileBuffer: Buffer; +} + +export interface IFormDataUpload { + [key: string]: IFormDataFields; +} + +export const getUploadFormData = async ({ request }: { request: Request }): Promise => + new Promise((resolve, reject) => { + const bb = busboy({ headers: request.headers }); + + const fields: IFormDataUpload = {}; + + bb.on('file', (fieldname: string, file: any, { filename, encoding, mimeType: mimetype }: Record) => { + const fileData: any[] = []; + + console.log(file); + file.on('data', (data: any) => fileData.push(data)); + + file.on('end', () => { + if (fields.hasOwnProperty(fieldname)) { + return reject('Just 1 file is allowed'); + } + + fields[fieldname] = { + file, + filename, + encoding, + mimetype, + fileBuffer: Buffer.concat(fileData), + }; + }); + }); + + bb.on('field', (fieldname: string, value: any) => { + fields[fieldname] = value; + }); + + bb.on('finish', () => resolve(fields)); + + request.pipe(bb); + }); diff --git a/apps/meteor/app/api/server/v1/assets.js b/apps/meteor/app/api/server/v1/assets.ts similarity index 89% rename from apps/meteor/app/api/server/v1/assets.js rename to apps/meteor/app/api/server/v1/assets.ts index c232d3c0ff2c..5f9b106d1676 100644 --- a/apps/meteor/app/api/server/v1/assets.js +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { Request } from 'express'; import { RocketChatAssets } from '../../../assets/server'; import { API } from '../api'; @@ -8,12 +9,8 @@ API.v1.addRoute( 'assets.setAsset', { authRequired: true }, { - post() { - const { refreshAllClients, ...files } = Promise.await( - getUploadFormData({ - request: this.request, - }), - ); + async post() { + const { refreshAllClients, ...files } = await getUploadFormData({ request: this.request as Request }); const assetsKeys = Object.keys(RocketChatAssets.assets); diff --git a/apps/meteor/app/assets/server/assets.js b/apps/meteor/app/assets/server/assets.ts similarity index 83% rename from apps/meteor/app/assets/server/assets.js rename to apps/meteor/app/assets/server/assets.ts index 79ced079ea60..b37074d0cf1b 100644 --- a/apps/meteor/app/assets/server/assets.js +++ b/apps/meteor/app/assets/server/assets.ts @@ -6,6 +6,8 @@ import { WebAppHashing } from 'meteor/webapp-hashing'; import _ from 'underscore'; import sizeOf from 'image-size'; import sharp from 'sharp'; +import { Request, Response, NextFunction } from 'express'; +import type { IRocketChatAssets, IRocketChatAsset } from '@rocket.chat/core-typings'; import { settings, settingsRegistry } from '../../settings/server'; import { getURL } from '../../utils/lib/getURL'; @@ -17,8 +19,7 @@ import { Settings } from '../../models/server'; const RocketChatAssetsInstance = new RocketChatFile.GridFS({ name: 'assets', }); - -const assets = { +const assets: IRocketChatAssets = { logo: { label: 'logo (svg, png, jpg)', defaultUrl: 'images/logo/logo.svg', @@ -38,6 +39,7 @@ const assets = { extensions: ['svg', 'png', 'jpg', 'jpeg'], }, }, + // eslint-disable-next-line @typescript-eslint/camelcase favicon_ico: { label: 'favicon (ico)', defaultUrl: 'favicon.ico', @@ -54,6 +56,7 @@ const assets = { extensions: ['svg'], }, }, + // eslint-disable-next-line @typescript-eslint/camelcase favicon_16: { label: 'favicon 16x16 (png)', defaultUrl: 'images/logo/favicon-16x16.png', @@ -64,6 +67,7 @@ const assets = { height: 16, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase favicon_32: { label: 'favicon 32x32 (png)', defaultUrl: 'images/logo/favicon-32x32.png', @@ -74,6 +78,7 @@ const assets = { height: 32, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase favicon_192: { label: 'android-chrome 192x192 (png)', defaultUrl: 'images/logo/android-chrome-192x192.png', @@ -84,6 +89,7 @@ const assets = { height: 192, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase favicon_512: { label: 'android-chrome 512x512 (png)', defaultUrl: 'images/logo/android-chrome-512x512.png', @@ -94,6 +100,7 @@ const assets = { height: 512, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase touchicon_180: { label: 'apple-touch-icon 180x180 (png)', defaultUrl: 'images/logo/apple-touch-icon.png', @@ -104,6 +111,7 @@ const assets = { height: 180, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase touchicon_180_pre: { label: 'apple-touch-icon-precomposed 180x180 (png)', defaultUrl: 'images/logo/apple-touch-icon-precomposed.png', @@ -114,6 +122,7 @@ const assets = { height: 180, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase tile_70: { label: 'mstile 70x70 (png)', defaultUrl: 'images/logo/mstile-70x70.png', @@ -124,6 +133,7 @@ const assets = { height: 70, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase tile_144: { label: 'mstile 144x144 (png)', defaultUrl: 'images/logo/mstile-144x144.png', @@ -134,6 +144,7 @@ const assets = { height: 144, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase tile_150: { label: 'mstile 150x150 (png)', defaultUrl: 'images/logo/mstile-150x150.png', @@ -144,6 +155,7 @@ const assets = { height: 150, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase tile_310_square: { label: 'mstile 310x310 (png)', defaultUrl: 'images/logo/mstile-310x310.png', @@ -154,6 +166,7 @@ const assets = { height: 310, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase tile_310_wide: { label: 'mstile 310x150 (png)', defaultUrl: 'images/logo/mstile-310x150.png', @@ -164,6 +177,7 @@ const assets = { height: 150, }, }, + // eslint-disable-next-line @typescript-eslint/camelcase safari_pinned: { label: 'safari pinned tab (svg)', defaultUrl: 'images/logo/safari-pinned-tab.svg', @@ -174,16 +188,16 @@ const assets = { }, }; -export const RocketChatAssets = new (class { - get mime() { +class RocketChatAssetsClass { + get mime(): any { return mime; } - get assets() { + get assets(): IRocketChatAssets { return assets; } - setAsset(binaryContent, contentType, asset) { + public setAsset(binaryContent: BufferEncoding, contentType: string, asset: string): void { if (!assets[asset]) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { function: 'RocketChat.Assets.setAsset', @@ -226,6 +240,7 @@ export const RocketChatAssets = new (class { }; Settings.updateValueById(key, value); + // eslint-disable-next-line @typescript-eslint/no-use-before-define return RocketChatAssets.processAsset(key, value); }, 200); }), @@ -234,7 +249,7 @@ export const RocketChatAssets = new (class { rs.pipe(ws); } - unsetAsset(asset) { + public unsetAsset(asset: string): void { if (!assets[asset]) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { function: 'RocketChat.Assets.unsetAsset', @@ -248,16 +263,17 @@ export const RocketChatAssets = new (class { }; Settings.updateValueById(key, value); + // eslint-disable-next-line @typescript-eslint/no-use-before-define RocketChatAssets.processAsset(key, value); } - refreshClients() { - return process.emit('message', { + public refreshClients(): boolean { + return (process.emit as Function)('message', { refresh: 'client', }); } - processAsset(settingKey, settingValue) { + public processAsset(settingKey: string, settingValue: any): Record | undefined { if (settingKey.indexOf('Assets_') !== 0) { return; } @@ -301,23 +317,25 @@ export const RocketChatAssets = new (class { return assetValue.cache; } - getURL(assetName, options = { cdn: false, full: true }) { + public getURL(assetName: string, options = { cdn: false, full: true }): string { const asset = settings.get(assetName); const url = asset.url || asset.defaultUrl; return getURL(url, options); } -})(); +} -settingsRegistry.addGroup('Assets'); +export const RocketChatAssets = new RocketChatAssetsClass(); -settingsRegistry.add('Assets_SvgFavicon_Enable', true, { - type: 'boolean', - group: 'Assets', - i18nLabel: 'Enable_Svg_Favicon', +settingsRegistry.addGroup('Assets', function () { + this.add('Assets_SvgFavicon_Enable', true, { + type: 'boolean', + group: 'Assets', + i18nLabel: 'Enable_Svg_Favicon', + }); }); -function addAssetToSetting(asset, value) { +function addAssetToSetting(asset: string, value: IRocketChatAsset): void { const key = `Assets_${asset}`; settingsRegistry.add( @@ -353,7 +371,7 @@ settings.watchByRegex(/^Assets_/, (key, value) => RocketChatAssets.processAsset( Meteor.startup(function () { return Meteor.setTimeout(function () { - return process.emit('message', { + return (process.emit as Function)('message', { refresh: 'client', }); }, 200); @@ -361,7 +379,7 @@ Meteor.startup(function () { const { calculateClientHash } = WebAppHashing; -WebAppHashing.calculateClientHash = function (manifest, includeFilter, runtimeConfigOverride) { +WebAppHashing.calculateClientHash = function (manifest: Record, includeFilter: Function, runtimeConfigOverride: any): string { for (const key of Object.keys(assets)) { const value = assets[key]; if (!value.cache && !value.defaultUrl) { @@ -466,7 +484,7 @@ Meteor.methods({ WebApp.connectHandlers.use( '/assets/', - Meteor.bindEnvironment(function (req, res, next) { + Meteor.bindEnvironment((req: Request, res: Response, next: NextFunction) => { const params = { asset: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')).replace(/\.[^.]*$/, ''), }; @@ -488,7 +506,7 @@ WebApp.connectHandlers.use( if (defaultUrl) { const assetUrl = format && ['png', 'svg'].includes(format) ? defaultUrl.replace(/(svg|png)$/, format) : defaultUrl; req.url = `/${assetUrl}`; - WebAppInternals.staticFilesMiddleware(WebAppInternals.staticFilesByArch, req, res, next); + WebAppInternals.staticFilesMiddleware((WebAppInternals as Record).staticFilesByArch, req, res, next); } else { res.writeHead(404); res.end(); @@ -512,7 +530,9 @@ WebApp.connectHandlers.use( if (format && format !== file.extension && ['png', 'jpg', 'jpeg'].includes(format)) { res.setHeader('Content-Type', `image/${format}`); - sharp(file.content).toFormat(format).pipe(res); + sharp(file.content) + .toFormat(format as any) + .pipe(res); return; } @@ -521,5 +541,5 @@ WebApp.connectHandlers.use( res.setHeader('Content-Length', file.size); res.writeHead(200); res.end(file.content); - }), + }) as any, ); diff --git a/apps/meteor/app/assets/server/index.js b/apps/meteor/app/assets/server/index.ts similarity index 100% rename from apps/meteor/app/assets/server/index.js rename to apps/meteor/app/assets/server/index.ts diff --git a/packages/core-typings/src/IRocketChatAssets.ts b/packages/core-typings/src/IRocketChatAssets.ts new file mode 100644 index 000000000000..051a27eb66b3 --- /dev/null +++ b/packages/core-typings/src/IRocketChatAssets.ts @@ -0,0 +1,37 @@ +export interface IRocketChatAssetConstraint { + type: string; + extensions: string[]; + width?: number; + height?: number; +} + +export interface IRocketChatAssetWizard { + step: number; + order: number; +} + +export interface IRocketChatAsset { + label: string; + constraints: IRocketChatAssetConstraint; + defaultUrl?: string; + wizard?: IRocketChatAssetWizard; +} + +export interface IRocketChatAssets { + logo: IRocketChatAsset; + background: IRocketChatAsset; + favicon_ico: IRocketChatAsset; + favicon: IRocketChatAsset; + favicon_16: IRocketChatAsset; + favicon_32: IRocketChatAsset; + favicon_192: IRocketChatAsset; + favicon_512: IRocketChatAsset; + touchicon_180: IRocketChatAsset; + touchicon_180_pre: IRocketChatAsset; + tile_70: IRocketChatAsset; + tile_144: IRocketChatAsset; + tile_150: IRocketChatAsset; + tile_310_square: IRocketChatAsset; + tile_310_wide: IRocketChatAsset; + safari_pinned: IRocketChatAsset; +} diff --git a/packages/core-typings/src/index.ts b/packages/core-typings/src/index.ts index 86a99127e824..3d798d3829e1 100644 --- a/packages/core-typings/src/index.ts +++ b/packages/core-typings/src/index.ts @@ -24,6 +24,7 @@ export * from './ICustomSound'; export * from './ICloud'; export * from './IServerEvent'; export * from './ICronJobs'; +export * from './IRocketChatAssets'; export * from './IUserDataFile'; export * from './IUserSession'; diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 45cc1b3c36a6..520b4f0f9b43 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -3,6 +3,7 @@ import type { KeyOfEach } from '@rocket.chat/core-typings'; import type { AppsEndpoints } from './apps'; import type { ReplacePlaceholders } from './helpers/ReplacePlaceholders'; +import type { AssetsEndpoints } from './v1/assets'; import type { BannersEndpoints } from './v1/banners'; import type { ChannelsEndpoints } from './v1/channels'; import type { ChatEndpoints } from './v1/chat'; @@ -58,6 +59,7 @@ export interface Endpoints VoipEndpoints, InvitesEndpoints, E2eEndpoints, + AssetsEndpoints, CustomSoundEndpoint {} type OperationsByPathPattern = TPathPattern extends any diff --git a/packages/rest-typings/src/v1/assets.ts b/packages/rest-typings/src/v1/assets.ts new file mode 100644 index 000000000000..a51ae5b5dac9 --- /dev/null +++ b/packages/rest-typings/src/v1/assets.ts @@ -0,0 +1,11 @@ +import type { IRocketChatAssets } from '@rocket.chat/core-typings'; + +export type AssetsEndpoints = { + 'assets.setAsset': { + POST: (params: { assetName: keyof IRocketChatAssets; refreshAllClients?: boolean }) => void; + }; + + 'assets.unsetAsset': { + POST: (params: { assetName: keyof IRocketChatAssets; refreshAllClients?: boolean }) => void; + }; +}; From 79718f04eef4ac0ed028a1d7006e041d3dfb99d0 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Mon, 2 May 2022 10:31:31 -0300 Subject: [PATCH 02/10] chore: fix type errors --- apps/meteor/app/api/server/v1/assets.ts | 2 +- apps/meteor/app/assets/server/assets.ts | 58 ++++++++++--------- .../externals/meteor/webapp-hashing.d.ts | 5 ++ apps/meteor/package.json | 1 + .../core-typings/src/IRocketChatAssets.ts | 16 +++++ yarn.lock | 10 ++++ 6 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 apps/meteor/definition/externals/meteor/webapp-hashing.d.ts diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index 5f9b106d1676..e327f2400574 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -41,7 +41,7 @@ API.v1.addRoute( { post() { const { assetName, refreshAllClients } = this.bodyParams; - const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName); + const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName as string); if (!isValidAsset) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); } diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index b37074d0cf1b..e53831015320 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -7,12 +7,12 @@ import _ from 'underscore'; import sizeOf from 'image-size'; import sharp from 'sharp'; import { Request, Response, NextFunction } from 'express'; -import type { IRocketChatAssets, IRocketChatAsset } from '@rocket.chat/core-typings'; +import { IRocketChatAssets, IRocketChatAsset } from '@rocket.chat/core-typings'; import { settings, settingsRegistry } from '../../settings/server'; import { getURL } from '../../utils/lib/getURL'; import { mime } from '../../utils/lib/mimeTypes'; -import { hasPermission } from '../../authorization'; +import { hasPermission } from '../../authorization/server'; import { RocketChatFile } from '../../file'; import { Settings } from '../../models/server'; @@ -188,6 +188,10 @@ const assets: IRocketChatAssets = { }, }; +function getAssetByKey(key: string): IRocketChatAsset { + return assets[key as keyof IRocketChatAssets]; +} + class RocketChatAssetsClass { get mime(): any { return mime; @@ -198,14 +202,14 @@ class RocketChatAssetsClass { } public setAsset(binaryContent: BufferEncoding, contentType: string, asset: string): void { - if (!assets[asset]) { + if (!getAssetByKey(asset)) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { function: 'RocketChat.Assets.setAsset', }); } const extension = mime.extension(contentType); - if (assets[asset].constraints.extensions.includes(extension) === false) { + if (getAssetByKey(asset).constraints.extensions.includes(extension as string) === false) { throw new Meteor.Error(contentType, `Invalid file type: ${contentType}`, { function: 'RocketChat.Assets.setAsset', errorTitle: 'error-invalid-file-type', @@ -213,14 +217,14 @@ class RocketChatAssetsClass { } const file = Buffer.from(binaryContent, 'binary'); - if (assets[asset].constraints.width || assets[asset].constraints.height) { + if (getAssetByKey(asset).constraints.width || getAssetByKey(asset).constraints.height) { const dimensions = sizeOf(file); - if (assets[asset].constraints.width && assets[asset].constraints.width !== dimensions.width) { + if (getAssetByKey(asset).constraints.width && getAssetByKey(asset).constraints.width !== dimensions.width) { throw new Meteor.Error('error-invalid-file-width', 'Invalid file width', { function: 'Invalid file width', }); } - if (assets[asset].constraints.height && assets[asset].constraints.height !== dimensions.height) { + if (getAssetByKey(asset).constraints.height && getAssetByKey(asset).constraints.height !== dimensions.height) { throw new Meteor.Error('error-invalid-file-height'); } } @@ -236,7 +240,7 @@ class RocketChatAssetsClass { const key = `Assets_${asset}`; const value = { url: `assets/${asset}.${extension}`, - defaultUrl: assets[asset].defaultUrl, + defaultUrl: getAssetByKey(asset).defaultUrl, }; Settings.updateValueById(key, value); @@ -250,7 +254,7 @@ class RocketChatAssetsClass { } public unsetAsset(asset: string): void { - if (!assets[asset]) { + if (!getAssetByKey(asset)) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { function: 'RocketChat.Assets.unsetAsset', }); @@ -259,7 +263,7 @@ class RocketChatAssetsClass { RocketChatAssetsInstance.deleteFile(asset); const key = `Assets_${asset}`; const value = { - defaultUrl: assets[asset].defaultUrl, + defaultUrl: getAssetByKey(asset).defaultUrl, }; Settings.updateValueById(key, value); @@ -279,7 +283,7 @@ class RocketChatAssetsClass { } const assetKey = settingKey.replace(/^Assets_/, ''); - const assetValue = assets[assetKey]; + const assetValue = getAssetByKey(assetKey); if (!assetValue) { return; @@ -318,7 +322,7 @@ class RocketChatAssetsClass { } public getURL(assetName: string, options = { cdn: false, full: true }): string { - const asset = settings.get(assetName); + const asset = settings.get>(assetName); const url = asset.url || asset.defaultUrl; return getURL(url, options); @@ -351,19 +355,19 @@ function addAssetToSetting(asset: string, value: IRocketChatAsset): void { asset, public: true, wizard: value.wizard, - }, + } as Record, ); - const currentValue = settings.get(key); + const currentValue = settings.get>(key); - if (typeof currentValue === 'object' && currentValue.defaultUrl !== assets[asset].defaultUrl) { - currentValue.defaultUrl = assets[asset].defaultUrl; + if (typeof currentValue === 'object' && currentValue.defaultUrl !== getAssetByKey(asset).defaultUrl) { + currentValue.defaultUrl = getAssetByKey(asset).defaultUrl; Settings.updateValueById(key, currentValue); } } for (const key of Object.keys(assets)) { - const value = assets[key]; + const value = getAssetByKey(key); addAssetToSetting(key, value); } @@ -381,7 +385,7 @@ const { calculateClientHash } = WebAppHashing; WebAppHashing.calculateClientHash = function (manifest: Record, includeFilter: Function, runtimeConfigOverride: any): string { for (const key of Object.keys(assets)) { - const value = assets[key]; + const value = getAssetByKey(key); if (!value.cache && !value.defaultUrl) { continue; } @@ -399,7 +403,7 @@ WebAppHashing.calculateClientHash = function (manifest: Record, inc hash: value.cache.hash, }; } else { - const extension = value.defaultUrl.split('.').pop(); + const extension = value.defaultUrl?.split('.').pop(); cache = { path: `assets/${key}.${extension}`, cacheable: false, @@ -434,7 +438,7 @@ Meteor.methods({ }); } - const _hasPermission = hasPermission(Meteor.userId(), 'manage-assets'); + const _hasPermission = hasPermission(Meteor.userId() as string, 'manage-assets'); if (!_hasPermission) { throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { method: 'refreshClients', @@ -452,7 +456,7 @@ Meteor.methods({ }); } - const _hasPermission = hasPermission(Meteor.userId(), 'manage-assets'); + const _hasPermission = hasPermission(Meteor.userId() as string, 'manage-assets'); if (!_hasPermission) { throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { method: 'unsetAsset', @@ -470,7 +474,7 @@ Meteor.methods({ }); } - const _hasPermission = hasPermission(Meteor.userId(), 'manage-assets'); + const _hasPermission = hasPermission(Meteor.userId() as string, 'manage-assets'); if (!_hasPermission) { throw new Meteor.Error('error-action-not-allowed', 'Managing assets not allowed', { method: 'setAsset', @@ -489,20 +493,20 @@ WebApp.connectHandlers.use( asset: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')).replace(/\.[^.]*$/, ''), }; - const file = assets[params.asset] && assets[params.asset].cache; + const file = getAssetByKey(params.asset) && getAssetByKey(params.asset).cache; const format = req.url.replace(/.*\.([a-z]+)(?:$|\?.*)/i, '$1'); if ( - assets[params.asset] && - Array.isArray(assets[params.asset].constraints.extensions) && - !assets[params.asset].constraints.extensions.includes(format) + getAssetByKey(params.asset) && + Array.isArray(getAssetByKey(params.asset).constraints.extensions) && + !getAssetByKey(params.asset).constraints.extensions.includes(format) ) { res.writeHead(403); return res.end(); } if (!file) { - const defaultUrl = assets[params.asset] && assets[params.asset].defaultUrl; + const defaultUrl = getAssetByKey(params.asset) && getAssetByKey(params.asset).defaultUrl; if (defaultUrl) { const assetUrl = format && ['png', 'svg'].includes(format) ? defaultUrl.replace(/(svg|png)$/, format) : defaultUrl; req.url = `/${assetUrl}`; diff --git a/apps/meteor/definition/externals/meteor/webapp-hashing.d.ts b/apps/meteor/definition/externals/meteor/webapp-hashing.d.ts new file mode 100644 index 000000000000..845923093f23 --- /dev/null +++ b/apps/meteor/definition/externals/meteor/webapp-hashing.d.ts @@ -0,0 +1,5 @@ +declare module 'meteor/webapp-hashing' { + namespace WebAppHashing { + function calculateClientHash(manifest: Record, includeFilter: Function, runtimeConfigOverride: any): string; + } +} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 7717dbd87cbe..16cf0b97d48b 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -86,6 +86,7 @@ "@types/bad-words": "^3.0.1", "@types/bcrypt": "^5.0.0", "@types/body-parser": "^1.19.0", + "@types/busboy": "^1.5.0", "@types/chai": "^4.2.22", "@types/chai-datetime": "0.0.37", "@types/chai-dom": "0.0.11", diff --git a/packages/core-typings/src/IRocketChatAssets.ts b/packages/core-typings/src/IRocketChatAssets.ts index 051a27eb66b3..5af8701a46ea 100644 --- a/packages/core-typings/src/IRocketChatAssets.ts +++ b/packages/core-typings/src/IRocketChatAssets.ts @@ -10,11 +10,27 @@ export interface IRocketChatAssetWizard { order: number; } +export interface IRocketChatAssetCache { + path: string; + cacheable: boolean; + where: string; + type: string; + content: Buffer; + extension: string; + url: string; + size: number; + uploadDate: Date; + contentType: string; + hash: string; + sourceMapUrl?: string; +} + export interface IRocketChatAsset { label: string; constraints: IRocketChatAssetConstraint; defaultUrl?: string; wizard?: IRocketChatAssetWizard; + cache?: IRocketChatAssetCache; } export interface IRocketChatAssets { diff --git a/yarn.lock b/yarn.lock index 4378569bef53..38db1b1a069b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3837,6 +3837,7 @@ __metadata: "@types/bad-words": ^3.0.1 "@types/bcrypt": ^5.0.0 "@types/body-parser": ^1.19.0 + "@types/busboy": ^1.5.0 "@types/chai": ^4.2.22 "@types/chai-datetime": 0.0.37 "@types/chai-dom": 0.0.11 @@ -5693,6 +5694,15 @@ __metadata: languageName: node linkType: hard +"@types/busboy@npm:^1.5.0": + version: 1.5.0 + resolution: "@types/busboy@npm:1.5.0" + dependencies: + "@types/node": "*" + checksum: ffa7bf25c0395f6927526b7d97e70cd2df789e4ca0d231e41855fb08542fa236891ce457d83cc50cac6e5cef6be092ab80597070dcf1413f736462690a23e987 + languageName: node + linkType: hard + "@types/caseless@npm:*": version: 0.12.2 resolution: "@types/caseless@npm:0.12.2" From f9873f4532fc668c664d1c42f72d8e38b9fb265e Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 6 May 2022 11:44:32 -0300 Subject: [PATCH 03/10] chore: update according to review --- apps/meteor/app/api/server/v1/assets.ts | 4 ++++ apps/meteor/app/assets/server/assets.ts | 2 +- packages/rest-typings/src/index.ts | 1 + packages/rest-typings/src/v1/assets.ts | 21 +++++++++++++++++++-- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index e327f2400574..478f434a3836 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Request } from 'express'; +import { isAssetsUnsetAssetProps } from '@rocket.chat/rest-typings'; import { RocketChatAssets } from '../../../assets/server'; import { API } from '../api'; @@ -41,6 +42,9 @@ API.v1.addRoute( { post() { const { assetName, refreshAllClients } = this.bodyParams; + if (!isAssetsUnsetAssetProps(this.bodyParams)) { + return API.v1.failure('invalid-body-params', isAssetsUnsetAssetProps.errors?.map((e) => e.message).join('\n ')); + } const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName as string); if (!isValidAsset) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index e53831015320..5778afd04799 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -495,7 +495,7 @@ WebApp.connectHandlers.use( const file = getAssetByKey(params.asset) && getAssetByKey(params.asset).cache; - const format = req.url.replace(/.*\.([a-z]+)(?:$|\?.*)/i, '$1'); + const format = req.url.split('.').pop() || ''; if ( getAssetByKey(params.asset) && diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index bbf7b571d413..4abeff85856c 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -136,6 +136,7 @@ export * from './v1/permissions'; export * from './v1/roles'; export * from './v1/settings'; export * from './v1/teams'; +export * from './v1/assets'; export * from './v1/oauthapps'; export * from './helpers/PaginatedRequest'; export * from './helpers/PaginatedResult'; diff --git a/packages/rest-typings/src/v1/assets.ts b/packages/rest-typings/src/v1/assets.ts index a51ae5b5dac9..fc318f63ba49 100644 --- a/packages/rest-typings/src/v1/assets.ts +++ b/packages/rest-typings/src/v1/assets.ts @@ -1,11 +1,28 @@ +import Ajv, { JSONSchemaType } from 'ajv'; import type { IRocketChatAssets } from '@rocket.chat/core-typings'; +export type AssetsUnsetAssetProps = { assetName: keyof IRocketChatAssets; refreshAllClients?: boolean }; + export type AssetsEndpoints = { 'assets.setAsset': { - POST: (params: { assetName: keyof IRocketChatAssets; refreshAllClients?: boolean }) => void; + POST: (params: AssetsUnsetAssetProps) => void; }; 'assets.unsetAsset': { - POST: (params: { assetName: keyof IRocketChatAssets; refreshAllClients?: boolean }) => void; + POST: (params: AssetsUnsetAssetProps) => void; }; }; + +const ajv = new Ajv(); + +const assetsUnsetAssetPropsSchema: JSONSchemaType = { + type: 'object', + properties: { + assetName: { type: 'string' }, + refreshAllClients: { type: 'boolean', nullable: true }, + }, + required: ['assetName'], + additionalProperties: false, +}; + +export const isAssetsUnsetAssetProps = ajv.compile(assetsUnsetAssetPropsSchema); From 08a71306b2b59a3463320bc2110c542b77ba9cec Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Mon, 9 May 2022 14:17:54 -0300 Subject: [PATCH 04/10] chore: fix according to the review --- .../app/api/server/lib/getUploadFormData.ts | 1 - apps/meteor/app/api/server/v1/assets.ts | 2 +- apps/meteor/app/assets/server/assets.ts | 129 +++++++++--------- apps/meteor/app/utils/lib/mimeTypes.js | 8 -- apps/meteor/app/utils/lib/mimeTypes.ts | 14 ++ apps/meteor/tests/unit/lib/mimeTypes.tests.ts | 15 ++ .../core-typings/src/IRocketChatAssets.ts | 1 + packages/core-typings/src/ISetting.ts | 6 +- 8 files changed, 99 insertions(+), 77 deletions(-) delete mode 100644 apps/meteor/app/utils/lib/mimeTypes.js create mode 100644 apps/meteor/app/utils/lib/mimeTypes.ts create mode 100644 apps/meteor/tests/unit/lib/mimeTypes.tests.ts diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts index 66be0bf0fa04..c262a82cd593 100644 --- a/apps/meteor/app/api/server/lib/getUploadFormData.ts +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -22,7 +22,6 @@ export const getUploadFormData = async ({ request }: { request: Request }): Prom bb.on('file', (fieldname: string, file: any, { filename, encoding, mimeType: mimetype }: Record) => { const fileData: any[] = []; - console.log(file); file.on('data', (data: any) => fileData.push(data)); file.on('end', () => { diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index 478f434a3836..da8bf3ec4e47 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -36,7 +36,7 @@ API.v1.addRoute( }, ); -API.v1.addRoute( +API.v1.addRoute<'assets.unsetAsset', { authRequired: true }>( 'assets.unsetAsset', { authRequired: true }, { diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index 5778afd04799..84ed75f8480e 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -1,4 +1,5 @@ import crypto from 'crypto'; +import { ServerResponse, IncomingMessage } from 'http'; import { Meteor } from 'meteor/meteor'; import { WebApp, WebAppInternals } from 'meteor/webapp'; @@ -6,12 +7,12 @@ import { WebAppHashing } from 'meteor/webapp-hashing'; import _ from 'underscore'; import sizeOf from 'image-size'; import sharp from 'sharp'; -import { Request, Response, NextFunction } from 'express'; +import { NextHandleFunction } from 'connect'; import { IRocketChatAssets, IRocketChatAsset } from '@rocket.chat/core-typings'; import { settings, settingsRegistry } from '../../settings/server'; import { getURL } from '../../utils/lib/getURL'; -import { mime } from '../../utils/lib/mimeTypes'; +import { getExtension } from '../../utils/lib/mimeTypes'; import { hasPermission } from '../../authorization/server'; import { RocketChatFile } from '../../file'; import { Settings } from '../../models/server'; @@ -193,23 +194,20 @@ function getAssetByKey(key: string): IRocketChatAsset { } class RocketChatAssetsClass { - get mime(): any { - return mime; - } - get assets(): IRocketChatAssets { return assets; } public setAsset(binaryContent: BufferEncoding, contentType: string, asset: string): void { - if (!getAssetByKey(asset)) { + const assetInstance = getAssetByKey(asset); + if (!assetInstance) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset', { function: 'RocketChat.Assets.setAsset', }); } - const extension = mime.extension(contentType); - if (getAssetByKey(asset).constraints.extensions.includes(extension as string) === false) { + const extension = getExtension(contentType); + if (assetInstance.constraints.extensions.includes(extension) === false) { throw new Meteor.Error(contentType, `Invalid file type: ${contentType}`, { function: 'RocketChat.Assets.setAsset', errorTitle: 'error-invalid-file-type', @@ -217,14 +215,14 @@ class RocketChatAssetsClass { } const file = Buffer.from(binaryContent, 'binary'); - if (getAssetByKey(asset).constraints.width || getAssetByKey(asset).constraints.height) { + if (assetInstance.constraints.width || assetInstance.constraints.height) { const dimensions = sizeOf(file); - if (getAssetByKey(asset).constraints.width && getAssetByKey(asset).constraints.width !== dimensions.width) { + if (assetInstance.constraints.width && assetInstance.constraints.width !== dimensions.width) { throw new Meteor.Error('error-invalid-file-width', 'Invalid file width', { function: 'Invalid file width', }); } - if (getAssetByKey(asset).constraints.height && getAssetByKey(asset).constraints.height !== dimensions.height) { + if (assetInstance.constraints.height && assetInstance.constraints.height !== dimensions.height) { throw new Meteor.Error('error-invalid-file-height'); } } @@ -240,7 +238,7 @@ class RocketChatAssetsClass { const key = `Assets_${asset}`; const value = { url: `assets/${asset}.${extension}`, - defaultUrl: getAssetByKey(asset).defaultUrl, + defaultUrl: assetInstance.defaultUrl, }; Settings.updateValueById(key, value); @@ -322,7 +320,7 @@ class RocketChatAssetsClass { } public getURL(assetName: string, options = { cdn: false, full: true }): string { - const asset = settings.get>(assetName); + const asset = settings.get(assetName); const url = asset.url || asset.defaultUrl; return getURL(url, options); @@ -355,10 +353,10 @@ function addAssetToSetting(asset: string, value: IRocketChatAsset): void { asset, public: true, wizard: value.wizard, - } as Record, + }, ); - const currentValue = settings.get>(key); + const currentValue = settings.get(key); if (typeof currentValue === 'object' && currentValue.defaultUrl !== getAssetByKey(asset).defaultUrl) { currentValue.defaultUrl = getAssetByKey(asset).defaultUrl; @@ -486,64 +484,63 @@ Meteor.methods({ }, }); -WebApp.connectHandlers.use( - '/assets/', - Meteor.bindEnvironment((req: Request, res: Response, next: NextFunction) => { - const params = { - asset: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')).replace(/\.[^.]*$/, ''), - }; +const listener = Meteor.bindEnvironment((req: IncomingMessage, res: ServerResponse, next: NextHandleFunction) => { + if (!req.url) { + return; + } + const params = { + asset: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')).replace(/\.[^.]*$/, ''), + }; - const file = getAssetByKey(params.asset) && getAssetByKey(params.asset).cache; + const asset = getAssetByKey(params.asset); + const file = asset?.cache; - const format = req.url.split('.').pop() || ''; + const format = req.url.split('.').pop() || ''; - if ( - getAssetByKey(params.asset) && - Array.isArray(getAssetByKey(params.asset).constraints.extensions) && - !getAssetByKey(params.asset).constraints.extensions.includes(format) - ) { - res.writeHead(403); - return res.end(); + if (asset && Array.isArray(asset.constraints.extensions) && !asset.constraints.extensions.includes(format)) { + res.writeHead(403); + return res.end(); + } + if (!file) { + const defaultUrl = asset?.defaultUrl; + if (defaultUrl) { + const assetUrl = format && ['png', 'svg'].includes(format) ? defaultUrl.replace(/(svg|png)$/, format) : defaultUrl; + req.url = `/${assetUrl}`; + WebAppInternals.staticFilesMiddleware((WebAppInternals as Record).staticFilesByArch, req, res, next); + } else { + res.writeHead(404); + res.end(); } - if (!file) { - const defaultUrl = getAssetByKey(params.asset) && getAssetByKey(params.asset).defaultUrl; - if (defaultUrl) { - const assetUrl = format && ['png', 'svg'].includes(format) ? defaultUrl.replace(/(svg|png)$/, format) : defaultUrl; - req.url = `/${assetUrl}`; - WebAppInternals.staticFilesMiddleware((WebAppInternals as Record).staticFilesByArch, req, res, next); - } else { - res.writeHead(404); - res.end(); - } + return; + } + + const reqModifiedHeader = req.headers['if-modified-since']; + if (reqModifiedHeader) { + if (reqModifiedHeader === (file.uploadDate && file.uploadDate.toUTCString())) { + res.setHeader('Last-Modified', reqModifiedHeader); + res.writeHead(304); + res.end(); return; } + } - const reqModifiedHeader = req.headers['if-modified-since']; - if (reqModifiedHeader) { - if (reqModifiedHeader === (file.uploadDate && file.uploadDate.toUTCString())) { - res.setHeader('Last-Modified', reqModifiedHeader); - res.writeHead(304); - res.end(); - return; - } - } + res.setHeader('Cache-Control', 'public, max-age=0'); + res.setHeader('Expires', '-1'); - res.setHeader('Cache-Control', 'public, max-age=0'); - res.setHeader('Expires', '-1'); + if (format && format !== file.extension && ['png', 'jpg', 'jpeg'].includes(format)) { + res.setHeader('Content-Type', `image/${format}`); + sharp(file.content) + .toFormat(format as any) + .pipe(res); + return; + } - if (format && format !== file.extension && ['png', 'jpg', 'jpeg'].includes(format)) { - res.setHeader('Content-Type', `image/${format}`); - sharp(file.content) - .toFormat(format as any) - .pipe(res); - return; - } + res.setHeader('Last-Modified', (file.uploadDate && file.uploadDate.toUTCString()) || new Date().toUTCString()); + res.setHeader('Content-Type', file.contentType); + res.setHeader('Content-Length', file.size); + res.writeHead(200); + res.end(file.content); +}); - res.setHeader('Last-Modified', (file.uploadDate && file.uploadDate.toUTCString()) || new Date().toUTCString()); - res.setHeader('Content-Type', file.contentType); - res.setHeader('Content-Length', file.size); - res.writeHead(200); - res.end(file.content); - }) as any, -); +WebApp.connectHandlers.use('/assets/', listener); diff --git a/apps/meteor/app/utils/lib/mimeTypes.js b/apps/meteor/app/utils/lib/mimeTypes.js deleted file mode 100644 index 70cd99776e43..000000000000 --- a/apps/meteor/app/utils/lib/mimeTypes.js +++ /dev/null @@ -1,8 +0,0 @@ -import mime from 'mime-type/with-db'; - -mime.types.wav = 'audio/wav'; -mime.define('image/vnd.microsoft.icon', { extensions: ['ico'] }, mime.dupAppend); -mime.define('image/x-icon', { extensions: ['ico'] }, mime.dupAppend); -mime.types.ico = 'image/x-icon'; - -export { mime }; diff --git a/apps/meteor/app/utils/lib/mimeTypes.ts b/apps/meteor/app/utils/lib/mimeTypes.ts new file mode 100644 index 000000000000..dd166f17bed3 --- /dev/null +++ b/apps/meteor/app/utils/lib/mimeTypes.ts @@ -0,0 +1,14 @@ +import mime from 'mime-type/with-db'; + +mime.types.wav = 'audio/wav'; +mime.define('image/vnd.microsoft.icon', { source: '', extensions: ['ico'] }, mime.dupAppend); +mime.define('image/x-icon', { source: '', extensions: ['ico'] }, mime.dupAppend); +mime.types.ico = 'image/x-icon'; + +const getExtension = (param: string): string => { + const extension = mime.extension(param); + + return !extension || typeof extension === 'boolean' ? '' : extension; +}; + +export { mime, getExtension }; diff --git a/apps/meteor/tests/unit/lib/mimeTypes.tests.ts b/apps/meteor/tests/unit/lib/mimeTypes.tests.ts new file mode 100644 index 000000000000..a3ef408741cd --- /dev/null +++ b/apps/meteor/tests/unit/lib/mimeTypes.tests.ts @@ -0,0 +1,15 @@ +import { expect } from 'chai'; + +import { getExtension } from '../../../app/utils/lib/mimeTypes'; + +describe('mimeTypes', () => { + describe('#getExtension()', () => { + it('should return an empty string if the given param is an invalid mime type', () => { + expect(getExtension('invalid-mime')).to.be.equal(''); + }); + + it('should return the correct extension when the mime type is valid', () => { + expect(getExtension('image/png')).to.be.equal('png'); + }); + }); +}); diff --git a/packages/core-typings/src/IRocketChatAssets.ts b/packages/core-typings/src/IRocketChatAssets.ts index 5af8701a46ea..b0a3c50f3ec1 100644 --- a/packages/core-typings/src/IRocketChatAssets.ts +++ b/packages/core-typings/src/IRocketChatAssets.ts @@ -29,6 +29,7 @@ export interface IRocketChatAsset { label: string; constraints: IRocketChatAssetConstraint; defaultUrl?: string; + url?: string; wizard?: IRocketChatAssetWizard; cache?: IRocketChatAssetCache; } diff --git a/packages/core-typings/src/ISetting.ts b/packages/core-typings/src/ISetting.ts index cdde985ff0c5..63236e56194b 100644 --- a/packages/core-typings/src/ISetting.ts +++ b/packages/core-typings/src/ISetting.ts @@ -1,5 +1,7 @@ import type { FilterQuery } from 'mongodb'; +import type { IRocketChatAssetConstraint } from './IRocketChatAssets'; + export type SettingId = string; export type GroupId = SettingId; export type TabId = SettingId; @@ -19,7 +21,7 @@ export interface ISettingSelectOption { i18nLabel: string; } -export type ISetting = ISettingBase | ISettingEnterprise | ISettingColor | ISettingCode | ISettingAction; +export type ISetting = ISettingBase | ISettingEnterprise | ISettingColor | ISettingCode | ISettingAction | ISettingAsset; export interface ISettingBase { _id: SettingId; @@ -119,6 +121,8 @@ export interface ISettingAction extends ISettingBase { export interface ISettingAsset extends ISettingBase { type: 'asset'; value: AssetValue; + fileConstraints: IRocketChatAssetConstraint; + asset: string; } export interface ISettingDate extends ISettingBase { From d248430c1695af7e8ab3b609e1a91892dd854cf4 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 11 May 2022 14:01:08 -0300 Subject: [PATCH 05/10] Update apps/meteor/app/api/server/v1/assets.ts --- apps/meteor/app/api/server/v1/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index da8bf3ec4e47..478f434a3836 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -36,7 +36,7 @@ API.v1.addRoute( }, ); -API.v1.addRoute<'assets.unsetAsset', { authRequired: true }>( +API.v1.addRoute( 'assets.unsetAsset', { authRequired: true }, { From 168696a3f5b757489304304e9db34a48a946cf72 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 11 May 2022 14:07:07 -0300 Subject: [PATCH 06/10] fixe review --- apps/meteor/app/api/server/v1/assets.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/api/server/v1/assets.ts b/apps/meteor/app/api/server/v1/assets.ts index 478f434a3836..04dacd04233d 100644 --- a/apps/meteor/app/api/server/v1/assets.ts +++ b/apps/meteor/app/api/server/v1/assets.ts @@ -38,23 +38,21 @@ API.v1.addRoute( API.v1.addRoute( 'assets.unsetAsset', - { authRequired: true }, + { + authRequired: true, + validateParams: isAssetsUnsetAssetProps, + }, { post() { const { assetName, refreshAllClients } = this.bodyParams; - if (!isAssetsUnsetAssetProps(this.bodyParams)) { - return API.v1.failure('invalid-body-params', isAssetsUnsetAssetProps.errors?.map((e) => e.message).join('\n ')); - } - const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName as string); + const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName); if (!isValidAsset) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); } - Meteor.runAsUser(this.userId, () => { - Meteor.call('unsetAsset', assetName); - if (refreshAllClients) { - Meteor.call('refreshClients'); - } - }); + Meteor.call('unsetAsset', assetName); + if (refreshAllClients) { + Meteor.call('refreshClients'); + } return API.v1.success(); }, }, From 8dcb19192650c7d9545f1c3c2348400e24ac7c8e Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 10 Jun 2022 10:40:28 -0300 Subject: [PATCH 07/10] fix: add missing param --- apps/meteor/app/api/server/lib/getUploadFormData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts index c262a82cd593..f4c888236f31 100644 --- a/apps/meteor/app/api/server/lib/getUploadFormData.ts +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -15,7 +15,7 @@ export interface IFormDataUpload { export const getUploadFormData = async ({ request }: { request: Request }): Promise => new Promise((resolve, reject) => { - const bb = busboy({ headers: request.headers }); + const bb = busboy({ headers: request.headers, defParamCharset: 'utf8' }); const fields: IFormDataUpload = {}; From 69e8267ee7eb8d79f9329094104536dd0a553a13 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 10 Jun 2022 11:04:06 -0300 Subject: [PATCH 08/10] chore: fix lint --- apps/meteor/app/api/server/lib/getUploadFormData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts index f4c888236f31..7ecda30716bd 100644 --- a/apps/meteor/app/api/server/lib/getUploadFormData.ts +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -15,7 +15,7 @@ export interface IFormDataUpload { export const getUploadFormData = async ({ request }: { request: Request }): Promise => new Promise((resolve, reject) => { - const bb = busboy({ headers: request.headers, defParamCharset: 'utf8' }); + const bb = busboy({ headers: request.headers, defParamCharset: 'utf8' }); const fields: IFormDataUpload = {}; From 05b67f44283e8b2a96fe02f1e228cbcf6ab8a40f Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 10 Jun 2022 13:00:06 -0300 Subject: [PATCH 09/10] fix: wrong merge --- yarn.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index a24867808837..4234d48e0c98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7018,13 +7018,6 @@ __metadata: languageName: node linkType: hard -"@types/caseless@npm:*": - version: 0.12.2 - resolution: "@types/caseless@npm:0.12.2" - checksum: 430d15911184ad11e0a8aa21d1ec15fcc93b90b63570c37bf16ebd34457482bfc8de3f5eb6771e0ef986ce183270d4297823b0f492c346255967e78f7292388b - languageName: node - linkType: hard - "@types/chai-datetime@npm:0.0.37": version: 0.0.37 resolution: "@types/chai-datetime@npm:0.0.37" From 6b4769bdcaba59e71b92f53ee7b840afb92582e7 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 10 Jun 2022 13:37:54 -0300 Subject: [PATCH 10/10] fix: types --- apps/meteor/app/api/server/lib/getUploadFormData.ts | 2 +- apps/meteor/app/api/server/v1/emoji-custom.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts index 7ecda30716bd..c3ce2652ca1a 100644 --- a/apps/meteor/app/api/server/lib/getUploadFormData.ts +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -10,7 +10,7 @@ export interface IFormDataFields { } export interface IFormDataUpload { - [key: string]: IFormDataFields; + [key: string]: IFormDataFields | any; } export const getUploadFormData = async ({ request }: { request: Request }): Promise => diff --git a/apps/meteor/app/api/server/v1/emoji-custom.ts b/apps/meteor/app/api/server/v1/emoji-custom.ts index fe16e3ed2392..671d26fc1ef2 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.ts +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { Request } from 'express'; import { EmojiCustom } from '../../../models/server/raw'; import { API } from '../api'; @@ -68,7 +69,7 @@ API.v1.addRoute( { async post() { const { emoji, ...fields } = await getUploadFormData({ - request: this.request, + request: this.request as Request, }); if (!emoji) { @@ -100,7 +101,7 @@ API.v1.addRoute( { async post() { const { emoji, ...fields } = await getUploadFormData({ - request: this.request, + request: this.request as Request, }); if (!fields._id) {