chore: add sslmode support to PrismaService database URL parser (#5671)

This commit is contained in:
Mir Arif Hasan 2025-12-10 12:19:17 +06:00 committed by GitHub
parent d67a85b128
commit 92e3f52b47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 88 additions and 28 deletions

View file

@ -507,13 +507,9 @@ describe('MockServerService', () => {
);
expect(E.isRight(result)).toBe(true);
expect(mockUserCollectionService.createUserCollection).toHaveBeenCalledWith(
user,
autoCreateInput.name,
null,
null,
'REST',
);
expect(
mockUserCollectionService.createUserCollection,
).toHaveBeenCalledWith(user, autoCreateInput.name, null, null, 'REST');
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
@ -549,7 +545,9 @@ describe('MockServerService', () => {
);
expect(E.isRight(result)).toBe(true);
expect(mockUserCollectionService.importCollectionsFromJSON).toHaveBeenCalled();
expect(
mockUserCollectionService.importCollectionsFromJSON,
).toHaveBeenCalled();
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
@ -629,7 +627,9 @@ describe('MockServerService', () => {
);
expect(E.isRight(result)).toBe(true);
expect(mockTeamCollectionService.importCollectionsFromJSON).toHaveBeenCalled();
expect(
mockTeamCollectionService.importCollectionsFromJSON,
).toHaveBeenCalled();
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
@ -700,7 +700,10 @@ describe('MockServerService', () => {
autoCreateRequestExample: false,
};
const createdCollection = { ...userCollection, id: 'rollback-coll-123' };
const createdCollection = {
...userCollection,
id: 'rollback-coll-123',
};
mockUserCollectionService.createUserCollection.mockResolvedValue(
E.right(createdCollection as any),
);
@ -717,10 +720,9 @@ describe('MockServerService', () => {
);
expect(E.isLeft(result)).toBe(true);
expect(mockUserCollectionService.deleteUserCollection).toHaveBeenCalledWith(
'rollback-coll-123',
user.uid,
);
expect(
mockUserCollectionService.deleteUserCollection,
).toHaveBeenCalledWith('rollback-coll-123', user.uid);
});
test('should rollback team collection on mock server creation failure', async () => {
@ -733,7 +735,10 @@ describe('MockServerService', () => {
autoCreateRequestExample: false,
};
const createdTeamColl = { ...teamCollection, id: 'rollback-team-coll-123' };
const createdTeamColl = {
...teamCollection,
id: 'rollback-team-coll-123',
};
mockPrisma.team.findFirst.mockResolvedValue({ id: 'team123' } as any);
mockTeamCollectionService.createCollection.mockResolvedValue(
E.right(createdTeamColl as any),

View file

@ -13,57 +13,112 @@ export class PrismaService
constructor() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL environment variable is not set');
}
const { connectionString, schema, connectionLimit, connectTimeout } =
PrismaService.parseDatabaseUrl(databaseUrl);
const parsed = PrismaService.parseDatabaseUrl(databaseUrl);
// Generic SSL configuration for all database environments
// Supports: AWS Aurora, Docker, local PostgreSQL, managed databases
const sslConfig = PrismaService.getSSLConfig(parsed.sslMode);
const pool = new pg.Pool({
connectionString,
max: connectionLimit ?? 20,
connectionString: parsed.connectionString,
max: parsed.connectionLimit ?? 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: connectTimeout ?? 5000,
connectionTimeoutMillis: parsed.connectTimeout ?? 10000,
ssl: sslConfig,
});
const adapter = new PrismaPg(pool, {
schema,
schema: parsed.schema,
});
super({
adapter,
transactionOptions: {
maxWait: 5000, // 5 seconds
timeout: 10000, // 10 seconds
maxWait: 5000,
timeout: 10000,
},
});
this.pool = pool;
}
/**
* --- SSL Configuration ---
* Generic SSL handling for various database environments
* - Local/Docker: No SSL (sslmode=disable or no sslmode)
* - AWS Aurora/RDS: SSL with relaxed validation (common for managed databases)
* - Custom: Set sslmode=verify-full for strict certificate validation
*/
private static getSSLConfig(
sslMode?: string,
): false | { rejectUnauthorized: boolean } {
if (!sslMode || sslMode === 'disable') {
// Local PostgreSQL, Docker containers - no SSL
return false;
}
if (sslMode === 'require' || sslMode === 'prefer' || sslMode === 'allow') {
// AWS Aurora, managed databases - SSL with relaxed validation
// This is a pragmatic approach for cloud databases where:
// - Connection is encrypted (prevents eavesdropping)
// - Network isolation (VPC/firewall) provides additional security
// - Certificate validation issues are common with managed services
return { rejectUnauthorized: false };
}
if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
// Strict certificate validation - requires proper CA certificates
// Note: May require additional configuration for Prisma v7 + adapter-pg
return { rejectUnauthorized: true };
}
// Default to no SSL for unknown modes
return false;
}
/**
* --- DATABASE_URL Parser ---
* Accepts:
* ?schema=custom
* ?connection_limit=10
* ?connect_timeout=5000
* ?sslmode=disable|prefer|require|verify-ca|verify-full
*/
private static parseDatabaseUrl(databaseUrl: string): {
connectionString: string;
schema: string;
connectionLimit?: number;
connectTimeout?: number;
sslMode?: string;
} {
try {
const url = new URL(databaseUrl);
const schema = url.searchParams.get('schema') || 'public';
const connectionLimit = url.searchParams.get('connection_limit');
const connectTimeout = url.searchParams.get('connect_timeout');
const connectionLimit = parseIntSafe(
url.searchParams.get('connection_limit'),
);
const connectTimeout = parseIntSafe(
url.searchParams.get('connect_timeout'),
);
const sslMode = url.searchParams.get('sslmode');
// Remove all custom parameters including sslmode
// We handle SSL configuration programmatically via the ssl option
url.searchParams.delete('schema');
url.searchParams.delete('connection_limit');
url.searchParams.delete('connect_timeout');
url.searchParams.delete('sslmode');
return {
connectionString: url.toString(),
schema,
connectionLimit: parseIntSafe(connectionLimit),
connectTimeout: parseIntSafe(connectTimeout),
connectionLimit,
connectTimeout,
sslMode: sslMode || undefined,
};
} catch (error) {
throw new Error(