From 37e9207b4364bf7414e487855f5bf3fc352f2211 Mon Sep 17 00:00:00 2001 From: Mir Arif Hasan Date: Wed, 4 Feb 2026 17:55:00 +0600 Subject: [PATCH] fix(backend): resolve database connection leak in infra-config operations (#5825) --- .../src/infra-config/helper.ts | 35 +++++++++++++++---- .../src/infra-config/infra-config.service.ts | 8 +++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/hoppscotch-backend/src/infra-config/helper.ts b/packages/hoppscotch-backend/src/infra-config/helper.ts index 76b2b1da..084a78fd 100644 --- a/packages/hoppscotch-backend/src/infra-config/helper.ts +++ b/packages/hoppscotch-backend/src/infra-config/helper.ts @@ -16,6 +16,29 @@ type DefaultInfraConfig = { isEncrypted: boolean; }; +// Singleton PrismaService instance for infra config operations +let sharedPrismaInstance: PrismaService | null = null; + +/** + * Get or create a shared PrismaService instance for infra config operations + */ +function getSharedPrismaInstance(): PrismaService { + if (!sharedPrismaInstance) { + sharedPrismaInstance = new PrismaService(); + } + return sharedPrismaInstance; +} + +/** + * Disconnect the shared Prisma instance during application shutdown + */ +export async function disconnectSharedPrismaInstance(): Promise { + if (sharedPrismaInstance) { + await sharedPrismaInstance.onModuleDestroy(); + sharedPrismaInstance = null; + } +} + /** * Returns a mapping of authentication providers to their required configuration keys based on the current environment configuration. */ @@ -67,8 +90,8 @@ export function getAuthProviderRequiredKeys( * (ConfigModule will set the environment variables in the process) */ export async function loadInfraConfiguration() { + const prisma = getSharedPrismaInstance(); try { - const prisma = new PrismaService(); const infraConfigs = await prisma.infraConfig.findMany(); const environmentObject: Record = {}; @@ -97,7 +120,7 @@ export async function loadInfraConfiguration() { * @returns Array of default infra configs */ export async function getDefaultInfraConfigs(): Promise { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); // Prepare rows for 'infra_config' table with default values (from .env) for each 'name' const onboardingCompleteStatus = await isOnboardingCompleted(); @@ -324,7 +347,7 @@ export async function getDefaultInfraConfigs(): Promise { export async function getMissingInfraConfigEntries( infraConfigDefaultObjs: DefaultInfraConfig[], ) { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const dbInfraConfigs = await prisma.infraConfig.findMany(); const missingEntries = infraConfigDefaultObjs.filter( @@ -342,7 +365,7 @@ export async function getMissingInfraConfigEntries( export async function getEncryptionRequiredInfraConfigEntries( infraConfigDefaultObjs: DefaultInfraConfig[], ) { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const dbInfraConfigs = await prisma.infraConfig.findMany(); const requiredEncryption = dbInfraConfigs.filter((dbConfig) => { @@ -400,7 +423,7 @@ export function stopApp() { * @returns Array of configured SSO providers */ export async function getConfiguredSSOProvidersFromInfraConfig() { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const env = await loadInfraConfiguration(); const providerConfigKeys = getAuthProviderRequiredKeys(env); @@ -437,7 +460,7 @@ export async function getConfiguredSSOProvidersFromInfraConfig() { * @returns boolean */ export async function isOnboardingCompleted(): Promise { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const allowedProviders = await prisma.infraConfig.findUnique({ where: { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS }, select: { value: true }, 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 fa763d77..e0042682 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts @@ -1,4 +1,4 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { InfraConfig } from './infra-config.model'; import { PrismaService } from 'src/prisma/prisma.service'; import { InfraConfig as DBInfraConfig } from 'src/generated/prisma/client'; @@ -26,6 +26,7 @@ import { ConfigService } from '@nestjs/config'; import { ServiceStatus, buildDerivedEnv, + disconnectSharedPrismaInstance, getDefaultInfraConfigs, getEncryptionRequiredInfraConfigEntries, getMissingInfraConfigEntries, @@ -45,7 +46,7 @@ import * as crypto from 'crypto'; import { PrismaError } from 'src/prisma/prisma-error-codes'; @Injectable() -export class InfraConfigService implements OnModuleInit { +export class InfraConfigService implements OnModuleInit, OnModuleDestroy { constructor( private readonly prisma: PrismaService, private readonly configService: ConfigService, @@ -72,6 +73,9 @@ export class InfraConfigService implements OnModuleInit { async onModuleInit() { await this.initializeInfraConfigTable(); } + async onModuleDestroy() { + await disconnectSharedPrismaInstance(); + } /** * Initialize the 'infra_config' table with values from .env