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);