dbgate-api
Version:
Allows run DbGate data-manipulation scripts.
199 lines (171 loc) • 6.18 kB
JavaScript
const fs = require('fs-extra');
const { decryptConnection, decryptPasswordString } = require('./crypting');
const { getSshTunnelProxy } = require('./sshTunnelProxy');
const platformInfo = require('../utility/platformInfo');
const connections = require('../controllers/connections');
const _ = require('lodash');
const axios = require('axios');
async function loadConnection(driver, storedConnection, connectionMode) {
const { allowShellConnection, allowConnectionFromEnvVariables } = platformInfo;
if (connectionMode == 'app') {
return storedConnection;
}
if (storedConnection._id || !allowShellConnection) {
if (!storedConnection._id) {
throw new Error('Missing connection _id');
}
await connections._init();
const loaded = await connections.getCore({ conid: storedConnection._id });
const loadedWithDb = {
...loaded,
database: storedConnection.database,
};
if (loaded.isReadOnly) {
if (connectionMode == 'read') return loadedWithDb;
if (connectionMode == 'write') throw new Error('Cannot write readonly connection');
if (connectionMode == 'script') {
if (driver.readOnlySessions) return loadedWithDb;
throw new Error('Cannot write readonly connection');
}
}
return loadedWithDb;
}
if (allowConnectionFromEnvVariables) {
return _.mapValues(storedConnection, (value, key) => {
if (_.isString(value) && value.startsWith('${') && value.endsWith('}')) {
return process.env[value.slice(2, -1)];
}
return value;
});
}
return storedConnection;
}
async function extractConnectionSslParams(connection) {
/** @type {any} */
let ssl = undefined;
if (connection.useSsl) {
ssl = {};
if (connection.sslCaFile) {
ssl.ca = await fs.readFile(connection.sslCaFile);
ssl.sslCaFile = connection.sslCaFile;
}
if (connection.sslCertFile) {
ssl.cert = await fs.readFile(connection.sslCertFile);
ssl.sslCertFile = connection.sslCertFile;
}
if (connection.sslKeyFile) {
ssl.key = await fs.readFile(connection.sslKeyFile);
ssl.sslKeyFile = connection.sslKeyFile;
}
if (connection.sslCertFilePassword) {
ssl.password = connection.sslCertFilePassword;
}
if (!ssl.key && !ssl.ca && !ssl.cert) {
// TODO: provide this as an option in settings
// or per-connection as 'reject self-signed certs'
// How it works:
// if false, cert can be self-signed
// if true, has to be from a public CA
// Heroku certs are self-signed.
// if you provide ca/cert/key files, it overrides this
ssl.rejectUnauthorized = false;
} else {
ssl.rejectUnauthorized = connection.sslRejectUnauthorized;
}
}
return ssl;
}
async function decryptCloudConnection(connection) {
const { getCloudFolderEncryptor } = require('./cloudIntf');
const m = connection?._id?.match(/^cloud\:\/\/(.+)\/(.+)$/);
if (!m) {
throw new Error('Invalid cloud connection ID format');
}
const folid = m[1];
const cntid = m[2];
const folderEncryptor = await getCloudFolderEncryptor(folid);
return decryptConnection(connection, folderEncryptor);
}
async function connectUtility(driver, storedConnection, connectionMode, additionalOptions = null) {
const connectionLoaded = await loadConnection(driver, storedConnection, connectionMode);
const connection = connectionLoaded?._id?.startsWith('cloud://')
? {
database: connectionLoaded.defaultDatabase,
...(await decryptCloudConnection(connectionLoaded)),
}
: {
database: connectionLoaded.defaultDatabase,
...decryptConnection(connectionLoaded),
};
if (!connection.port && driver.defaultPort) {
connection.port = driver.defaultPort.toString();
}
if (connection.useSshTunnel) {
const tunnel = await getSshTunnelProxy(connection);
if (tunnel.state == 'error') {
throw new Error(tunnel.message);
}
connection.server = tunnel.localHost;
connection.port = tunnel.localPort;
}
connection.ssl = await extractConnectionSslParams(connection);
const proxyUrl = String(connection.httpProxyUrl ?? '').trim();
const proxyUser = String(connection.httpProxyUser ?? '').trim();
const proxyPassword = String(connection.httpProxyPassword ?? '').trim();
if (!proxyUrl && (proxyUser || proxyPassword)) {
throw new Error('DBGM-00329 Proxy user or password is set but proxy URL is missing');
}
if (proxyUrl) {
let parsedProxy;
try {
const parsed = new URL(proxyUrl.includes('://') ? proxyUrl : `http://${proxyUrl}`);
parsedProxy = {
protocol: parsed.protocol.replace(':', ''),
host: parsed.hostname,
port: parsed.port ? parseInt(parsed.port, 10) : (parsed.protocol === 'https:' ? 443 : 80),
};
const username = connection.httpProxyUser ?? parsed.username;
const rawPassword = connection.httpProxyPassword ?? parsed.password;
const password = decryptPasswordString(rawPassword);
if (username) {
parsedProxy.auth = { username, password: password ?? '' };
}
} catch (err) {
throw new Error(`DBGM-00334 Invalid proxy URL "${proxyUrl}": ${err && err.message ? err.message : err}`);
}
connection.axios = axios.default.create({ proxy: parsedProxy });
} else {
connection.axios = axios.default;
}
const conn = await driver.connect({ conid: connectionLoaded?._id, ...connection, ...additionalOptions });
return conn;
}
function getRestAuthFromConnection(connection) {
if (!connection) return null;
if (connection.authType == 'basic') {
return {
type: 'basic',
user: connection.user,
password: decryptPasswordString(connection.password),
};
}
if (connection.authType == 'bearer') {
return {
type: 'bearer',
token: connection.authToken,
};
}
if (connection.authType == 'apikey') {
return {
type: 'apikey',
header: connection.apiKeyHeader,
value: connection.apiKeyValue,
};
}
return null;
}
module.exports = {
extractConnectionSslParams,
connectUtility,
getRestAuthFromConnection,
};