From 97d71e50320415275ffa3c93660e694a7ac94105 Mon Sep 17 00:00:00 2001 From: Mir Arif Hasan Date: Tue, 28 Nov 2023 18:50:39 +0600 Subject: [PATCH] chore: smtp url validation added --- packages/hoppscotch-backend/src/errors.ts | 6 ++++ .../src/infra-config/infra-config.service.ts | 34 +++++++++++++++--- packages/hoppscotch-backend/src/utils.ts | 36 ++++++++++++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index f59545b31..90534f629 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -670,6 +670,12 @@ export const INFRA_CONFIG_NOT_LISTED = */ export const INFRA_CONFIG_RESET_FAILED = 'infra_config/reset_failed' as const; +/** + * Infra Config reset failed + * (InfraConfigService) + */ +export const INFRA_CONFIG_INVALID_INPUT = 'infra_config/invalid_input' as const; + /** * Error message for when the database table does not exist * (InfraConfigService) diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts index 737f00c71..d9de6b158 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts @@ -9,12 +9,13 @@ import { } from 'src/types/InfraConfig'; import { DATABASE_TABLE_NOT_EXIST, + INFRA_CONFIG_INVALID_INPUT, INFRA_CONFIG_NOT_FOUND, INFRA_CONFIG_NOT_LISTED, INFRA_CONFIG_RESET_FAILED, INFRA_CONFIG_UPDATE_FAILED, } from 'src/errors'; -import { throwErr } from 'src/utils'; +import { throwErr, validateUrl } from 'src/utils'; import { ConfigService } from '@nestjs/config'; import { AuthProviderStatus, stopApp } from './helper'; import { InfraConfigArgs } from './input-args'; @@ -130,7 +131,13 @@ export class InfraConfigService implements OnModuleInit { * @param value Value of the InfraConfig * @returns InfraConfig model */ - async update(name: InfraConfigEnum, value: string) { + async update( + name: InfraConfigEnumForClient | InfraConfigEnum, + value: string, + ) { + const isValidate = this.validateEnvValues([{ name, value }]); + if (E.isLeft(isValidate)) return E.left(isValidate.left); + try { const infraConfig = await this.prisma.infraConfig.update({ where: { name }, @@ -151,6 +158,9 @@ export class InfraConfigService implements OnModuleInit { * @returns InfraConfig model */ async updateMany(infraConfigs: InfraConfigArgs[]) { + const isValidate = this.validateEnvValues(infraConfigs); + if (E.isLeft(isValidate)) return E.left(isValidate.left); + try { await this.prisma.$transaction(async (tx) => { const deleteCount = await tx.infraConfig.deleteMany({ @@ -165,7 +175,7 @@ export class InfraConfigService implements OnModuleInit { }); if (deleteCount.count !== createCount.count) { - throwErr(INFRA_CONFIG_UPDATE_FAILED); + return E.left(INFRA_CONFIG_UPDATE_FAILED); } }); @@ -205,7 +215,7 @@ export class InfraConfigService implements OnModuleInit { InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, newEnabledAuthProviders.join(','), ); - if (E.isLeft(isUpdated)) throwErr(isUpdated.left); + if (E.isLeft(isUpdated)) return E.left(isUpdated.left); return E.right(true); } @@ -275,4 +285,20 @@ export class InfraConfigService implements OnModuleInit { return E.left(INFRA_CONFIG_RESET_FAILED); } } + + validateEnvValues( + infraConfigs: { + name: InfraConfigEnumForClient | InfraConfigEnum; + value: string; + }[], + ) { + for (let i = 0; i < infraConfigs.length; i++) { + if (infraConfigs[i].name === InfraConfigEnumForClient.MAILER_SMTP_URL) { + const isValidUrl = validateUrl(infraConfigs[i].value); + if (!isValidUrl) return E.left(INFRA_CONFIG_INVALID_INPUT); + } + } + + return E.right(true); + } } diff --git a/packages/hoppscotch-backend/src/utils.ts b/packages/hoppscotch-backend/src/utils.ts index ef12f3eb1..346e4b214 100644 --- a/packages/hoppscotch-backend/src/utils.ts +++ b/packages/hoppscotch-backend/src/utils.ts @@ -16,7 +16,6 @@ import { JSON_INVALID, } from './errors'; import { AuthProvider } from './auth/helper'; -import { ConfigService } from '@nestjs/config'; /** * A workaround to throw an exception in an expression. @@ -132,6 +131,41 @@ export const validateEmail = (email: string) => { ).test(email); }; +/** + * Checks to see if the URL is valid or not + * @param url The URL to validate + * @returns boolean + */ +export const validateUrl = (url: string) => { + /** + * RegExps. + * A URL must match #1 and then at least one of #2/#3. + * Use two levels of REs to avoid REDOS. + */ + const protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/; + const localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/; + const nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/; + + const match = url.match(protocolAndDomainRE); + if (!match) { + return false; + } + + const everythingAfterProtocol = match[1]; + if (!everythingAfterProtocol) { + return false; + } + + if ( + localhostDomainRE.test(everythingAfterProtocol) || + nonLocalhostDomainRE.test(everythingAfterProtocol) + ) { + return true; + } + + return false; +}; + /** * String to JSON parser * @param {str} str The string to parse