UNPKG

db-connections-util

Version:

A utility package for parsing, encrypting, and reconstructing database connection strings

311 lines (287 loc) 13.3 kB
import { DB_TYPES, ERROR_MESSAGES } from './constants.js'; import { encrypt } from './encryption.js'; //Parses a MongoDB connection string const parseMongoDBConnection = (connectionString, result) => { const withoutProtocol = connectionString.slice(result.protocol.length + 3); const [credentialsAndHost, params] = withoutProtocol.split('?'); const atSplit = credentialsAndHost.split('@'); if (atSplit.length === 2) { const [credentials, host] = atSplit; const hostParts = host.split('/'); result.host = hostParts[0]; if (!connectionString.startsWith('mongodb+srv') && result.host.includes(':')) { const [hostname, port] = result.host.split(':'); result.host = hostname; result.port = port; } const colonSplit = credentials.split(':'); if (colonSplit.length === 2) { result.username = decodeURIComponent(colonSplit[0]); result.password = decodeURIComponent(colonSplit[1]); } if (hostParts.length > 1 && hostParts[1]) { result.dbName = hostParts[1].split('?')[0]; } } else { const hostParts = credentialsAndHost.split('/'); result.host = hostParts[0]; if (hostParts.length > 1 && hostParts[1]) { result.dbName = hostParts[1].split('?')[0]; } } if (params) { const paramsObj = new URLSearchParams(params); paramsObj.forEach((value, key) => { result.params[key] = value; }); } }; //Parses an Azure CosmosDB connection string const parseAzureCosmosConnection = (connectionString, result) => { const parts = connectionString.split(';'); parts.forEach(part => { const [key, value] = part.split('='); if (key && value) { switch (key.toLowerCase()) { case 'accountendpoint': try { const url = new URL(value); result.host = url.hostname; result.port = url.port || ''; result.protocol = url.protocol.replace(':', ''); result.params.originalEndpoint = value; } catch { result.host = value; } break; case 'accountkey': result.password = value; break; case 'databasename': case 'database': result.dbName = value; break; default: result.params[key] = value; } } }); }; //Parses a SQL Server connection string const parseSqlServerConnection = (connectionString, result) => { // Handle both semicolon-separated and URL-style connection strings if (connectionString.includes(';')) { // Semicolon-separated format (e.g., "Server=localhost;Database=mydb;User Id=user;Password=pass;") const parts = connectionString.split(';').filter(part => part.trim()); parts.forEach(part => { const [key, ...valueParts] = part.split('='); const value = valueParts.join('='); // Handle values that contain '=' if (key && value) { const lowerKey = key.toLowerCase().trim(); switch (lowerKey) { case 'server': case 'data source': case 'datasource': // Handle server with instance (e.g., "localhost\SQLEXPRESS") if (value.includes('\\')) { const [host, instance] = value.split('\\'); result.host = host; result.params.instance = instance; } else if (value.includes(',')) { // Handle server with port (e.g., "localhost,1433") const [host, port] = value.split(','); result.host = host; result.port = port; } else { result.host = value; } break; case 'database': case 'initial catalog': result.dbName = value; break; case 'user id': case 'userid': case 'uid': result.username = value; break; case 'password': case 'pwd': result.password = value; break; case 'port': result.port = value; break; default: result.params[key.trim()] = value; } } }); } else { try { // Handle instance name in URL format if (connectionString.includes('\\')) { const [protocolAndAuth, hostAndPath] = connectionString.split('@'); const [protocol, auth] = protocolAndAuth.split('://'); const [username, password] = auth.split(':'); const [hostWithInstance, ...pathParts] = hostAndPath.split('/'); const [host, instance] = hostWithInstance.split('\\'); result.protocol = protocol; result.username = username; result.password = password; result.host = host; result.params.instance = instance; result.port = hostWithInstance.includes(':') ? hostWithInstance.split(':')[1].split('\\')[0] : '1433'; result.dbName = pathParts[0] || null; } else { const url = new URL(connectionString); result.host = url.hostname; result.port = url.port; result.username = url.username; result.password = url.password; result.dbName = url.pathname ? url.pathname.replace(/^\//, '') : null; url.searchParams.forEach((value, key) => { result.params[key] = value; }); } } catch (e) { throw new Error('Invalid SQL Server connection string format'); } } }; //Parses a Redis connection string (including cluster support) const parseRedisConnection = (connectionString, result) => { // Handle Redis cluster format (multiple hosts separated by commas) if ((connectionString.includes(',') && !connectionString.startsWith('redis://') && !connectionString.startsWith('rediss://')) || (connectionString.startsWith('redis://') && connectionString.split('@')[1]?.includes(','))) { // Cluster format: "host1:port1,host2:port2,host3:port3" or "redis://user:pass@host1:port1,host2:port2" const authPart = connectionString.includes('@') ? connectionString.split('@')[0] : ''; const hostsPart = connectionString.includes('@') ? connectionString.split('@')[1] : connectionString; const hosts = hostsPart.split(',').map(host => host.trim()); if (authPart) { const [username, password] = authPart.replace('redis://', '').split(':'); result.username = username; result.password = password; } result.host = hosts[0].split(':')[0]; result.port = hosts[0].includes(':') ? hosts[0].split(':')[1] : '6379'; result.params.clusterHosts = hosts; result.params.isCluster = true; } else { // Standard URL format or simple host:port try { if (connectionString.startsWith('redis://') || connectionString.startsWith('rediss://')) { const url = new URL(connectionString); result.protocol = url.protocol.replace(':', ''); result.host = url.hostname; result.port = url.port || '6379'; result.username = url.username; result.password = url.password; // Redis database number from pathname if (url.pathname && url.pathname !== '/') { result.dbName = url.pathname.replace(/^\//, ''); } url.searchParams.forEach((value, key) => { result.params[key] = value; }); } else { // Simple host:port format if (connectionString.includes(':')) { const [host, port] = connectionString.split(':'); result.host = host; result.port = port; } else { result.host = connectionString; result.port = '6379'; } } } catch (e) { throw new Error('Invalid Redis connection string format'); } } }; //Parses a database connection string export const parseDatabaseConnection = (connectionString, secret) => { const result = { dbType: DB_TYPES.UNKNOWN, isCloud: false, protocol: null, username: null, password: null, host: null, port: null, dbName: null, params: {}, originalString: connectionString }; if (!connectionString) { return result; } try { if (connectionString.startsWith('mongodb+srv://')) { result.dbType = DB_TYPES.MONGODB_ATLAS; result.isCloud = true; result.protocol = 'mongodb+srv'; parseMongoDBConnection(connectionString, result); } else if (connectionString.startsWith('mongodb://')) { result.dbType = DB_TYPES.MONGODB_COMPASS; result.protocol = 'mongodb'; parseMongoDBConnection(connectionString, result); } else if (connectionString.includes('AccountEndpoint=') || connectionString.includes('.documents.azure.com')) { result.dbType = DB_TYPES.AZURE_COSMOS; result.isCloud = true; parseAzureCosmosConnection(connectionString, result); } else if (connectionString.toLowerCase().includes('server=') || connectionString.toLowerCase().includes('data source=') || connectionString.startsWith('mssql://') || connectionString.startsWith('sqlserver://')) { result.dbType = DB_TYPES.SQL_SERVER; result.protocol = connectionString.startsWith('mssql://') ? 'mssql' : connectionString.startsWith('sqlserver://') ? 'sqlserver' : 'mssql'; parseSqlServerConnection(connectionString, result); } else if (connectionString.startsWith('redis://') || connectionString.startsWith('rediss://') || // Check for Redis cluster format or simple host:port without protocol (!connectionString.includes('://') && (connectionString.includes(':') || connectionString.includes(',')))) { result.dbType = DB_TYPES.REDIS; result.protocol = connectionString.startsWith('rediss://') ? 'rediss' : 'redis'; parseRedisConnection(connectionString, result); } else { try { const url = new URL(connectionString); result.protocol = url.protocol.replace(':', ''); result.host = url.hostname; result.port = url.port; result.username = url.username; result.password = url.password; result.dbName = url.pathname ? url.pathname.replace(/^\//, '') : null; url.searchParams.forEach((value, key) => { result.params[key] = value; }); if (result.protocol.includes('mysql')) result.dbType = DB_TYPES.MYSQL; else if (result.protocol.includes('postgres')) result.dbType = DB_TYPES.POSTGRESQL; else if (result.protocol.includes('redis')) result.dbType = DB_TYPES.REDIS; else if (result.protocol.includes('mssql') || result.protocol.includes('sqlserver')) result.dbType = DB_TYPES.SQL_SERVER; } catch (e) { throw new Error(ERROR_MESSAGES.INVALID_CONNECTION_STRING); } } if (secret) { // Store encrypted password if (result.password) { result.password = encrypt(result.password, secret); } // Store encrypted connection string if (result.originalString) { result.originalString = encrypt(result.originalString, secret); } } return result; } catch (error) { throw new Error(`${ERROR_MESSAGES.INVALID_CONNECTION_STRING}: ${error.message}`); } };