diff --git a/app/package.json b/app/package.json
index 559b95b5e..77005ee43 100644
--- a/app/package.json
+++ b/app/package.json
@@ -37,6 +37,15 @@
"github"
]
},
+ "snap": {
+ "publish": [
+ "github",
+ "snapStore"
+ ],
+ "environment": {
+ "ELECTRON_SNAP": "true"
+ }
+ },
"win": {
"target": [
"nsis"
diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js
index 7baa33201..16f9efae5 100644
--- a/packages/api/src/controllers/config.js
+++ b/packages/api/src/controllers/config.js
@@ -1,4 +1,5 @@
const currentVersion = require('../currentVersion');
+const platformInfo = require('../utility/platformInfo');
module.exports = {
get_meta: 'get',
@@ -31,4 +32,10 @@ module.exports = {
...currentVersion,
};
},
+
+ platformInfo_meta: 'get',
+ async platformInfo() {
+ return platformInfo;
+ },
+
};
diff --git a/packages/api/src/controllers/runners.js b/packages/api/src/controllers/runners.js
index bec81e626..e1c9a2253 100644
--- a/packages/api/src/controllers/runners.js
+++ b/packages/api/src/controllers/runners.js
@@ -24,6 +24,7 @@ const requirePluginsTemplate = plugins =>
const scriptTemplate = script => `
const dbgateApi = require(process.env.DBGATE_API);
+dbgateApi.registerProcessCommunication();
${requirePluginsTemplate(extractPlugins(script))}
require=null;
async function run() {
@@ -36,6 +37,7 @@ dbgateApi.runScript(run);
const loaderScriptTemplate = (functionName, props, runid) => `
const dbgateApi = require(process.env.DBGATE_API);
+dbgateApi.registerProcessCommunication();
${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
require=null;
async function run() {
diff --git a/packages/api/src/shell/index.js b/packages/api/src/shell/index.js
index 403501ec5..492adc6f7 100644
--- a/packages/api/src/shell/index.js
+++ b/packages/api/src/shell/index.js
@@ -17,6 +17,7 @@ const requirePlugin = require('./requirePlugin');
const download = require('./download');
const executeQuery = require('./executeQuery');
const loadFile = require('./loadFile');
+const registerProcessCommunication = require('./registerProcessCommunication');
const dbgateApi = {
queryReader,
@@ -37,6 +38,7 @@ const dbgateApi = {
registerPlugins,
executeQuery,
loadFile,
+ registerProcessCommunication,
};
requirePlugin.initializeDbgateApi(dbgateApi);
diff --git a/packages/api/src/shell/registerProcessCommunication.js b/packages/api/src/shell/registerProcessCommunication.js
new file mode 100644
index 000000000..d090bd7cb
--- /dev/null
+++ b/packages/api/src/shell/registerProcessCommunication.js
@@ -0,0 +1,9 @@
+const { handleProcessCommunication } = require('../utility/processComm');
+
+async function registerProcessCommunication() {
+ process.on('message', async message => {
+ handleProcessCommunication(message);
+ });
+}
+
+module.exports = registerProcessCommunication;
diff --git a/packages/api/src/utility/crypting.js b/packages/api/src/utility/crypting.js
index 98c267186..8fad163a1 100644
--- a/packages/api/src/utility/crypting.js
+++ b/packages/api/src/utility/crypting.js
@@ -42,31 +42,45 @@ function getEncryptor() {
return _encryptor;
}
-function encryptConnection(connection) {
+function encryptPasswordField(connection, field) {
if (
connection &&
- connection.password &&
- !connection.password.startsWith('crypt:') &&
+ connection[field] &&
+ !connection[field].startsWith('crypt:') &&
connection.passwordMode != 'saveRaw'
) {
return {
...connection,
- password: 'crypt:' + getEncryptor().encrypt(connection.password),
+ [field]: 'crypt:' + getEncryptor().encrypt(connection[field]),
};
}
return connection;
}
-function decryptConnection(connection) {
- if (connection && connection.password && connection.password.startsWith('crypt:')) {
+function decryptPasswordField(connection, field) {
+ if (connection && connection[field] && connection[field].startsWith('crypt:')) {
return {
...connection,
- password: getEncryptor().decrypt(connection.password.substring('crypt:'.length)),
+ [field]: getEncryptor().decrypt(connection[field].substring('crypt:'.length)),
};
}
return connection;
}
+function encryptConnection(connection) {
+ connection = encryptPasswordField(connection, 'password');
+ connection = encryptPasswordField(connection, 'sshPassword');
+ connection = encryptPasswordField(connection, 'sshKeyFilePassword');
+ return connection;
+}
+
+function decryptConnection(connection) {
+ connection = decryptPasswordField(connection, 'password');
+ connection = decryptPasswordField(connection, 'sshPassword');
+ connection = decryptPasswordField(connection, 'sshKeyFilePassword');
+ return connection;
+}
+
module.exports = {
loadEncryptionKey,
encryptConnection,
diff --git a/packages/api/src/utility/platformInfo.js b/packages/api/src/utility/platformInfo.js
new file mode 100644
index 000000000..22ba0fb24
--- /dev/null
+++ b/packages/api/src/utility/platformInfo.js
@@ -0,0 +1,27 @@
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+const p = process;
+const platform = p.env.OS_OVERRIDE ? p.env.OS_OVERRIDE : p.platform;
+const isWindows = platform === 'win32';
+const isMac = platform === 'darwin';
+const isLinux = platform === 'linux';
+const isDocker = fs.existsSync('/home/dbgate-docker/build');
+
+const platformInfo = {
+ isWindows,
+ isMac,
+ isLinux,
+ isDocker,
+ isSnap: p.env.ELECTRON_SNAP,
+ isPortable: isWindows && p.env.PORTABLE_EXECUTABLE_DIR,
+ isAppImage: p.env.DESKTOPINTEGRATION === 'AppImageLauncher',
+ sshAuthSock: p.env.SSH_AUTH_SOCK,
+ environment: process.env.NODE_ENV,
+ platform,
+ runningInWebpack: !!p.env.WEBPACK_DEV_SERVER_URL,
+ defaultKeyFile: path.join(os.homedir(), '.ssh/id_rsa'),
+};
+
+module.exports = platformInfo;
diff --git a/packages/api/src/utility/sshTunnel.js b/packages/api/src/utility/sshTunnel.js
index c37b9e726..2744f695c 100644
--- a/packages/api/src/utility/sshTunnel.js
+++ b/packages/api/src/utility/sshTunnel.js
@@ -1,7 +1,9 @@
const { SSHConnection } = require('node-ssh-forward');
+const fs = require('fs-extra');
const portfinder = require('portfinder');
const stableStringify = require('json-stable-stringify');
const _ = require('lodash');
+const platformInfo = require('./platformInfo');
const sshConnectionCache = {};
const sshTunnelCache = {};
@@ -16,11 +18,14 @@ async function getSshConnection(connection) {
const sshConfig = {
endHost: connection.sshHost || '',
endPort: connection.sshPort || 22,
- bastionHost: '',
- agentForward: false,
- passphrase: undefined,
+ bastionHost: connection.sshBastionHost || '',
+ agentForward: connection.sshMode == 'agent',
+ passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyFilePassword : undefined,
username: connection.sshLogin,
- password: connection.sshPassword,
+ password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
+ agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
+ privateKey:
+ connection.sshMode == 'keyFile' && connection.sshKeyFile ? await fs.readFile(connection.sshKeyFile) : undefined,
skipAutoPrivateKey: true,
noReadline: true,
};
diff --git a/packages/web/src/impexp/createImpExpScript.js b/packages/web/src/impexp/createImpExpScript.js
index 5b29fc17c..b1ff82614 100644
--- a/packages/web/src/impexp/createImpExpScript.js
+++ b/packages/web/src/impexp/createImpExpScript.js
@@ -31,7 +31,7 @@ async function getConnection(extensions, storageType, conid, database) {
const driver = findEngineDriver(conn, extensions);
return [
{
- ..._.pick(conn, ['server', 'engine', 'user', 'password', 'port', 'authType']),
+ ..._.omit(conn, ['_id', 'displayName']),
database,
},
driver,
diff --git a/packages/web/src/modals/ConnectionModal.js b/packages/web/src/modals/ConnectionModal.js
index f189e6330..ec8bdf7c8 100644
--- a/packages/web/src/modals/ConnectionModal.js
+++ b/packages/web/src/modals/ConnectionModal.js
@@ -8,6 +8,7 @@ import {
FormSubmit,
FormPasswordField,
FormCheckboxField,
+ FormElectronFileSelector,
} from '../utility/forms';
import ModalHeader from './ModalHeader';
import ModalFooter from './ModalFooter';
@@ -17,6 +18,8 @@ import LoadingInfo from '../widgets/LoadingInfo';
import { FontIcon } from '../icons';
import { FormProvider, useForm } from '../utility/FormProvider';
import { TabControl, TabPage } from '../widgets/TabControl';
+import { usePlatformInfo } from '../utility/metadataLoaders';
+import getElectron from '../utility/getElectron';
// import FormikForm from '../utility/FormikForm';
function DriverFields({ extensions }) {
@@ -69,15 +72,55 @@ function DriverFields({ extensions }) {
}
function SshTunnelFields() {
- const { values } = useForm();
- const { useSshTunnel } = values;
+ const { values, setFieldValue } = useForm();
+ const { useSshTunnel, sshMode, sshKeyfile } = values;
+ const platformInfo = usePlatformInfo();
+ const electron = getElectron();
+
+ React.useEffect(() => {
+ if (useSshTunnel && !sshMode) {
+ setFieldValue('sshMode', 'userPassword');
+ }
+ if (useSshTunnel && sshMode == 'keyFile' && !sshKeyfile) {
+ setFieldValue('sshKeyfile', platformInfo.defaultKeyFile);
+ }
+ }, [useSshTunnel, sshMode]);
+
return (
<>