From 20b0976fbbb3d1cbee55e315813bef1c993fc06b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 27 Apr 2023 16:07:09 -0300 Subject: [PATCH] refactor: Remove wrapAsync (#28825) Co-authored-by: Guilherme Gazzo --- .../meteor/app/apps/server/bridges/uploads.ts | 16 +--- .../federation/server/endpoints/uploads.js | 2 +- .../meteor/app/federation/server/lib/crypt.js | 2 +- apps/meteor/app/federation/server/lib/dns.js | 18 ++-- .../file-upload/server/config/FileSystem.ts | 14 +-- .../app/file-upload/server/lib/FileUpload.ts | 22 +++-- .../importer-pending-files/server/importer.js | 11 +-- .../meteor/app/integrations/server/api/api.js | 5 +- .../integrations/server/lib/triggerHandler.js | 5 +- .../app/lib/server/functions/saveUser.js | 2 +- .../app/lib/server/functions/setEmail.ts | 2 +- .../app/lib/server/lib/validateEmailDomain.js | 7 +- .../app/livechat/server/lib/Livechat.js | 5 +- .../meteor-accounts-saml/server/lib/SAML.ts | 28 +++--- .../server/lib/ServiceProvider.ts | 96 +++++++++---------- .../server/methods/samlLogout.ts | 4 +- .../EmailInbox/EmailInbox_Outgoing.ts | 51 +++++----- apps/meteor/server/lib/http/call.ts | 38 +++++--- apps/meteor/server/main.ts | 1 - apps/meteor/server/methods/registerUser.ts | 2 +- apps/meteor/server/overrides/http.ts | 7 -- .../rocket-chat/adapters/File.ts | 20 ++-- apps/meteor/server/services/upload/service.ts | 19 ++-- 23 files changed, 183 insertions(+), 194 deletions(-) delete mode 100644 apps/meteor/server/overrides/http.ts diff --git a/apps/meteor/app/apps/server/bridges/uploads.ts b/apps/meteor/app/apps/server/bridges/uploads.ts index 4368b1dba6d..1f399e3ff4a 100644 --- a/apps/meteor/app/apps/server/bridges/uploads.ts +++ b/apps/meteor/app/apps/server/bridges/uploads.ts @@ -31,19 +31,13 @@ export class AppUploadBridge extends UploadBridge { const rocketChatUpload = this.orch.getConverters()?.get('uploads').convertToRocketChat(upload); - return new Promise((resolve, reject) => { - FileUpload.getBuffer(rocketChatUpload, (error?: Error, result?: Buffer | false) => { - if (error) { - return reject(error); - } + const result = await FileUpload.getBuffer(rocketChatUpload); - if (!(result instanceof Buffer)) { - return reject(new Error('Unknown error')); - } + if (!(result instanceof Buffer)) { + throw new Error('Unknown error'); + } - resolve(result); - }); - }); + return result; } protected async createUpload(details: IUploadDetails, buffer: Buffer, appId: string): Promise { diff --git a/apps/meteor/app/federation/server/endpoints/uploads.js b/apps/meteor/app/federation/server/endpoints/uploads.js index 0d3860723fb..da5740f95f1 100644 --- a/apps/meteor/app/federation/server/endpoints/uploads.js +++ b/apps/meteor/app/federation/server/endpoints/uploads.js @@ -21,7 +21,7 @@ API.v1.addRoute( return API.v1.failure('There is no such file in this server'); } - const buffer = FileUpload.getBufferSync(upload); + const buffer = await FileUpload.getBuffer(upload); return API.v1.success({ upload, buffer }); }, diff --git a/apps/meteor/app/federation/server/lib/crypt.js b/apps/meteor/app/federation/server/lib/crypt.js index 02bdce50a41..dde280524e2 100644 --- a/apps/meteor/app/federation/server/lib/crypt.js +++ b/apps/meteor/app/federation/server/lib/crypt.js @@ -40,7 +40,7 @@ export async function decryptIfNeeded(request, bodyParams) { } // // Find the peer's public key - const { publicKey: peerKey } = search(remotePeerDomain); + const { publicKey: peerKey } = await search(remotePeerDomain); if (!peerKey) { throw new Error("Could not find the peer's public key to decrypt"); diff --git a/apps/meteor/app/federation/server/lib/dns.js b/apps/meteor/app/federation/server/lib/dns.js index d119dedff87..928e4f9e440 100644 --- a/apps/meteor/app/federation/server/lib/dns.js +++ b/apps/meteor/app/federation/server/lib/dns.js @@ -1,6 +1,6 @@ import dnsResolver from 'dns'; +import util from 'util'; -import { Meteor } from 'meteor/meteor'; import mem from 'mem'; import * as federationErrors from '../functions/errors'; @@ -8,8 +8,8 @@ import { dnsLogger } from './logger'; import { isFederationEnabled } from './isFederationEnabled'; import { federationRequest } from './http'; -const dnsResolveSRV = Meteor.wrapAsync(dnsResolver.resolveSrv); -const dnsResolveTXT = Meteor.wrapAsync(dnsResolver.resolveTxt); +const dnsResolveSRV = util.promisify(dnsResolver.resolveSrv); +const dnsResolveTXT = util.promisify(dnsResolver.resolveTxt); const cacheMaxAge = 3600000; // one hour const memoizedDnsResolveSRV = mem(dnsResolveSRV, { maxAge: cacheMaxAge }); @@ -62,7 +62,7 @@ async function searchHub(peerDomain) { } } -export function search(peerDomain) { +export async function search(peerDomain) { if (!isFederationEnabled()) { throw federationErrors.disabled('dns.search'); } @@ -75,7 +75,7 @@ export function search(peerDomain) { // Search by HTTPS first try { dnsLogger.debug(`search: peerDomain=${peerDomain} srv=_rocketchat._https.${peerDomain}`); - srvEntries = memoizedDnsResolveSRV(`_rocketchat._https.${peerDomain}`); + srvEntries = await memoizedDnsResolveSRV(`_rocketchat._https.${peerDomain}`); protocol = 'https'; } catch (err) { // Ignore errors when looking for DNS entries @@ -85,7 +85,7 @@ export function search(peerDomain) { if (!srvEntries.length) { try { dnsLogger.debug(`search: peerDomain=${peerDomain} srv=_rocketchat._http.${peerDomain}`); - srvEntries = memoizedDnsResolveSRV(`_rocketchat._http.${peerDomain}`); + srvEntries = await memoizedDnsResolveSRV(`_rocketchat._http.${peerDomain}`); protocol = 'http'; } catch (err) { // Ignore errors when looking for DNS entries @@ -96,12 +96,12 @@ export function search(peerDomain) { if (!srvEntries.length) { try { dnsLogger.debug(`search: peerDomain=${peerDomain} srv=_rocketchat._tcp.${peerDomain}`); - srvEntries = memoizedDnsResolveSRV(`_rocketchat._tcp.${peerDomain}`); + srvEntries = await memoizedDnsResolveSRV(`_rocketchat._tcp.${peerDomain}`); protocol = 'https'; // https is the default // Then, also try to get the protocol dnsLogger.debug(`search: peerDomain=${peerDomain} txt=rocketchat-tcp-protocol.${peerDomain}`); - protocol = memoizedDnsResolveSRV(`rocketchat-tcp-protocol.${peerDomain}`); + protocol = await memoizedDnsResolveSRV(`rocketchat-tcp-protocol.${peerDomain}`); protocol = protocol[0].join(''); if (protocol !== 'http' && protocol !== 'https') { @@ -131,7 +131,7 @@ export function search(peerDomain) { // Get the public key from the TXT record try { dnsLogger.debug(`search: peerDomain=${peerDomain} txt=rocketchat-public-key.${peerDomain}`); - const publicKeyTxtRecords = memoizedDnsResolveTXT(`rocketchat-public-key.${peerDomain}`); + const publicKeyTxtRecords = await memoizedDnsResolveTXT(`rocketchat-public-key.${peerDomain}`); // Join the TXT record, that might be split publicKey = publicKeyTxtRecords[0].join(''); diff --git a/apps/meteor/app/file-upload/server/config/FileSystem.ts b/apps/meteor/app/file-upload/server/config/FileSystem.ts index 94217a6457c..e4b6afb80a0 100644 --- a/apps/meteor/app/file-upload/server/config/FileSystem.ts +++ b/apps/meteor/app/file-upload/server/config/FileSystem.ts @@ -1,14 +1,10 @@ -import fs from 'fs'; - -import { Meteor } from 'meteor/meteor'; +import fsp from 'fs/promises'; import { UploadFS } from '../../../../server/ufs'; import { settings } from '../../../settings/server'; import { FileUploadClass, FileUpload } from '../lib/FileUpload'; import { getFileRange, setRangeHeaders } from '../lib/ranges'; -const statSync = Meteor.wrapAsync(fs.stat); - const FileSystemUploads = new FileUploadClass({ name: 'FileSystem:Uploads', // store setted bellow @@ -22,7 +18,7 @@ const FileSystemUploads = new FileUploadClass({ const options: { start?: number; end?: number } = {}; try { - const stat = statSync(filePath); + const stat = await fsp.stat(filePath); if (!stat?.isFile()) { res.writeHead(404); res.end(); @@ -65,7 +61,7 @@ const FileSystemUploads = new FileUploadClass({ } const filePath = await this.store.getFilePath(file._id, file); try { - const stat = statSync(filePath); + const stat = await fsp.stat(filePath); if (stat?.isFile()) { file = FileUpload.addExtensionTo(file); @@ -89,7 +85,7 @@ const FileSystemAvatars = new FileUploadClass({ const filePath = await this.store.getFilePath(file._id, file); try { - const stat = statSync(filePath); + const stat = await fsp.stat(filePath); if (stat?.isFile()) { file = FileUpload.addExtensionTo(file); @@ -113,7 +109,7 @@ const FileSystemUserDataFiles = new FileUploadClass({ const filePath = await this.store.getFilePath(file._id, file); try { - const stat = statSync(filePath); + const stat = await fsp.stat(filePath); if (stat?.isFile()) { file = FileUpload.addExtensionTo(file); diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index a6c5c4e7d12..26ee6c19085 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -290,7 +290,7 @@ export const FileUpload = { }, async extractMetadata(file: IUpload) { - return sharp(FileUpload.getBufferSync(file)).metadata(); + return sharp(await FileUpload.getBuffer(file)).metadata(); }, async createImageThumbnail(fileParam: IUpload) { @@ -508,26 +508,30 @@ export const FileUpload = { res.end(); }, - getBuffer(file: IUpload, cb: (err?: Error, data?: false | Buffer) => void) { + async getBuffer(file: IUpload): Promise { const store = this.getStoreByName(file.store); if (!store?.get) { - cb(new Error('Store is invalid'), undefined); + throw new Error('Store is invalid'); } const buffer = new streamBuffers.WritableStreamBuffer({ initialSize: file.size, }); - buffer.on('finish', () => { - cb(undefined, buffer.getContents()); - }); + return new Promise((resolve, reject) => { + buffer.on('finish', () => { + const contents = buffer.getContents(); + if (contents === false) { + return reject(); + } + resolve(contents); + }); - void store.copy?.(file, buffer); + void store.copy?.(file, buffer); + }); }, - getBufferSync: Meteor.wrapAsync((file: IUpload, cb: (err?: Error, data?: false | Buffer) => void) => FileUpload.getBuffer(file, cb)), - async copy(file: IUpload, targetFile: string) { const store = this.getStoreByName(file.store); const out = fs.createWriteStream(targetFile); diff --git a/apps/meteor/app/importer-pending-files/server/importer.js b/apps/meteor/app/importer-pending-files/server/importer.js index cc9a71f1cc8..69bc24a9071 100644 --- a/apps/meteor/app/importer-pending-files/server/importer.js +++ b/apps/meteor/app/importer-pending-files/server/importer.js @@ -1,7 +1,6 @@ import https from 'https'; import http from 'http'; -import { Meteor } from 'meteor/meteor'; import { Random } from '@rocket.chat/random'; import { Messages } from '@rocket.chat/models'; @@ -48,12 +47,12 @@ export class PendingFileImporter extends Base { let currentSize = 0; let nextSize = 0; - const waitForFiles = () => { + const waitForFiles = async () => { if (count + 1 < maxFileCount && currentSize + nextSize < maxFileSize) { return; } - Meteor.wrapAsync((callback) => { + return new Promise((resolve) => { const handler = setInterval(() => { if (count + 1 >= maxFileCount) { return; @@ -64,9 +63,9 @@ export class PendingFileImporter extends Base { } clearInterval(handler); - callback(); + resolve(); }, 1000); - })(); + }); }; const completeFile = async (details) => { @@ -109,7 +108,7 @@ export class PendingFileImporter extends Base { const reportProgress = this.reportProgress.bind(this); nextSize = details.size; - waitForFiles(); + await waitForFiles(); count++; currentSize += nextSize; downloadedFileIds.push(_importFile.id); diff --git a/apps/meteor/app/integrations/server/api/api.js b/apps/meteor/app/integrations/server/api/api.js index df3c7098289..96e130fcc91 100644 --- a/apps/meteor/app/integrations/server/api/api.js +++ b/apps/meteor/app/integrations/server/api/api.js @@ -1,6 +1,5 @@ import { VM, VMScript } from 'vm2'; import { Meteor } from 'meteor/meteor'; -import { HTTP } from 'meteor/http'; import { Random } from '@rocket.chat/random'; import { Livechat } from 'meteor/rocketchat:livechat'; import Fiber from 'fibers'; @@ -15,6 +14,7 @@ import { incomingLogger } from '../logger'; import { processWebhookMessage } from '../../../lib/server/functions/processWebhookMessage'; import { API, APIClass, defaultRateLimiterOptions } from '../../../api/server'; import { settings } from '../../../settings/server'; +import { httpCall } from '../../../../server/lib/http/call'; import { deleteOutgoingIntegration } from '../methods/outgoing/deleteOutgoingIntegration'; export const forbiddenModelMethods = ['registerModel', 'getCollectionName']; @@ -43,8 +43,9 @@ function buildSandbox(store = {}) { }, HTTP(method, url, options) { try { + // Need to review how we will handle this, possible breaking change on removing fibers return { - result: HTTP.call(method, url, options), + result: Promise.await(httpCall(method, url, options)), }; } catch (error) { return { diff --git a/apps/meteor/app/integrations/server/lib/triggerHandler.js b/apps/meteor/app/integrations/server/lib/triggerHandler.js index 5fabc7ca147..d0a0f59d3e1 100644 --- a/apps/meteor/app/integrations/server/lib/triggerHandler.js +++ b/apps/meteor/app/integrations/server/lib/triggerHandler.js @@ -1,7 +1,6 @@ import { VM, VMScript } from 'vm2'; import { Meteor } from 'meteor/meteor'; import { Random } from '@rocket.chat/random'; -import { HTTP } from 'meteor/http'; import _ from 'underscore'; import moment from 'moment'; import Fiber from 'fibers'; @@ -18,6 +17,7 @@ import { outgoingLogger } from '../logger'; import { outgoingEvents } from '../../lib/outgoingEvents'; import { omit } from '../../../../lib/utils/omit'; import { forbiddenModelMethods } from '../api/api'; +import { httpCall } from '../../../../server/lib/http/call'; class RocketChatIntegrationHandler { constructor() { @@ -247,8 +247,9 @@ class RocketChatIntegrationHandler { }, HTTP: (method, url, options) => { try { + // Need to review how we will handle this, possible breaking change on removing fibers return { - result: HTTP.call(method, url, options), + result: Promise.await(httpCall(method, url, options)), }; } catch (error) { return { error }; diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index 66bbf90ca50..849a96260dd 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -266,7 +266,7 @@ const handleNickname = (updateUser, nickname) => { }; const saveNewUser = async function (userData, sendPassword) { - validateEmailDomain(userData.email); + await validateEmailDomain(userData.email); const roles = (!!userData.roles && userData.roles.length > 0 && userData.roles) || getNewUserRoles(); const isGuest = roles && roles.length === 1 && roles.includes('guest'); diff --git a/apps/meteor/app/lib/server/functions/setEmail.ts b/apps/meteor/app/lib/server/functions/setEmail.ts index ce28a141101..a23ab5ff8f8 100644 --- a/apps/meteor/app/lib/server/functions/setEmail.ts +++ b/apps/meteor/app/lib/server/functions/setEmail.ts @@ -47,7 +47,7 @@ const _setEmail = async function (userId: string, email: string, shouldSendVerif throw new Meteor.Error('error-invalid-email', 'Invalid email', { function: '_setEmail' }); } - validateEmailDomain(email); + await validateEmailDomain(email); const user = await Users.findOneById(userId); if (!user) { diff --git a/apps/meteor/app/lib/server/lib/validateEmailDomain.js b/apps/meteor/app/lib/server/lib/validateEmailDomain.js index b794f170191..18ff8404d8a 100644 --- a/apps/meteor/app/lib/server/lib/validateEmailDomain.js +++ b/apps/meteor/app/lib/server/lib/validateEmailDomain.js @@ -1,4 +1,5 @@ import dns from 'dns'; +import util from 'util'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +7,7 @@ import { emailDomainDefaultBlackList } from './defaultBlockedDomainsList'; import { settings } from '../../../settings/server'; import { validateEmail } from '../../../../lib/emailValidator'; -const dnsResolveMx = Meteor.wrapAsync(dns.resolveMx); +const dnsResolveMx = util.promisify(dns.resolveMx); let emailDomainBlackList = []; let emailDomainWhiteList = []; @@ -34,7 +35,7 @@ settings.watch('Accounts_AllowedDomainsList', function (value) { .map((domain) => domain.trim()); }); -export const validateEmailDomain = function (email) { +export const validateEmailDomain = async function (email) { if (!validateEmail(email)) { throw new Meteor.Error('error-invalid-email', `Invalid email ${email}`, { function: 'RocketChat.validateEmailDomain', @@ -61,7 +62,7 @@ export const validateEmailDomain = function (email) { if (settings.get('Accounts_UseDNSDomainCheck')) { try { - dnsResolveMx(emailDomain); + await dnsResolveMx(emailDomain); } catch (e) { throw new Meteor.Error('error-invalid-domain', 'Invalid domain', { function: 'RocketChat.validateEmailDomain', diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 807757f176f..a4257fdebe6 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -2,6 +2,7 @@ // Please add new methods to LivechatTyped.ts import dns from 'dns'; +import util from 'util'; import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; @@ -47,7 +48,7 @@ import { Livechat as LivechatTyped } from './LivechatTyped'; const logger = new Logger('Livechat'); -const dnsResolveMx = Meteor.wrapAsync(dns.resolveMx); +const dnsResolveMx = util.promisify(dns.resolveMx); export const Livechat = { Analytics, @@ -1183,7 +1184,7 @@ export const Livechat = { const emailDomain = email.substr(email.lastIndexOf('@') + 1); try { - dnsResolveMx(emailDomain); + await dnsResolveMx(emailDomain); } catch (e) { throw new Meteor.Error('error-invalid-email-address', 'Invalid email address', { method: 'livechat:sendOfflineMessage', diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts index 52bfe571ef2..3d40cf1e0b5 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts @@ -374,22 +374,28 @@ export class SAML { res.end(); } - private static processAuthorizeAction(res: ServerResponse, service: IServiceProviderOptions, samlObject: ISAMLAction): void { + private static async processAuthorizeAction( + res: ServerResponse, + service: IServiceProviderOptions, + samlObject: ISAMLAction, + ): Promise { service.id = samlObject.credentialToken; const serviceProvider = new SAMLServiceProvider(service); - serviceProvider.getAuthorizeUrl((err, url) => { - if (err) { - SAMLUtils.error('Unable to generate authorize url'); - SAMLUtils.error(err); - url = Meteor.absoluteUrl(); - } + let url: string | undefined; - res.writeHead(302, { - Location: url, - }); - res.end(); + try { + url = await serviceProvider.getAuthorizeUrl(); + } catch (err: any) { + SAMLUtils.error('Unable to generate authorize url'); + SAMLUtils.error(err); + url = Meteor.absoluteUrl(); + } + + res.writeHead(302, { + Location: url, }); + res.end(); } private static processValidateAction( diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/ServiceProvider.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/ServiceProvider.ts index 426f03fee82..a8094e63fb2 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/ServiceProvider.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/ServiceProvider.ts @@ -1,6 +1,7 @@ import zlib from 'zlib'; import crypto from 'crypto'; import querystring from 'querystring'; +import util from 'util'; import { Meteor } from 'meteor/meteor'; @@ -20,18 +21,12 @@ import type { ILogoutRequestValidateCallback, ILogoutResponseValidateCallback, I export class SAMLServiceProvider { serviceProviderOptions: IServiceProviderOptions; - syncRequestToUrl: Function; - constructor(serviceProviderOptions: IServiceProviderOptions) { if (!serviceProviderOptions) { throw new Error('SAMLServiceProvider instantiated without an options object'); } this.serviceProviderOptions = serviceProviderOptions; - - this.syncRequestToUrl = Meteor.wrapAsync< - (request: string, operation: string, callback: (err: string | object | null, url?: string | undefined) => void) => void - >(this.requestToUrl, this); } private signRequest(xml: string): string { @@ -105,68 +100,63 @@ export class SAMLServiceProvider { /* This method will generate the request URL with all the query string params and pass it to the callback */ - public requestToUrl(request: string, operation: string, callback: (err: string | object | null, url?: string) => void): void { - zlib.deflateRaw(request, (err, buffer) => { - if (err) { - return callback(err); - } - - try { - const base64 = buffer.toString('base64'); - let target = this.serviceProviderOptions.entryPoint; - - if (operation === 'logout') { - if (this.serviceProviderOptions.idpSLORedirectURL) { - target = this.serviceProviderOptions.idpSLORedirectURL; - } + public async requestToUrl(request: string, operation: string): Promise { + const buffer = await util.promisify(zlib.deflateRaw)(request); + try { + const base64 = buffer.toString('base64'); + let target = this.serviceProviderOptions.entryPoint; + + if (operation === 'logout') { + if (this.serviceProviderOptions.idpSLORedirectURL) { + target = this.serviceProviderOptions.idpSLORedirectURL; } + } - if (target.indexOf('?') > 0) { - target += '&'; - } else { - target += '?'; - } + if (target.indexOf('?') > 0) { + target += '&'; + } else { + target += '?'; + } - // TBD. We should really include a proper RelayState here - let relayState; - if (operation === 'logout') { - // in case of logout we want to be redirected back to the Meteor app. - relayState = Meteor.absoluteUrl(); - } else { - relayState = this.serviceProviderOptions.provider; - } + // TBD. We should really include a proper RelayState here + let relayState; + if (operation === 'logout') { + // in case of logout we want to be redirected back to the Meteor app. + relayState = Meteor.absoluteUrl(); + } else { + relayState = this.serviceProviderOptions.provider; + } - const samlRequest: Record = { - SAMLRequest: base64, - RelayState: relayState, - }; + const samlRequest: Record = { + SAMLRequest: base64, + RelayState: relayState, + }; - if (this.serviceProviderOptions.privateCert) { - samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - samlRequest.Signature = this.signRequest(querystring.stringify(samlRequest)); - } + if (this.serviceProviderOptions.privateCert) { + samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + samlRequest.Signature = this.signRequest(querystring.stringify(samlRequest)); + } - target += querystring.stringify(samlRequest); + target += querystring.stringify(samlRequest); - SAMLUtils.log(`requestToUrl: ${target}`); + SAMLUtils.log(`requestToUrl: ${target}`); - if (operation === 'logout') { - // in case of logout we want to be redirected back to the Meteor app. - return callback(null, target); - } - callback(null, target); - } catch (error) { - callback(error instanceof Error ? error : String(error)); + if (operation === 'logout') { + // in case of logout we want to be redirected back to the Meteor app. + return target; } - }); + return target; + } catch (error) { + throw error instanceof Error ? error : String(error); + } } - public getAuthorizeUrl(callback: (err: string | object | null, url?: string) => void): void { + public async getAuthorizeUrl(): Promise { const request = this.generateAuthorizeRequest(); SAMLUtils.log('-----REQUEST------'); SAMLUtils.log(request); - this.requestToUrl(request, 'authorize', callback); + return this.requestToUrl(request, 'authorize'); } public async validateLogoutRequest(samlRequest: string, callback: ILogoutRequestValidateCallback): Promise { diff --git a/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts b/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts index 909adb7c0b5..877e3f96b9b 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts @@ -28,7 +28,7 @@ function getSamlServiceProviderOptions(provider: string): IServiceProviderOption declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - samlLogout(provider: string): Promise; + samlLogout(provider: string): Promise; } } @@ -71,7 +71,7 @@ Meteor.methods({ await Users.setSamlInResponseTo(userId, request.id); - const result = _saml.syncRequestToUrl(request.request, 'logout'); + const result = await _saml.requestToUrl(request.request, 'logout'); SAMLUtils.log(`SAML Logout Request ${result}`); return result; diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts index 1c8cdeb8dd1..837f7a39517 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts @@ -124,32 +124,31 @@ slashCommands.add({ return; } - FileUpload.getBuffer(file, (_err?: Error, buffer?: Buffer | false) => { - !_err && - buffer && - void sendEmail( - inbox, - { - to: room.email?.replyTo, - subject: room.email?.subject, - text: message?.attachments?.[0].description || '', - attachments: [ - { - content: buffer, - contentType: file.type, - filename: file.name, - }, - ], - inReplyTo: Array.isArray(room.email?.thread) ? room.email?.thread[0] : room.email?.thread, - references: ([] as string[]).concat(room.email?.thread || []), - }, - { - msgId: message._id, - sender: message.u.username, - rid: message.rid, - }, - ).then((info) => LivechatRooms.updateEmailThreadByRoomId(room._id, info.messageId)); - }); + const buffer = await FileUpload.getBuffer(file); + if (buffer) { + void sendEmail( + inbox, + { + to: room.email?.replyTo, + subject: room.email?.subject, + text: message?.attachments?.[0].description || '', + attachments: [ + { + content: buffer, + contentType: file.type, + filename: file.name, + }, + ], + inReplyTo: Array.isArray(room.email?.thread) ? room.email?.thread[0] : room.email?.thread, + references: ([] as string[]).concat(room.email?.thread || []), + }, + { + msgId: message._id, + sender: message.u.username, + rid: message.rid, + }, + ).then((info) => LivechatRooms.updateEmailThreadByRoomId(room._id, info.messageId)); + } await Messages.updateOne( { _id: message._id }, diff --git a/apps/meteor/server/lib/http/call.ts b/apps/meteor/server/lib/http/call.ts index d00eae785cb..521b242ace8 100644 --- a/apps/meteor/server/lib/http/call.ts +++ b/apps/meteor/server/lib/http/call.ts @@ -1,4 +1,4 @@ -import { Meteor } from 'meteor/meteor'; +import { HTTP } from 'meteor/http'; import { URL, URLSearchParams } from 'meteor/url'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; @@ -25,10 +25,17 @@ type HttpCallOptions = { integrity?: string; }; -type callbackFn = { - (error: unknown): void; - (error: unknown, result: unknown): void; -}; +// eslint-disable-next-line @typescript-eslint/naming-convention +interface HTTPResponse { + statusCode?: number; + headers?: { [id: string]: string }; + content?: string; + data?: any; + ok?: boolean; + redirected?: boolean; +} + +type callbackFn = (error: Error | undefined, result?: HTTPResponse) => void; // Fill in `response.data` if the content-type is JSON. function populateData(response: Record): void { @@ -49,7 +56,7 @@ function populateData(response: Record): void { } } -function makeErrorByStatus(statusCode: string, content: string): Error { +function makeErrorByStatus(statusCode: number, content: string): Error { let message = `failed [${statusCode}]`; if (content) { @@ -117,9 +124,9 @@ function _call(httpMethod: string, url: string, options: HttpCallOptions, callba // wrap callback to add a 'response' property on an error, in case // we have both (http 4xx/5xx error, which has a response payload) - const wrappedCallback = ((cb: callbackFn): { (error: unknown, response?: unknown): void } => { + const wrappedCallback = ((cb: callbackFn): { (error: Error | undefined, response?: HTTPResponse): void } => { let called = false; - return (error: unknown, response: unknown): void => { + return (error: Error | undefined, response?: HTTPResponse): void => { if (!called) { called = true; if (error && response) { @@ -147,7 +154,7 @@ function _call(httpMethod: string, url: string, options: HttpCallOptions, callba fetch(newUrl, requestOptions) .then(async (res) => { const content = await res.text(); - const response: Record = {}; + const response: HTTPResponse = {}; response.statusCode = res.status; response.content = `${content}`; @@ -177,7 +184,7 @@ function _call(httpMethod: string, url: string, options: HttpCallOptions, callba function httpCallAsync(httpMethod: string, url: string, options: HttpCallOptions, callback: callbackFn): void; function httpCallAsync(httpMethod: string, url: string, callback: callbackFn): void; function httpCallAsync(httpMethod: string, url: string, optionsOrCallback: HttpCallOptions | callbackFn = {}, callback?: callbackFn): void { - // If the options argument was ommited, adjust the arguments: + // If the options argument was omitted, adjust the arguments: if (!callback && typeof optionsOrCallback === 'function') { return _call(httpMethod, url, {}, optionsOrCallback as callbackFn); } @@ -185,4 +192,13 @@ function httpCallAsync(httpMethod: string, url: string, optionsOrCallback: HttpC return _call(httpMethod, url, optionsOrCallback as HttpCallOptions, callback as callbackFn); } -export const httpCall = Meteor.wrapAsync(httpCallAsync); +export const httpCall = async (httpMethod: string, url: string, options: HttpCallOptions) => { + return new Promise((resolve, reject) => { + httpCallAsync.bind(HTTP)(httpMethod, url, options, (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }); + }); +}; diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index b233d0238b7..5579261911f 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -4,7 +4,6 @@ import '../ee/server/models/startup'; import './services/startup'; import '../app/settings/server'; import '../lib/oauthRedirectUriServer'; -import './overrides/http'; import './lib/logger/startup'; import './importPackages'; import '../imports/startup/server'; diff --git a/apps/meteor/server/methods/registerUser.ts b/apps/meteor/server/methods/registerUser.ts index 95f26330c61..535203f3369 100644 --- a/apps/meteor/server/methods/registerUser.ts +++ b/apps/meteor/server/methods/registerUser.ts @@ -84,7 +84,7 @@ Meteor.methods({ passwordPolicy.validate(formData.pass); - validateEmailDomain(formData.email); + await validateEmailDomain(formData.email); const userData = { email: trim(formData.email.toLowerCase()), diff --git a/apps/meteor/server/overrides/http.ts b/apps/meteor/server/overrides/http.ts deleted file mode 100644 index f85e555a792..00000000000 --- a/apps/meteor/server/overrides/http.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { HTTP } from 'meteor/http'; - -import { httpCall } from '../lib/http/call'; - -HTTP.call = function _call(...args: Parameters): ReturnType { - return httpCall.call(HTTP, ...args); -}; diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/File.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/File.ts index 6121c64615c..8538c6423b7 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/File.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/File.ts @@ -34,17 +34,11 @@ export class RocketChatFileAdapter { } public async getBufferFromFileRecord(fileRecord: IUpload): Promise { - return new Promise((resolve, reject) => { - FileUpload.getBuffer(fileRecord, (err?: Error, buffer?: Buffer | false) => { - if (err) { - return reject(err); - } - if (!(buffer instanceof Buffer)) { - return reject(new Error('Unknown error')); - } - resolve(buffer); - }); - }); + const buffer = await FileUpload.getBuffer(fileRecord); + if (!(buffer instanceof Buffer)) { + throw new Error('Unknown error'); + } + return buffer; } public async getFileRecordById(fileId: string): Promise { @@ -71,11 +65,11 @@ export class RocketChatFileAdapter { } public async getBufferForAvatarFile(username: string): Promise { - const file = (await Avatars.findOneByName(username)) as Record; + const file = await Avatars.findOneByName(username); if (!file?._id) { return; } - return FileUpload.getBufferSync(file); + return FileUpload.getBuffer(file); } public async getFileMetadataForAvatarFile(username: string): Promise { diff --git a/apps/meteor/server/services/upload/service.ts b/apps/meteor/server/services/upload/service.ts index 016b04dfe46..bb1e3d398a2 100644 --- a/apps/meteor/server/services/upload/service.ts +++ b/apps/meteor/server/services/upload/service.ts @@ -24,18 +24,13 @@ export class UploadService extends ServiceClassInternal implements IUploadServic } async getFileBuffer({ userId, file }: { userId: string; file: IUpload }): Promise { - return Meteor.runAsUser(userId, () => { - return new Promise((resolve, reject) => { - FileUpload.getBuffer(file, (err?: Error, buffer?: false | Buffer) => { - if (err) { - return reject(err); - } - if (!(buffer instanceof Buffer)) { - return reject(new Error('Unknown error')); - } - return resolve(buffer); - }); - }); + return Meteor.runAsUser(userId, async () => { + const buffer = await FileUpload.getBuffer(file); + + if (!(buffer instanceof Buffer)) { + throw new Error('Unknown error'); + } + return buffer; }); } }