diff --git a/.env.example b/.env.example index ae3ccd6a..c3b41639 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,12 @@ DB_POOL_MAX=20 DB_CONNECTION_TIMEOUT=5000 DB_IDLE_TIMEOUT=30000 +# Database SSL Configuration (Required in production) +# Path to CA certificate file for TLS verification +# In production, this must be set to enable secure certificate validation +# Example: /etc/ssl/certs/ca-bundle.crt or /path/to/ca.pem +DB_SSL_CA= + # SMS Integration (#448) # Provider selection: twilio | sns | vonage (default: twilio) SMS_PROVIDER=twilio diff --git a/src/lib/db/pool.ts b/src/lib/db/pool.ts index f1fc9dd8..894d927b 100644 --- a/src/lib/db/pool.ts +++ b/src/lib/db/pool.ts @@ -13,13 +13,30 @@ import { retryWithBackoff } from '@/utils/errorUtils'; * - Query queueing during reconnect windows */ -const DB_CONFIG: PoolConfig = { +const getSSLConfig = () => { + if (process.env.NODE_ENV === 'production') { + if (!process.env.DB_SSL_CA) { + throw new Error( + 'DB_SSL_CA environment variable is required in production. ' + + 'This should contain the path to your CA certificate file.', + ); + } + return { + rejectUnauthorized: true, + ca: process.env.DB_SSL_CA, + }; + } + // Allow unverified certificates in development + return false; +}; + +const getDbConfig = (): PoolConfig => ({ connectionString: process.env.DATABASE_URL, max: parseInt(process.env.DB_POOL_MAX || '20', 10), connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT || '5000', 10), idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000', 10), - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}; + ssl: getSSLConfig(), +}); type CircuitState = 'CLOSED' | 'OPEN'; @@ -44,7 +61,7 @@ class DatabasePool { public static getInstance(): Pool { if (!DatabasePool.instance) { - DatabasePool.instance = new Pool(DB_CONFIG); + DatabasePool.instance = new Pool(getDbConfig()); DatabasePool.instance.on('connect', () => { if (process.env.NODE_ENV === 'development') {