From 71aad15420145b860e1ed17208410d45854ed4e9 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 15 Dec 2023 09:14:08 +0100 Subject: [PATCH 1/4] fix: improvements to Wasabi provider --- packages/common/src/utils/shared-utils.ts | 33 +-- .../providers/wasabi-s3.provider.ts | 228 ++++++++++-------- 2 files changed, 142 insertions(+), 119 deletions(-) diff --git a/packages/common/src/utils/shared-utils.ts b/packages/common/src/utils/shared-utils.ts index 68e5c5208b1..5573b73055e 100644 --- a/packages/common/src/utils/shared-utils.ts +++ b/packages/common/src/utils/shared-utils.ts @@ -6,7 +6,7 @@ import slugify from 'slugify'; * @returns {boolean} */ export function isFunction(item: any): boolean { - return (item && typeof item === 'function' && !Array.isArray(item)); + return item && typeof item === 'function' && !Array.isArray(item); } /** @@ -16,7 +16,7 @@ export function isFunction(item: any): boolean { * From https://stackoverflow.com/a/34749873/772859 */ export function isObject(item: any) { - return (item && typeof item === 'object' && !Array.isArray(item)); + return item && typeof item === 'object' && !Array.isArray(item); } /** @@ -57,28 +57,18 @@ export function isEmpty(item: any) { return item.length === 0; } else if (item && typeof item === 'object') { for (var key in item) { - if ( - item[key] === null || - item[key] === undefined || - item[key] === '' - ) { + if (item[key] === null || item[key] === undefined || item[key] === '') { delete item[key]; } } return Object.keys(item).length === 0; } else { - return ( - !item || - (item + '').toLocaleLowerCase() === 'null' || - (item + '').toLocaleLowerCase() === 'undefined' - ); + return !item || (item + '').toLocaleLowerCase() === 'null' || (item + '').toLocaleLowerCase() === 'undefined'; } } export function isJsObject(object: any) { - return ( - object !== null && object !== undefined && typeof object === 'object' - ); + return object !== null && object !== undefined && typeof object === 'object'; } /* @@ -136,7 +126,7 @@ export function removeDuplicates(data: string[]) { * @returns */ export function isNullOrUndefined(string: T | null | undefined): string is null | undefined { - return typeof string === "undefined" || string === null + return typeof string === 'undefined' || string === null; } /** @@ -151,9 +141,7 @@ export function chunks(items: any[], size: number): any[] { const chunks = []; items = [].concat(...items); while (items.length) { - chunks.push( - items.splice(0, size) - ) + chunks.push(items.splice(0, size)); } return chunks; } @@ -199,7 +187,7 @@ export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); */ export const trimAndGetValue = (value?: string): string | undefined => { return isNotEmpty(value) ? value.trim() : undefined; -} +}; /** * Adds "http://" prefix to a URL if it's missing. @@ -207,8 +195,9 @@ export const trimAndGetValue = (value?: string): string | undefined => { * @returns The URL with the "http://" prefix. */ export const addHttpsPrefix = (url: string, prefix = 'https'): string => { - if (!url.startsWith('http://') && !url.startsWith('https://')) { + if (url && !url.startsWith('http://') && !url.startsWith('https://')) { return `${prefix}://${url}`; } + return url; -} +}; diff --git a/packages/core/src/core/file-storage/providers/wasabi-s3.provider.ts b/packages/core/src/core/file-storage/providers/wasabi-s3.provider.ts index cb371977240..2011d2a61cb 100644 --- a/packages/core/src/core/file-storage/providers/wasabi-s3.provider.ts +++ b/packages/core/src/core/file-storage/providers/wasabi-s3.provider.ts @@ -101,7 +101,6 @@ const WASABI_REGION_SERVICE_URLS: IWasabiRegionServiceURL[] = [ ]; export class WasabiS3Provider extends Provider { - public instance: WasabiS3Provider; public readonly name = FileStorageProviderEnum.WASABI; public config: IWasabiProviderConfig; @@ -119,9 +118,9 @@ export class WasabiS3Provider extends Provider { } /** - * Get the singleton instance of WasabiS3Provider - * @returns {WasabiS3Provider} The singleton instance - */ + * Get the singleton instance of WasabiS3Provider + * @returns {WasabiS3Provider} The singleton instance + */ getProviderInstance(): WasabiS3Provider { if (!this.instance) { this.instance = new WasabiS3Provider(); @@ -133,7 +132,7 @@ export class WasabiS3Provider extends Provider { /** * Set Wasabi details based on the current request's tenantSettings - */ + */ private setWasabiConfiguration() { // Use the default configuration as a starting point this.config = { @@ -156,7 +155,7 @@ export class WasabiS3Provider extends Provider { wasabi_aws_secret_access_key: trimAndGetValue(settings.wasabi_aws_secret_access_key), wasabi_aws_service_url: addHttpsPrefix(trimAndGetValue(settings.wasabi_aws_service_url)), wasabi_aws_default_region: trimAndGetValue(settings.wasabi_aws_default_region), - wasabi_aws_bucket: trimAndGetValue(settings.wasabi_aws_bucket), + wasabi_aws_bucket: trimAndGetValue(settings.wasabi_aws_bucket) }; } } @@ -176,14 +175,23 @@ export class WasabiS3Provider extends Provider { try { const s3Client = this.getWasabiInstance(); - const signedUrl = await getSignedUrl(s3Client, new GetObjectCommand({ - Bucket: this.getWasabiBucket(), - Key: fileURL, - }), { - expiresIn: 3600 - }); - - return signedUrl; + if (s3Client) { + const signedUrl = await getSignedUrl( + s3Client, + new GetObjectCommand({ + Bucket: this.getWasabiBucket(), + Key: fileURL + }), + { + expiresIn: 3600 + } + ); + + return signedUrl; + } else { + console.error('Error while retrieving signed URL: s3Client is null'); + return null; + } } catch (error) { console.error('Error while retrieving signed URL:', error); return null; @@ -212,7 +220,7 @@ export class WasabiS3Provider extends Provider { return multerS3({ s3: this.getWasabiInstance(), bucket: (_req, _file, callback) => { - callback(null, this.getWasabiBucket()) + callback(null, this.getWasabiBucket()); }, metadata: function (_req, _file, callback) { callback(null, { fieldName: _file.fieldname }); @@ -228,7 +236,7 @@ export class WasabiS3Provider extends Provider { let fileName: string; if (filename) { - fileName = (typeof filename === 'string') ? filename : filename(file, extension); + fileName = typeof filename === 'string' ? filename : filename(file, extension); } else { fileName = `${prefix}-${moment().unix()}-${parseInt('' + Math.random() * 1000, 10)}.${extension}`; } @@ -251,17 +259,21 @@ export class WasabiS3Provider extends Provider { try { const s3Client = this.getWasabiInstance(); - // Input parameters when using the GetObjectCommand to retrieve an object from Wasabi storage. - const command = new GetObjectCommand({ - Bucket: this.getWasabiBucket(), // The name of the bucket from which to retrieve the object. - Key: key, // The key (path) of the object to retrieve from the bucket. - }); - - /** - * Send a GetObjectCommand to Wasabi to retrieve an object - */ - const data: GetObjectCommandOutput = await s3Client.send(command); - return data.Body; + if (s3Client) { + // Input parameters when using the GetObjectCommand to retrieve an object from Wasabi storage. + const command = new GetObjectCommand({ + Bucket: this.getWasabiBucket(), // The name of the bucket from which to retrieve the object. + Key: key // The key (path) of the object to retrieve from the bucket. + }); + + /** + * Send a GetObjectCommand to Wasabi to retrieve an object + */ + const data: GetObjectCommandOutput = await s3Client.send(command); + return data.Body; + } else { + console.error('Error while retrieving signed URL: s3Client is null'); + } } catch (error) { console.error(`Error while fetching file with key '${key}':`, error); } @@ -277,40 +289,45 @@ export class WasabiS3Provider extends Provider { async putFile(fileContent: string, key: string = ''): Promise { try { const s3Client = this.getWasabiInstance(); - const filename = basename(key); - - // Input parameters for the PutObjectCommand when uploading a file to Wasabi storage. - const putObjectCommand = new PutObjectCommand({ - Bucket: this.getWasabiBucket(), // The name of the bucket to which the file should be uploaded. - Body: fileContent, // The content of the file to be uploaded. - Key: key, // The key (path) under which to store the file in the bucket. - ContentDisposition: `inline; ${filename}`, // Additional headers for the object. - ContentType: 'image' - }); - - /** - * Send a PutObjectCommand to Wasabi to upload the object - */ - await s3Client.send(putObjectCommand); - - // Input parameters for the HeadObjectCommand when retrieving metadata about an object in Wasabi storage. - const headObjectCommand = new HeadObjectCommand({ - Key: key, // The key (path) of the object for which to retrieve metadata. - Bucket: this.getWasabiBucket() // The name of the bucket where the object is stored. - }); - // Send a HeadObjectCommand to Wasabi to retrieve ContentLength property metadata - const { ContentLength } = await s3Client.send(headObjectCommand); - - const file: Partial = { - originalname: filename, // original file name - size: ContentLength, // files in bytes - filename: filename, - path: key, // Full path of the file - key: key // Full path of the file - }; + if (s3Client) { + const filename = basename(key); + + // Input parameters for the PutObjectCommand when uploading a file to Wasabi storage. + const putObjectCommand = new PutObjectCommand({ + Bucket: this.getWasabiBucket(), // The name of the bucket to which the file should be uploaded. + Body: fileContent, // The content of the file to be uploaded. + Key: key, // The key (path) under which to store the file in the bucket. + ContentDisposition: `inline; ${filename}`, // Additional headers for the object. + ContentType: 'image' + }); + + /** + * Send a PutObjectCommand to Wasabi to upload the object + */ + await s3Client.send(putObjectCommand); + + // Input parameters for the HeadObjectCommand when retrieving metadata about an object in Wasabi storage. + const headObjectCommand = new HeadObjectCommand({ + Key: key, // The key (path) of the object for which to retrieve metadata. + Bucket: this.getWasabiBucket() // The name of the bucket where the object is stored. + }); + + // Send a HeadObjectCommand to Wasabi to retrieve ContentLength property metadata + const { ContentLength } = await s3Client.send(headObjectCommand); + + const file: Partial = { + originalname: filename, // original file name + size: ContentLength, // files in bytes + filename: filename, + path: key, // Full path of the file + key: key // Full path of the file + }; - return await this.mapUploadedFile(file) + return await this.mapUploadedFile(file); + } else { + console.error('Error while retrieving signed URL: s3Client is null'); + } } catch (error) { console.log('Error while put file for wasabi provider', error); } @@ -326,53 +343,67 @@ export class WasabiS3Provider extends Provider { try { const s3Client = this.getWasabiInstance(); - // Input parameters when using the DeleteObjectCommand to delete an object from Wasabi storage. - const command = new DeleteObjectCommand({ - Bucket: this.getWasabiBucket(), // The name of the bucket from which to delete the object. - Key: key // The key (path) of the object to delete from the bucket. - }) - - /** - * Send a DeleteObjectCommand to Wasabi to delete an object - */ - const data: DeleteObjectCommandOutput = await s3Client.send(command); - return new Object({ - status: HttpStatus.OK, - message: `file with key: ${key} is successfully deleted`, - data, - }); + if (s3Client) { + // Input parameters when using the DeleteObjectCommand to delete an object from Wasabi storage. + const command = new DeleteObjectCommand({ + Bucket: this.getWasabiBucket(), // The name of the bucket from which to delete the object. + Key: key // The key (path) of the object to delete from the bucket. + }); + + /** + * Send a DeleteObjectCommand to Wasabi to delete an object + */ + const data: DeleteObjectCommandOutput = await s3Client.send(command); + return new Object({ + status: HttpStatus.OK, + message: `file with key: ${key} is successfully deleted`, + data + }); + } else { + console.error('Error while retrieving signed URL: s3Client is null'); + } } catch (error) { console.error(`Error while deleting file with key '${key}':`, error); - throw new HttpException(error, HttpStatus.BAD_REQUEST, { description: `Error while deleting file with key: '${key}'` }); + throw new HttpException(error, HttpStatus.BAD_REQUEST, { + description: `Error while deleting file with key: '${key}'` + }); } } /** - * Get an AWS S3 instance configured with Wasabi details. - * - * @returns An AWS S3 instance or null in case of an error - */ + * Get an AWS S3 instance configured with Wasabi details. + * + * @returns An AWS S3 instance or null in case of an error + */ private getWasabiInstance(): S3Client | null { try { this.setWasabiConfiguration(); - // Create S3 wasabi endpoint - const endpoint = addHttpsPrefix(this.config.wasabi_aws_service_url); - - // Create S3 wasabi region - const region = this.config.wasabi_aws_default_region; // Specify your Wasabi region - - // Create S3 client service object - const s3Client = new S3Client({ - credentials: { - accessKeyId: this.config.wasabi_aws_access_key_id, - secretAccessKey: this.config.wasabi_aws_secret_access_key, - }, - region, // Specify your Wasabi region - endpoint - }); - - return s3Client; + if (this.config.wasabi_aws_service_url) { + // Create S3 wasabi endpoint + const endpoint = addHttpsPrefix(this.config.wasabi_aws_service_url); + + // Create S3 wasabi region + const region = this.config.wasabi_aws_default_region; // Specify your Wasabi region + + // Create S3 client service object + const s3Client = new S3Client({ + credentials: { + accessKeyId: this.config.wasabi_aws_access_key_id, + secretAccessKey: this.config.wasabi_aws_secret_access_key + }, + region, // Specify your Wasabi region + endpoint + }); + + return s3Client; + } else { + console.warn( + `Error while retrieving ${FileStorageProviderEnum.WASABI} instance: this.config.wasabi_aws_service_url is undefined` + ); + + return null; + } } catch (error) { console.error(`Error while retrieving ${FileStorageProviderEnum.WASABI} instance:`, error); return null; @@ -408,7 +439,10 @@ export class WasabiS3Provider extends Provider { * @param serviceUrl - Wasabi service URL * @returns { wasabi_aws_default_region: string, wasabi_aws_service_url: string } */ - private _mapDefaultWasabiServiceUrl(region?: string, serviceUrl?: string): { + private _mapDefaultWasabiServiceUrl( + region?: string, + serviceUrl?: string + ): { wasabi_aws_default_region: string; wasabi_aws_service_url: string; } { From ad9d4f22a8019f801ae7ed7bfe5830e1e9eb9394 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 15 Dec 2023 14:31:17 +0100 Subject: [PATCH 2/4] chore: log request IP address on development envs --- packages/core/src/throttler/throttler-behind-proxy.guard.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/throttler/throttler-behind-proxy.guard.ts b/packages/core/src/throttler/throttler-behind-proxy.guard.ts index fad0d29ab69..fee377b7fb5 100644 --- a/packages/core/src/throttler/throttler-behind-proxy.guard.ts +++ b/packages/core/src/throttler/throttler-behind-proxy.guard.ts @@ -1,10 +1,15 @@ import { ThrottlerGuard } from '@nestjs/throttler'; import { Injectable } from '@nestjs/common'; +import { environment } from '@gauzy/config'; @Injectable() export class ThrottlerBehindProxyGuard extends ThrottlerGuard { protected getTracker(req: Record): string { const tracker = req.ips.length > 0 ? req.ips[0] : req.ip; + if (!environment.production) { + console.log(`Request IP: ${tracker}`); + } + return tracker; } } From 0b34926f1a4bd7cf3fad4fb1a9775a994f2113ce Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 15 Dec 2023 15:25:25 +0100 Subject: [PATCH 3/4] feat: add more DB connection parameters for PostgreSQL --- .env.compose | 4 +++ .env.demo.compose | 4 +++ .env.docker | 4 +++ .env.local | 4 +++ .env.sample | 4 +++ apps/api/src/plugin-config.ts | 22 +++++++++++--- packages/config/src/database.ts | 54 ++++++++++++++++----------------- 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/.env.compose b/.env.compose index cd854fa0e8b..79fa53ac2ff 100644 --- a/.env.compose +++ b/.env.compose @@ -63,6 +63,10 @@ DB_PORT=5432 DB_NAME=gauzy DB_USER=postgres DB_PASS=gauzy_password +DB_LOGGING=all +DB_POOL_SIZE=20 +DB_CONNECTION_TIMEOUT=1000 +DB_SLOW_QUERY_LOGGING_TIMEOUT=3000 EXPRESS_SESSION_SECRET=gauzy diff --git a/.env.demo.compose b/.env.demo.compose index 21b23563694..d88f72b4e39 100644 --- a/.env.demo.compose +++ b/.env.demo.compose @@ -64,6 +64,10 @@ DB_PORT=5432 DB_NAME=gauzy DB_USER=postgres DB_PASS=gauzy_password +DB_LOGGING=all +DB_POOL_SIZE=20 +DB_CONNECTION_TIMEOUT=1000 +DB_SLOW_QUERY_LOGGING_TIMEOUT=3000 EXPRESS_SESSION_SECRET=gauzy diff --git a/.env.docker b/.env.docker index 688d4edfc5f..50b69614ebc 100644 --- a/.env.docker +++ b/.env.docker @@ -62,6 +62,10 @@ DB_PORT=5432 DB_NAME=gauzy DB_USER=postgres DB_PASS=root +DB_LOGGING=all +DB_POOL_SIZE=20 +DB_CONNECTION_TIMEOUT=1000 +DB_SLOW_QUERY_LOGGING_TIMEOUT=3000 EXPRESS_SESSION_SECRET=gauzy diff --git a/.env.local b/.env.local index a0c52489175..8002404db84 100644 --- a/.env.local +++ b/.env.local @@ -62,6 +62,10 @@ DB_PORT=5432 DB_NAME=gauzy DB_USER=postgres DB_PASS=root +DB_LOGGING=all +DB_POOL_SIZE=20 +DB_CONNECTION_TIMEOUT=1000 +DB_SLOW_QUERY_LOGGING_TIMEOUT=3000 EXPRESS_SESSION_SECRET=gauzy diff --git a/.env.sample b/.env.sample index 8bee435be88..ee31a7f2431 100644 --- a/.env.sample +++ b/.env.sample @@ -46,6 +46,10 @@ DB_TYPE=better-sqlite3 # DB_NAME=gauzy # DB_USER=postgres # DB_PASS=root +# DB_LOGGING=all +# DB_POOL_SIZE=20 +# DB_CONNECTION_TIMEOUT=1000 +# DB_SLOW_QUERY_LOGGING_TIMEOUT=3000 EXPRESS_SESSION_SECRET=gauzy diff --git a/apps/api/src/plugin-config.ts b/apps/api/src/plugin-config.ts index cb87dc482cb..4b3ec540c44 100644 --- a/apps/api/src/plugin-config.ts +++ b/apps/api/src/plugin-config.ts @@ -90,16 +90,30 @@ function getDbConfig(): DataSourceOptions { const postgresConnectionOptions: PostgresConnectionOptions = { type: dbType, + ssl: ssl ? sslParams : undefined, host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 5432, database: process.env.DB_NAME || 'postgres', username: process.env.DB_USER || 'postgres', password: process.env.DB_PASS || 'root', - ssl: ssl ? sslParams : undefined, - logging: 'all', - logger: 'file', // Removes console logging, instead logs all queries in a file ormlogs.log + logging: process.env.DB_LOGGING == 'all' ? 'all' : ['query', 'error'], + logger: 'advanced-console', + // log queries that take more than 3 sec as warnings + maxQueryExecutionTime: process.env.DB_SLOW_QUERY_LOGGING_TIMEOUT + ? parseInt(process.env.DB_SLOW_QUERY_LOGGING_TIMEOUT) + : 3000, synchronize: process.env.DB_SYNCHRONIZE === 'true' ? true : false, // We are using migrations, synchronize should be set to false. - uuidExtension: 'pgcrypto' + uuidExtension: 'pgcrypto', + // NOTE: in new TypeORM version this unified too `poolSize` in the root of connections option object. + // See https://typeorm.io/data-source-options#common-data-source-options + extra: { + // based on https://node-postgres.com/api/pool max connection pool size + max: process.env.DB_POOL_SIZE || 20, + // connection timeout + connectionTimeoutMillis: process.env.DB_CONNECTION_TIMEOUT + ? parseInt(process.env.DB_CONNECTION_TIMEOUT) + : 1000 + } }; return postgresConnectionOptions; diff --git a/packages/config/src/database.ts b/packages/config/src/database.ts index 0dfc86267df..2ef9e696b27 100644 --- a/packages/config/src/database.ts +++ b/packages/config/src/database.ts @@ -7,10 +7,8 @@ import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConne let dbType: string; -if (process.env.DB_TYPE) - dbType = process.env.DB_TYPE; -else - dbType = 'sqlite'; +if (process.env.DB_TYPE) dbType = process.env.DB_TYPE; +else dbType = 'sqlite'; console.log(`Selected DB Type (DB_TYPE env var): ${dbType}`); @@ -20,12 +18,10 @@ console.log(chalk.magenta(`Currently running Node.js version %s`), process.versi let connectionConfig: TypeOrmModuleOptions; switch (dbType) { - case 'mongodb': - throw "MongoDB not supported yet"; + throw 'MongoDB not supported yet'; case 'postgres': - const ssl = process.env.DB_SSL_MODE === 'true' ? true : undefined; let sslParams: TlsOptions; @@ -38,25 +34,37 @@ switch (dbType) { sslParams = { rejectUnauthorized: true, ca: sslCert - } + }; } const postgresConnectionOptions: PostgresConnectionOptions = { type: dbType, ssl: ssl ? sslParams : undefined, host: process.env.DB_HOST || 'localhost', + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 5432, + database: process.env.DB_NAME || 'postgres', username: process.env.DB_USER || 'postgres', password: process.env.DB_PASS || 'root', - database: process.env.DB_NAME || 'postgres', - port: process.env.DB_PORT - ? parseInt(process.env.DB_PORT, 10) - : 5432, - logging: 'all', - logger: 'file', // Removes console logging, instead logs all queries in a file ormlogs.log + logging: process.env.DB_LOGGING == 'all' ? 'all' : ['query', 'error'], + logger: 'advanced-console', + // log queries that take more than 3 sec as warnings + maxQueryExecutionTime: process.env.DB_SLOW_QUERY_LOGGING_TIMEOUT + ? parseInt(process.env.DB_SLOW_QUERY_LOGGING_TIMEOUT) + : 3000, synchronize: process.env.DB_SYNCHRONIZE === 'true' ? true : false, // We are using migrations, synchronize should be set to false. uuidExtension: 'pgcrypto', - migrations: ["src/modules/not-exists/*.migration{.ts,.js}"], - entities: ["src/modules/not-exists/*.entity{.ts,.js}"], + migrations: ['src/modules/not-exists/*.migration{.ts,.js}'], + entities: ['src/modules/not-exists/*.entity{.ts,.js}'], + // NOTE: in new TypeORM version this unified too `poolSize` in the root of connections option object. + // See https://typeorm.io/data-source-options#common-data-source-options + extra: { + // based on https://node-postgres.com/api/pool max connection pool size + max: process.env.DB_POOL_SIZE || 20, + // connection timeout + connectionTimeoutMillis: process.env.DB_CONNECTION_TIMEOUT + ? parseInt(process.env.DB_CONNECTION_TIMEOUT) + : 1000 + } }; connectionConfig = postgresConnectionOptions; @@ -64,10 +72,7 @@ switch (dbType) { break; case 'sqlite': - - const dbPath = - process.env.DB_PATH || - path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3'); + const dbPath = process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3'); console.log('Sqlite DB Path: ' + dbPath); @@ -76,7 +81,7 @@ switch (dbType) { database: dbPath, logging: 'all', logger: 'file', //Removes console logging, instead logs all queries in a file ormlogs.log - synchronize: process.env.DB_SYNCHRONIZE === 'true' ? true : false, // We are using migrations, synchronize should be set to false. + synchronize: process.env.DB_SYNCHRONIZE === 'true' ? true : false // We are using migrations, synchronize should be set to false. }; connectionConfig = sqliteConfig; @@ -85,12 +90,7 @@ switch (dbType) { case 'better-sqlite3': const betterSqlitePath = - process.env.DB_PATH || - path.join( - process.cwd(), - ...['apps', 'api', 'data'], - 'gauzy.sqlite3' - ); + process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3'); console.log('Better Sqlite DB Path: ' + betterSqlitePath); From 24434c4d8b55cac2f605220b77763b522ae3dfbf Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 15 Dec 2023 15:59:42 +0100 Subject: [PATCH 4/4] feat: improvements to Cloudflare Proxy handling of IPs in Throttler --- packages/core/src/bootstrap/index.ts | 2 +- .../throttler/throttler-behind-proxy.guard.ts | 28 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/core/src/bootstrap/index.ts b/packages/core/src/bootstrap/index.ts index c1e07a5db8f..72e2703d62a 100644 --- a/packages/core/src/bootstrap/index.ts +++ b/packages/core/src/bootstrap/index.ts @@ -40,7 +40,7 @@ export async function bootstrap(pluginConfig?: Partial): Promise< }); // Enable Express behind proxies (https://expressjs.com/en/guide/behind-proxies.html) - app.set('trust proxy', 1); + app.set('trust proxy', true); // Starts listening for shutdown hooks app.enableShutdownHooks(); diff --git a/packages/core/src/throttler/throttler-behind-proxy.guard.ts b/packages/core/src/throttler/throttler-behind-proxy.guard.ts index fee377b7fb5..09505b91c75 100644 --- a/packages/core/src/throttler/throttler-behind-proxy.guard.ts +++ b/packages/core/src/throttler/throttler-behind-proxy.guard.ts @@ -5,9 +5,31 @@ import { environment } from '@gauzy/config'; @Injectable() export class ThrottlerBehindProxyGuard extends ThrottlerGuard { protected getTracker(req: Record): string { - const tracker = req.ips.length > 0 ? req.ips[0] : req.ip; - if (!environment.production) { - console.log(`Request IP: ${tracker}`); + let tracker: string; + + // Handle Cloudflare proxy + if (req.headers && req.headers['cf-connecting-ip']) { + if (!environment.production) { + console.log(`Cloudflare cf-connecting-ip: ${req.headers['cf-connecting-ip']}`); + } + + if (req.headers['cf-connecting-ip'].split(', ').length) { + var first = req.headers['cf-connecting-ip'].split(', '); + tracker = first[0]; + if (!environment.production) { + console.log(`Cloudflare Request IP: ${tracker}`); + } + } else { + tracker = req.ips.length > 0 ? req.ips[0] : req.ip; + if (!environment.production) { + console.log(`Request IP: ${tracker}`); + } + } + } else { + tracker = req.ips.length > 0 ? req.ips[0] : req.ip; + if (!environment.production) { + console.log(`Request IP: ${tracker}`); + } } return tracker;