Add MongoDB connection support

- Introduced MongoDB connection router and schemas.
- Updated app connection enums and maps to include MongoDB.
- Implemented validation and connection functions for MongoDB.
- Enhanced app connection service to handle MongoDB credentials.
This commit is contained in:
Victor Santos
2025-11-14 20:26:21 -03:00
parent c563787c83
commit 8121db69f2
13 changed files with 231 additions and 1 deletions

View File

@@ -27,6 +27,7 @@ import { registerHerokuConnectionRouter } from "./heroku-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLaravelForgeConnectionRouter } from "./laravel-forge-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMongoDBConnectionRouter } from "./mongodb-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
import { registerNetlifyConnectionRouter } from "./netlify-connection-router";
@@ -88,5 +89,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Northflank]: registerNorthflankConnectionRouter,
[AppConnection.Okta]: registerOktaConnectionRouter,
[AppConnection.Redis]: registerRedisConnectionRouter,
[AppConnection.MongoDB]: registerMongoDBConnectionRouter,
[AppConnection.Chef]: registerChefConnectionRouter
};

View File

@@ -0,0 +1,18 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateMongoDBConnectionSchema,
SanitizedMongoDBConnectionSchema,
UpdateMongoDBConnectionSchema
} from "@app/services/app-connection/mongodb";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerMongoDBConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.MongoDB,
server,
sanitizedResponseSchema: SanitizedMongoDBConnectionSchema,
createSchema: CreateMongoDBConnectionSchema,
updateSchema: UpdateMongoDBConnectionSchema
});
};

View File

@@ -38,6 +38,7 @@ export enum AppConnection {
Netlify = "netlify",
Okta = "okta",
Redis = "redis",
MongoDB = "mongodb",
LaravelForge = "laravel-forge",
Chef = "chef",
Northflank = "northflank"

View File

@@ -114,6 +114,7 @@ import {
validateLaravelForgeConnectionCredentials
} from "./laravel-forge";
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
import { getMongoDBConnectionListItem, MongoDBConnectionMethod, validateMongoDBConnectionCredentials } from "./mongodb";
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
@@ -216,6 +217,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => {
getNorthflankConnectionListItem(),
getOktaConnectionListItem(),
getRedisConnectionListItem(),
getMongoDBConnectionListItem(),
getChefConnectionListItem()
]
.filter((option) => {
@@ -348,7 +350,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Northflank]: validateNorthflankConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Chef]: validateChefConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Redis]: validateRedisConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Redis]: validateRedisConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.MongoDB]: validateMongoDBConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService, gatewayV2Service);
@@ -400,6 +403,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case OracleDBConnectionMethod.UsernameAndPassword:
case AzureADCSConnectionMethod.UsernamePassword:
case RedisConnectionMethod.UsernameAndPassword:
case MongoDBConnectionMethod.UsernameAndPassword:
return "Username & Password";
case WindmillConnectionMethod.AccessToken:
case HCVaultConnectionMethod.AccessToken:
@@ -492,6 +496,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Northflank]: platformManagedCredentialsNotSupported,
[AppConnection.Okta]: platformManagedCredentialsNotSupported,
[AppConnection.Redis]: platformManagedCredentialsNotSupported,
[AppConnection.MongoDB]: platformManagedCredentialsNotSupported,
[AppConnection.LaravelForge]: platformManagedCredentialsNotSupported,
[AppConnection.Chef]: platformManagedCredentialsNotSupported
};

View File

@@ -41,6 +41,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Netlify]: "Netlify",
[AppConnection.Okta]: "Okta",
[AppConnection.Redis]: "Redis",
[AppConnection.MongoDB]: "MongoDB",
[AppConnection.Chef]: "Chef",
[AppConnection.Northflank]: "Northflank"
};
@@ -86,6 +87,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Netlify]: AppConnectionPlanType.Regular,
[AppConnection.Okta]: AppConnectionPlanType.Regular,
[AppConnection.Redis]: AppConnectionPlanType.Regular,
[AppConnection.MongoDB]: AppConnectionPlanType.Regular,
[AppConnection.Chef]: AppConnectionPlanType.Enterprise,
[AppConnection.Northflank]: AppConnectionPlanType.Regular
};

View File

@@ -106,6 +106,7 @@ import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateRailwayConnectionCredentialsSchema } from "./railway";
import { railwayConnectionService } from "./railway/railway-connection-service";
import { ValidateRedisConnectionCredentialsSchema } from "./redis";
import { ValidateMongoDBConnectionCredentialsSchema } from "./mongodb";
import { ValidateRenderConnectionCredentialsSchema } from "./render/render-connection-schema";
import { renderConnectionService } from "./render/render-connection-service";
import { ValidateSupabaseConnectionCredentialsSchema } from "./supabase";
@@ -177,6 +178,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Northflank]: ValidateNorthflankConnectionCredentialsSchema,
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema,
[AppConnection.Redis]: ValidateRedisConnectionCredentialsSchema,
[AppConnection.MongoDB]: ValidateMongoDBConnectionCredentialsSchema,
[AppConnection.Chef]: ValidateChefConnectionCredentialsSchema
};

View File

@@ -203,6 +203,12 @@ import {
TRedisConnectionInput,
TValidateRedisConnectionCredentialsSchema
} from "./redis";
import {
TMongoDBConnection,
TMongoDBConnectionConfig,
TMongoDBConnectionInput,
TValidateMongoDBConnectionCredentialsSchema
} from "./mongodb";
import {
TRenderConnection,
TRenderConnectionConfig,
@@ -288,6 +294,7 @@ export type TAppConnection = { id: string } & (
| TNorthflankConnection
| TOktaConnection
| TRedisConnection
| TMongoDBConnection
| TChefConnection
);
@@ -337,6 +344,7 @@ export type TAppConnectionInput = { id: string } & (
| TNorthflankConnectionInput
| TOktaConnectionInput
| TRedisConnectionInput
| TMongoDBConnectionInput
| TChefConnectionInput
);
@@ -404,6 +412,7 @@ export type TAppConnectionConfig =
| TNorthflankConnectionConfig
| TOktaConnectionConfig
| TRedisConnectionConfig
| TMongoDBConnectionConfig
| TChefConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
@@ -448,6 +457,7 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateNorthflankConnectionCredentialsSchema
| TValidateOktaConnectionCredentialsSchema
| TValidateRedisConnectionCredentialsSchema
| TValidateMongoDBConnectionCredentialsSchema
| TValidateChefConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {

View File

@@ -0,0 +1,4 @@
export * from "./mongodb-connection-enums";
export * from "./mongodb-connection-fns";
export * from "./mongodb-connection-schemas";
export * from "./mongodb-connection-types";

View File

@@ -0,0 +1,4 @@
export enum MongoDBConnectionMethod {
UsernameAndPassword = "username-and-password"
}

View File

@@ -0,0 +1,70 @@
import { MongoClient } from "mongodb";
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { MongoDBConnectionMethod } from "./mongodb-connection-enums";
import { TMongoDBConnectionConfig } from "./mongodb-connection-types";
export const getMongoDBConnectionListItem = () => {
return {
name: "MongoDB" as const,
app: AppConnection.MongoDB as const,
methods: Object.values(MongoDBConnectionMethod) as [MongoDBConnectionMethod.UsernameAndPassword],
supportsPlatformManagement: false as const
};
};
export const validateMongoDBConnectionCredentials = async (config: TMongoDBConnectionConfig) => {
const [hostIp] = await verifyHostInputValidity(config.credentials.host);
let client: MongoClient | null = null;
try {
const isSrv = !config.credentials.port;
const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${config.credentials.port}`;
const clientOptions: {
auth?: { username: string; password?: string };
tls?: boolean;
tlsInsecure?: boolean;
ca?: string;
directConnection?: boolean;
} = {
auth: {
username: config.credentials.username,
password: config.credentials.password
},
directConnection: !isSrv
};
if (config.credentials.sslEnabled || isSrv) {
clientOptions.tls = true;
clientOptions.tlsInsecure = !config.credentials.sslRejectUnauthorized;
if (config.credentials.sslCertificate) {
clientOptions.ca = config.credentials.sslCertificate;
}
}
client = new MongoClient(uri, clientOptions);
// Validate connection by running ping command
await client
.db(config.credentials.database)
.command({ ping: 1 })
.then(() => true);
if (client) await client.close();
return config.credentials;
} catch (err) {
if (err instanceof BadRequestError) {
throw err;
}
throw new BadRequestError({
message: `Unable to validate connection: ${(err as Error)?.message || "verify credentials"}`
});
} finally {
if (client) await client.close();
}
};

View File

@@ -0,0 +1,89 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { AppConnection } from "../app-connection-enums";
import { MongoDBConnectionMethod } from "./mongodb-connection-enums";
export const BaseMongoDBUsernameAndPasswordConnectionSchema = z.object({
host: z.string().toLowerCase().min(1),
port: z.coerce.number(),
username: z.string().min(1),
password: z.string().min(1),
database: z.string().min(1).trim(),
sslRejectUnauthorized: z.boolean(),
sslEnabled: z.boolean(),
sslCertificate: z
.string()
.trim()
.transform((value) => value || undefined)
.optional()
});
export const MongoDBConnectionAccessTokenCredentialsSchema = BaseMongoDBUsernameAndPasswordConnectionSchema;
const BaseMongoDBConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.MongoDB) });
export const MongoDBConnectionSchema = BaseMongoDBConnectionSchema.extend({
method: z.literal(MongoDBConnectionMethod.UsernameAndPassword),
credentials: MongoDBConnectionAccessTokenCredentialsSchema
});
export const SanitizedMongoDBConnectionSchema = z.discriminatedUnion("method", [
BaseMongoDBConnectionSchema.extend({
method: z.literal(MongoDBConnectionMethod.UsernameAndPassword),
credentials: MongoDBConnectionAccessTokenCredentialsSchema.pick({
host: true,
port: true,
username: true,
database: true,
sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: true
})
})
]);
export const ValidateMongoDBConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(MongoDBConnectionMethod.UsernameAndPassword)
.describe(AppConnections.CREATE(AppConnection.MongoDB).method),
credentials: MongoDBConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.MongoDB).credentials
)
})
]);
export const CreateMongoDBConnectionSchema = ValidateMongoDBConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.MongoDB, {
supportsPlatformManagedCredentials: false,
supportsGateways: false
})
);
export const UpdateMongoDBConnectionSchema = z
.object({
credentials: MongoDBConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.MongoDB).credentials
)
})
.and(
GenericUpdateAppConnectionFieldsSchema(AppConnection.MongoDB, {
supportsPlatformManagedCredentials: false,
supportsGateways: false
})
);
export const MongoDBConnectionListItemSchema = z.object({
name: z.literal("MongoDB"),
app: z.literal(AppConnection.MongoDB),
methods: z.nativeEnum(MongoDBConnectionMethod).array(),
supportsPlatformManagement: z.literal(false)
});

View File

@@ -0,0 +1,22 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateMongoDBConnectionSchema,
MongoDBConnectionSchema,
ValidateMongoDBConnectionCredentialsSchema
} from "./mongodb-connection-schemas";
export type TMongoDBConnection = z.infer<typeof MongoDBConnectionSchema>;
export type TMongoDBConnectionInput = z.infer<typeof CreateMongoDBConnectionSchema> & {
app: AppConnection.MongoDB;
};
export type TValidateMongoDBConnectionCredentialsSchema = typeof ValidateMongoDBConnectionCredentialsSchema;
export type TMongoDBConnectionConfig = DiscriminativePick<TMongoDBConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};

View File

@@ -39,6 +39,7 @@ export enum AppConnection {
Northflank = "northflank",
Okta = "okta",
Redis = "redis",
MongoDB = "mongodb",
LaravelForge = "laravel-forge",
Chef = "chef"
}