// Database configuration template with environment variable support
//
// IMPORTANT: Copy this file to auth.ts and customize the database connection
//
// Steps:
// 1. Copy this file: cp src/auth.example.ts src/auth.ts
// 2. Update the connectionString below with your PostgreSQL credentials
// 3. Adjust connection pool settings if needed
// 4. The auth.ts file is already in .gitignore to protect your credentials
// 5. You can also use environment variables to override any setting
import { DatabaseConfig } from './types.js';
import { ValidationError } from './error-handler.js';
// Environment variable configuration
const getEnvVar = (key: string, defaultValue?: string): string => {
const value = process.env[key];
if (value === undefined) {
if (defaultValue !== undefined) {
return defaultValue;
}
throw new Error(`Required environment variable ${key} is not set`);
}
return value;
};
const getEnvNumber = (key: string, defaultValue: number): number => {
const value = process.env[key];
if (value === undefined) {
return defaultValue;
}
const num = Number(value);
if (isNaN(num)) {
throw new Error(`Environment variable ${key} must be a number`);
}
return num;
};
const getEnvBoolean = (key: string, defaultValue: boolean): boolean => {
const value = process.env[key];
if (value === undefined) {
return defaultValue;
}
return value.toLowerCase() === 'true';
};
// Connection string validation
export function validateConnectionString(connectionString: string): void {
if (!connectionString || typeof connectionString !== 'string') {
throw new ValidationError('Connection string must be a non-empty string');
}
// Check for basic PostgreSQL connection string format
const postgresRegex =
/^postgres(ql)?:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)$/;
if (!postgresRegex.test(connectionString)) {
throw new ValidationError(
'Invalid PostgreSQL connection string format. Expected: postgresql://username:password@host:port/database'
);
}
// Parse and validate components
try {
const url = new URL(connectionString);
// Validate protocol
if (!url.protocol.startsWith('postgres')) {
throw new ValidationError(
'Connection string must use postgresql:// protocol'
);
}
// Validate required components
if (!url.hostname) {
throw new ValidationError('Connection string must include hostname');
}
if (!url.port) {
throw new ValidationError('Connection string must include port');
}
const port = parseInt(url.port, 10);
if (isNaN(port) || port < 1 || port > 65535) {
throw new ValidationError(
'Port must be a valid number between 1 and 65535'
);
}
if (!url.pathname || url.pathname === '/') {
throw new ValidationError('Connection string must include database name');
}
if (!url.username) {
throw new ValidationError('Connection string must include username');
}
if (!url.password) {
throw new ValidationError('Connection string must include password');
}
// Validate hostname format
const hostnameRegex = /^[a-zA-Z0-9.-]+$/;
if (!hostnameRegex.test(url.hostname)) {
throw new ValidationError('Invalid hostname format');
}
// Validate database name format
const dbName = url.pathname.substring(1); // Remove leading slash
const dbNameRegex = /^[a-zA-Z0-9_-]+$/;
if (!dbNameRegex.test(dbName)) {
throw new ValidationError(
'Database name can only contain letters, numbers, underscores, and hyphens'
);
}
// Validate username format
const usernameRegex = /^[a-zA-Z0-9_-]+$/;
if (!usernameRegex.test(url.username)) {
throw new ValidationError(
'Username can only contain letters, numbers, underscores, and hyphens'
);
}
} catch (error) {
if (error instanceof ValidationError) {
throw error;
}
throw new ValidationError(
`Invalid connection string: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
// Validate database configuration
export function validateDatabaseConfig(config: DatabaseConfig): void {
// Validate connection string
validateConnectionString(config.connectionString);
// Validate pool settings
if (config.max !== undefined) {
if (typeof config.max !== 'number' || config.max < 1 || config.max > 100) {
throw new ValidationError(
'Pool max connections must be a number between 1 and 100'
);
}
}
if (config.idleTimeoutMillis !== undefined) {
if (
typeof config.idleTimeoutMillis !== 'number' ||
config.idleTimeoutMillis < 1000
) {
throw new ValidationError('Idle timeout must be at least 1000ms');
}
}
if (config.connectionTimeoutMillis !== undefined) {
if (
typeof config.connectionTimeoutMillis !== 'number' ||
config.connectionTimeoutMillis < 1000
) {
throw new ValidationError('Connection timeout must be at least 1000ms');
}
}
// Validate SSL configuration
if (config.ssl !== undefined) {
if (typeof config.ssl !== 'object' || config.ssl === null) {
throw new ValidationError('SSL configuration must be an object');
}
if (
config.ssl.rejectUnauthorized !== undefined &&
typeof config.ssl.rejectUnauthorized !== 'boolean'
) {
throw new ValidationError('SSL rejectUnauthorized must be a boolean');
}
}
}
// Database configuration with environment variable support
const dbConfig: DatabaseConfig = {
// PostgreSQL connection string
// Can be overridden with DB_CONNECTION_STRING environment variable
connectionString: getEnvVar(
'DB_CONNECTION_STRING',
'postgresql://username:password@localhost:5432/database'
),
// Connection pool settings
max: getEnvNumber('DB_POOL_MAX', 20), // Maximum number of clients in the pool
idleTimeoutMillis: getEnvNumber('DB_IDLE_TIMEOUT', 30000), // Close idle clients after 30 seconds
connectionTimeoutMillis: getEnvNumber('DB_CONNECTION_TIMEOUT', 2000), // Return an error after 2 seconds if connection could not be established
// SSL configuration
ssl:
process.env.DB_SSL_ENABLED === 'true'
? {
rejectUnauthorized: getEnvBoolean(
'DB_SSL_REJECT_UNAUTHORIZED',
false
),
}
: undefined,
};
// Validate the database configuration
try {
validateDatabaseConfig(dbConfig);
} catch (error) {
console.error('❌ Database configuration validation failed:', error);
throw error;
}
export const DB_CONFIG = dbConfig;
// Optional: Provider-specific settings with environment variable support
export const PROVIDER_CONFIG = {
// Default behavior when no provider is specified
defaultBehavior: getEnvVar('DEFAULT_PROVIDER_BEHAVIOR', 'ALL'), // Query all providers by default
// Available providers (will be auto-detected from database)
availableProviders: [], // Populated at runtime
// Provider display names
displayNames: {
'deutsche-bank': getEnvVar(
'PROVIDER_DISPLAY_DEUTSCHE_BANK',
'Deutsche Bank'
),
tink: getEnvVar('PROVIDER_DISPLAY_TINK', 'Tink'),
truelayer: getEnvVar('PROVIDER_DISPLAY_TRUELAYER', 'TrueLayer'),
gocardless: getEnvVar('PROVIDER_DISPLAY_GOCARDLESS', 'GoCardless'),
},
};
// Application configuration with environment variable support
export const APP_CONFIG = {
// Server settings
port: getEnvNumber('APP_PORT', 3000),
host: getEnvVar('APP_HOST', 'localhost'),
environment: getEnvVar('NODE_ENV', 'development'),
// Logging settings
logLevel: getEnvVar('LOG_LEVEL', 'info'), // debug, info, warn, error
enableStructuredLogging: getEnvBoolean('ENABLE_STRUCTURED_LOGGING', false),
enableQueryLogging: getEnvBoolean('ENABLE_QUERY_LOGGING', false),
// Performance settings
enableCaching: getEnvBoolean('ENABLE_CACHING', false),
enablePerformanceMonitoring: getEnvBoolean(
'ENABLE_PERFORMANCE_MONITORING',
true
),
// Health check settings
healthCheckInterval: getEnvNumber('HEALTH_CHECK_INTERVAL', 30000), // 30 seconds
// Security settings
enableInputSanitization: getEnvBoolean('ENABLE_INPUT_SANITIZATION', true),
};