From 8c3b36f15c00921a5823be75b79dd6048effc2f4 Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Fri, 5 Dec 2025 19:35:23 -0300 Subject: [PATCH] refactor(mongodb-credentials): replace SSL terminology with TLS and enhance MongoDB client creation logic --- .../mongodb-credentials-rotation-fns.ts | 70 ++----------- .../mongodb/mongodb-connection-fns.ts | 98 +++++++++++++------ .../mongodb/mongodb-connection-schemas.ts | 12 +-- .../types/mongodb-connection.ts | 6 +- .../MongoDBConnectionForm.tsx | 42 ++++---- 5 files changed, 105 insertions(+), 123 deletions(-) diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts index 479bd3108d..aad286ecc5 100644 --- a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts @@ -1,8 +1,6 @@ /* eslint-disable no-await-in-loop */ import { MongoClient } from "mongodb"; -import RE2 from "re2"; -import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; import { TRotationFactory, TRotationFactoryGetSecretsPayload, @@ -10,6 +8,7 @@ import { TRotationFactoryRevokeCredentials, TRotationFactoryRotateCredentials } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; +import { createMongoClient } from "@app/services/app-connection/mongodb/mongodb-connection-fns"; import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../shared/utils"; import { @@ -44,67 +43,10 @@ export const mongodbCredentialsRotationFactory: TRotationFactory< const passwordRequirement = DEFAULT_PASSWORD_REQUIREMENTS; - // Helper function to create MongoDB client with given credentials - const $createMongoClient = async ( - authCredentials: { username: string; password: string }, - options?: { validateConnection?: boolean } - ): Promise => { - let normalizedHost = connection.credentials.host.trim(); - const srvRegex = new RE2("^mongodb\\+srv:\\/\\/"); - const protocolRegex = new RE2("^mongodb:\\/\\/"); - - const isSrvFromHost = srvRegex.test(normalizedHost); - if (isSrvFromHost) { - normalizedHost = srvRegex.replace(normalizedHost, ""); - } else if (protocolRegex.test(normalizedHost)) { - normalizedHost = protocolRegex.replace(normalizedHost, ""); - } - - const [hostIp] = await verifyHostInputValidity(normalizedHost); - - const isSrv = !connection.credentials.port || isSrvFromHost; - const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${connection.credentials.port}`; - - const clientOptions: { - auth?: { username: string; password?: string }; - authSource?: string; - tls?: boolean; - tlsInsecure?: boolean; - ca?: string; - directConnection?: boolean; - } = { - auth: { - username: authCredentials.username, - password: authCredentials.password - }, - authSource: isSrv ? undefined : connection.credentials.database, - directConnection: !isSrv - }; - - if (connection.credentials.sslCertificate) { - clientOptions.tls = true; - clientOptions.ca = connection.credentials.sslCertificate; - } - - const client = new MongoClient(uri, clientOptions); - - if (options?.validateConnection) { - await client.db(connection.credentials.database).command({ ping: 1 }); - } - - return client; - }; - const $getClient = async () => { let client: MongoClient | null = null; try { - client = await $createMongoClient( - { - username: connection.credentials.username, - password: connection.credentials.password - }, - { validateConnection: true } - ); + client = await createMongoClient(connection.credentials, { validateConnection: true }); return client; } catch (err) { if (client) await client.close(); @@ -115,13 +57,13 @@ export const mongodbCredentialsRotationFactory: TRotationFactory< const $validateCredentials = async (credentials: TMongoDBCredentialsRotationGeneratedCredentials[number]) => { let client: MongoClient | null = null; try { - client = await $createMongoClient( - { + client = await createMongoClient(connection.credentials, { + authCredentials: { username: credentials.username, password: credentials.password }, - { validateConnection: true } - ); + validateConnection: true + }); } catch (error) { throw new Error(redactPasswords(error, [credentials])); } finally { diff --git a/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts b/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts index 9cad40175d..69588a2406 100644 --- a/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts +++ b/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts @@ -17,11 +17,32 @@ export const getMongoDBConnectionListItem = () => { }; }; -export const validateMongoDBConnectionCredentials = async (config: TMongoDBConnectionConfig) => { +export type TMongoDBConnectionCredentials = { + host: string; + port?: number; + database: string; + username: string; + password: string; + tlsEnabled?: boolean; + tlsRejectUnauthorized?: boolean; + tlsCertificate?: string; +}; + +export type TCreateMongoClientOptions = { + authCredentials?: { username: string; password: string }; + validateConnection?: boolean; +}; + +const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000; + +export const createMongoClient = async ( + credentials: TMongoDBConnectionCredentials, + options?: TCreateMongoClientOptions +): Promise => { const srvRegex = new RE2("^mongodb\\+srv:\\/\\/"); const protocolRegex = new RE2("^mongodb:\\/\\/"); - let normalizedHost = config.credentials.host.trim(); + let normalizedHost = credentials.host.trim(); const isSrvFromHost = srvRegex.test(normalizedHost); if (isSrvFromHost) { normalizedHost = srvRegex.replace(normalizedHost, ""); @@ -31,41 +52,60 @@ export const validateMongoDBConnectionCredentials = async (config: TMongoDBConne const [hostIp] = await verifyHostInputValidity(normalizedHost); - let client: MongoClient | null = null; - try { - const isSrv = !config.credentials.port || isSrvFromHost; - const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${config.credentials.port}`; + const isSrv = !credentials.port || isSrvFromHost; + const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${credentials.port}`; - const clientOptions: { - auth?: { username: string; password?: string }; - authSource?: string; - tls?: boolean; - tlsInsecure?: boolean; - ca?: string; - directConnection?: boolean; - } = { - auth: { - username: config.credentials.username, - password: config.credentials.password - }, - authSource: isSrv ? undefined : config.credentials.database, - directConnection: !isSrv - }; + const authCredentials = options?.authCredentials ?? { + username: credentials.username, + password: credentials.password + }; - if (config.credentials.sslEnabled) { - clientOptions.tls = true; - clientOptions.tlsInsecure = !config.credentials.sslRejectUnauthorized; - if (config.credentials.sslCertificate) { - clientOptions.ca = config.credentials.sslCertificate; - } + const clientOptions: { + auth?: { username: string; password?: string }; + authSource?: string; + tls?: boolean; + tlsInsecure?: boolean; + ca?: string; + directConnection?: boolean; + connectTimeoutMS?: number; + serverSelectionTimeoutMS?: number; + socketTimeoutMS?: number; + } = { + auth: { + username: authCredentials.username, + password: authCredentials.password + }, + authSource: isSrv ? undefined : credentials.database, + directConnection: !isSrv, + connectTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS, + serverSelectionTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS, + socketTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS + }; + + if (credentials.tlsEnabled) { + clientOptions.tls = true; + clientOptions.tlsInsecure = !credentials.tlsRejectUnauthorized; + if (credentials.tlsCertificate) { + clientOptions.ca = credentials.tlsCertificate; } + } - client = new MongoClient(uri, clientOptions); + const client = new MongoClient(uri, clientOptions); + if (options?.validateConnection) { await client - .db(config.credentials.database) + .db(credentials.database) .command({ ping: 1 }) .then(() => true); + } + + return client; +}; + +export const validateMongoDBConnectionCredentials = async (config: TMongoDBConnectionConfig) => { + let client: MongoClient | null = null; + try { + client = await createMongoClient(config.credentials, { validateConnection: true }); if (client) await client.close(); diff --git a/backend/src/services/app-connection/mongodb/mongodb-connection-schemas.ts b/backend/src/services/app-connection/mongodb/mongodb-connection-schemas.ts index 1caba50715..c934a07414 100644 --- a/backend/src/services/app-connection/mongodb/mongodb-connection-schemas.ts +++ b/backend/src/services/app-connection/mongodb/mongodb-connection-schemas.ts @@ -17,9 +17,9 @@ export const BaseMongoDBUsernameAndPasswordConnectionSchema = z.object({ password: z.string().min(1), database: z.string().min(1).trim(), - sslRejectUnauthorized: z.boolean(), - sslEnabled: z.boolean(), - sslCertificate: z + tlsRejectUnauthorized: z.boolean(), + tlsEnabled: z.boolean(), + tlsCertificate: z .string() .trim() .transform((value) => value || undefined) @@ -43,9 +43,9 @@ export const SanitizedMongoDBConnectionSchema = z.discriminatedUnion("method", [ port: true, username: true, database: true, - sslEnabled: true, - sslRejectUnauthorized: true, - sslCertificate: true + tlsEnabled: true, + tlsRejectUnauthorized: true, + tlsCertificate: true }) }) ]); diff --git a/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts b/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts index 151c676a54..5c8e72cb52 100644 --- a/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts @@ -11,9 +11,9 @@ export type TMongoDBConnectionCredentials = { username: string; password: string; database: string; - sslEnabled: boolean; - sslRejectUnauthorized: boolean; - sslCertificate?: string; + tlsEnabled: boolean; + tlsRejectUnauthorized: boolean; + tlsCertificate?: string; }; export type TMongoDBConnection = TRootAppConnection & { app: AppConnection.MongoDB } & { diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx index 32b38fea02..72e359e0a5 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx @@ -45,9 +45,9 @@ const formSchema = z.discriminatedUnion("method", [ username: z.string().trim().min(1, "Username required"), password: z.string().trim().min(1, "Password required"), database: z.string().trim().min(1, "Database required"), - sslEnabled: z.boolean().default(false), - sslRejectUnauthorized: z.boolean().default(true), - sslCertificate: z + tlsEnabled: z.boolean().default(false), + tlsRejectUnauthorized: z.boolean().default(true), + tlsCertificate: z .string() .trim() .transform((value) => value || undefined) @@ -73,9 +73,9 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => { username: "", password: "", database: "", - sslEnabled: false, - sslRejectUnauthorized: true, - sslCertificate: undefined + tlsEnabled: false, + tlsRejectUnauthorized: true, + tlsCertificate: undefined } } }); @@ -87,7 +87,7 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => { formState: { isSubmitting, isDirty } } = form; - const sslEnabled = watch("credentials.sslEnabled"); + const tlsEnabled = watch("credentials.tlsEnabled"); return ( @@ -147,7 +147,7 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => { }` } > - SSL ({sslEnabled ? "Enabled" : "Disabled"}) + TLS ({tlsEnabled ? "Enabled" : "Disabled"}) @@ -233,53 +233,53 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => { ( - Enable SSL + Enable TLS )} /> ( -