diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js
index 5447a6f4..bf967bd0 100644
--- a/packages/api/src/controllers/connections.js
+++ b/packages/api/src/controllers/connections.js
@@ -2,6 +2,7 @@ const path = require('path');
const { fork } = require('child_process');
const _ = require('lodash');
const fs = require('fs-extra');
+const crypto = require('crypto');
const { datadir, filesdir } = require('../utility/directories');
const socket = require('../utility/socket');
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo');
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
+let volatileConnections = {};
+
function getNamedArgs() {
const res = {};
for (let i = 0; i < process.argv.length; i++) {
@@ -126,6 +129,7 @@ function getPortalCollections() {
return null;
}
+
const portalConnections = getPortalCollections();
function getSingleDatabase() {
@@ -199,6 +203,24 @@ module.exports = {
});
},
+ saveVolatile_meta: true,
+ async saveVolatile({ conid, user, password }) {
+ const old = await this.getCore({ conid });
+ const res = {
+ ...old,
+ _id: crypto.randomUUID(),
+ password,
+ passwordMode: undefined,
+ unsaved: true,
+ };
+ if (old.passwordMode == 'askUser') {
+ res.user = user;
+ }
+
+ volatileConnections[res._id] = res;
+ return res;
+ },
+
save_meta: true,
async save(connection) {
if (portalConnections) return;
@@ -258,6 +280,10 @@ module.exports = {
async getCore({ conid, mask = false }) {
if (!conid) return null;
+ const volatile = volatileConnections[conid];
+ if (volatile) {
+ return volatile;
+ }
if (portalConnections) {
const res = portalConnections.find(x => x._id == conid) || null;
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js
index 6be1a25c..2ace4869 100644
--- a/packages/api/src/controllers/databaseConnections.js
+++ b/packages/api/src/controllers/databaseConnections.js
@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
+const { MissingCredentialsError } = require('../utility/exceptions');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -81,6 +82,9 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) return existing;
const connection = await connections.getCore({ conid });
+ if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
+ throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
+ }
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js
index 36a751ba..2ed0189a 100644
--- a/packages/api/src/controllers/serverConnections.js
+++ b/packages/api/src/controllers/serverConnections.js
@@ -9,6 +9,7 @@ const lock = new AsyncLock();
const config = require('./config');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
+const { MissingCredentialsError } = require('../utility/exceptions');
module.exports = {
opened: [],
@@ -46,6 +47,9 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid);
if (existing) return existing;
const connection = await connections.getCore({ conid });
+ if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
+ throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
+ }
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
diff --git a/packages/api/src/utility/exceptions.js b/packages/api/src/utility/exceptions.js
new file mode 100644
index 00000000..fca5679d
--- /dev/null
+++ b/packages/api/src/utility/exceptions.js
@@ -0,0 +1,9 @@
+class MissingCredentialsError {
+ constructor(detail) {
+ this.detail = detail;
+ }
+}
+
+module.exports = {
+ MissingCredentialsError,
+};
diff --git a/packages/api/src/utility/useController.js b/packages/api/src/utility/useController.js
index 8ee431a4..1c3e562c 100644
--- a/packages/api/src/utility/useController.js
+++ b/packages/api/src/utility/useController.js
@@ -1,6 +1,7 @@
const _ = require('lodash');
const express = require('express');
const getExpressPath = require('./getExpressPath');
+const { MissingCredentialsError } = require('./exceptions');
/**
* @param {string} route
@@ -37,6 +38,13 @@ module.exports = function useController(app, electron, route, controller) {
if (data === undefined) return null;
return data;
} catch (err) {
+ if (err instanceof MissingCredentialsError) {
+ return {
+ missingCredentials: true,
+ apiErrorMessage: 'Missing credentials',
+ detail: err.detail,
+ };
+ }
return { apiErrorMessage: err.message };
}
});
@@ -69,7 +77,15 @@ module.exports = function useController(app, electron, route, controller) {
res.json(data);
} catch (e) {
console.log(e);
- res.status(500).json({ apiErrorMessage: e.message });
+ if (e instanceof MissingCredentialsError) {
+ res.json({
+ missingCredentials: true,
+ apiErrorMessage: 'Missing credentials',
+ detail: e.detail,
+ });
+ } else {
+ res.status(500).json({ apiErrorMessage: e.message });
+ }
}
});
}
diff --git a/packages/web/src/modals/DatabaseLoginModal.svelte b/packages/web/src/modals/DatabaseLoginModal.svelte
new file mode 100644
index 00000000..e5c14ddc
--- /dev/null
+++ b/packages/web/src/modals/DatabaseLoginModal.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ Database Log In
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web/src/settings/ConnectionDriverFields.svelte b/packages/web/src/settings/ConnectionDriverFields.svelte
index ebe5e518..eb3e1a6a 100644
--- a/packages/web/src/settings/ConnectionDriverFields.svelte
+++ b/packages/web/src/settings/ConnectionDriverFields.svelte
@@ -28,8 +28,12 @@
$: driver = $extensions.drivers.find(x => x.engine == engine);
$: defaultDatabase = $values.defaultDatabase;
- $: showUser = driver?.showConnectionField('user', $values);
- $: showPassword = driver?.showConnectionField('password', $values);
+ $: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
+ $: showPassword =
+ driver?.showConnectionField('password', $values) &&
+ $values.passwordMode != 'askPassword' &&
+ $values.passwordMode != 'askUser';
+ $: showPasswordMode = driver?.showConnectionField('password', $values);
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
@@ -159,7 +163,7 @@
{/if}
-{#if !disabledFields.includes('password') && showPassword}
+{#if !disabledFields.includes('password') && showPasswordMode}
{/if}
diff --git a/packages/web/src/utility/api.ts b/packages/web/src/utility/api.ts
index e55c839c..aa331267 100644
--- a/packages/web/src/utility/api.ts
+++ b/packages/web/src/utility/api.ts
@@ -5,6 +5,9 @@ import getElectron from './getElectron';
// import socket from './socket';
import { showSnackbarError } from '../utility/snackbar';
import { isOauthCallback, redirectToLogin } from '../clientAuth';
+import { showModal } from '../modals/modalTools';
+import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
+import _ from 'lodash';
let eventSource;
let apiLogging = false;
@@ -12,6 +15,8 @@ let apiLogging = false;
let apiDisabled = false;
const disabledOnOauth = isOauthCallback();
+const volatileConnectionMap = {};
+
export function disableApi() {
apiDisabled = true;
}
@@ -20,6 +25,10 @@ export function enableApi() {
apiDisabled = false;
}
+export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
+ volatileConnectionMap[existingConnectionId] = volatileConnectionId;
+}
+
function wantEventSource() {
if (!eventSource) {
eventSource = new EventSource(`${resolveApi()}/stream`);
@@ -32,7 +41,16 @@ function processApiResponse(route, args, resp) {
// console.log('<<< API RESPONSE', route, args, resp);
// }
- if (resp?.apiErrorMessage) {
+ if (resp?.missingCredentials) {
+ if (!isDatabaseLoginVisible()) {
+ showModal(DatabaseLoginModal, resp.detail);
+ }
+ return null;
+ // return {
+ // errorMessage: resp.apiErrorMessage,
+ // missingCredentials: true,
+ // };
+ } else if (resp?.apiErrorMessage) {
showSnackbarError('API error:' + resp?.apiErrorMessage);
return {
errorMessage: resp.apiErrorMessage,
@@ -42,6 +60,10 @@ function processApiResponse(route, args, resp) {
return resp;
}
+function transformApiArgs(args) {
+ return _.mapValues(args, (v, k) => (k == 'conid' && v && volatileConnectionMap[v] ? volatileConnectionMap[v] : v));
+}
+
export async function apiCall(route: string, args: {} = undefined) {
if (apiLogging) {
console.log('>>> API CALL', route, args);
@@ -55,6 +77,8 @@ export async function apiCall(route: string, args: {} = undefined) {
return;
}
+ args = transformApiArgs(args);
+
const electron = getElectron();
if (electron) {
const resp = await electron.invoke(route.replace('/', '-'), args);