mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-23 16:16:02 +00:00
ask password logic & modal
This commit is contained in:
@@ -2,6 +2,7 @@ const path = require('path');
|
|||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const { datadir, filesdir } = require('../utility/directories');
|
const { datadir, filesdir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
|
|||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
|
||||||
|
let volatileConnections = {};
|
||||||
|
|
||||||
function getNamedArgs() {
|
function getNamedArgs() {
|
||||||
const res = {};
|
const res = {};
|
||||||
for (let i = 0; i < process.argv.length; i++) {
|
for (let i = 0; i < process.argv.length; i++) {
|
||||||
@@ -126,6 +129,7 @@ function getPortalCollections() {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const portalConnections = getPortalCollections();
|
const portalConnections = getPortalCollections();
|
||||||
|
|
||||||
function getSingleDatabase() {
|
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,
|
save_meta: true,
|
||||||
async save(connection) {
|
async save(connection) {
|
||||||
if (portalConnections) return;
|
if (portalConnections) return;
|
||||||
@@ -258,6 +280,10 @@ module.exports = {
|
|||||||
|
|
||||||
async getCore({ conid, mask = false }) {
|
async getCore({ conid, mask = false }) {
|
||||||
if (!conid) return null;
|
if (!conid) return null;
|
||||||
|
const volatile = volatileConnections[conid];
|
||||||
|
if (volatile) {
|
||||||
|
return volatile;
|
||||||
|
}
|
||||||
if (portalConnections) {
|
if (portalConnections) {
|
||||||
const res = portalConnections.find(x => x._id == conid) || null;
|
const res = portalConnections.find(x => x._id == conid) || null;
|
||||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
|
|||||||
const diff2htmlPage = require('../utility/diff2htmlPage');
|
const diff2htmlPage = require('../utility/diff2htmlPage');
|
||||||
const processArgs = require('../utility/processArgs');
|
const processArgs = require('../utility/processArgs');
|
||||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||||
@@ -81,6 +82,9 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
const connection = await connections.getCore({ conid });
|
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], [
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||||
'--is-forked-api',
|
'--is-forked-api',
|
||||||
'--start-process',
|
'--start-process',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const lock = new AsyncLock();
|
|||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const processArgs = require('../utility/processArgs');
|
const processArgs = require('../utility/processArgs');
|
||||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
opened: [],
|
opened: [],
|
||||||
@@ -46,6 +47,9 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
const connection = await connections.getCore({ conid });
|
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], [
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||||
'--is-forked-api',
|
'--is-forked-api',
|
||||||
'--start-process',
|
'--start-process',
|
||||||
|
|||||||
9
packages/api/src/utility/exceptions.js
Normal file
9
packages/api/src/utility/exceptions.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class MissingCredentialsError {
|
||||||
|
constructor(detail) {
|
||||||
|
this.detail = detail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MissingCredentialsError,
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const getExpressPath = require('./getExpressPath');
|
const getExpressPath = require('./getExpressPath');
|
||||||
|
const { MissingCredentialsError } = require('./exceptions');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} route
|
* @param {string} route
|
||||||
@@ -37,6 +38,13 @@ module.exports = function useController(app, electron, route, controller) {
|
|||||||
if (data === undefined) return null;
|
if (data === undefined) return null;
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof MissingCredentialsError) {
|
||||||
|
return {
|
||||||
|
missingCredentials: true,
|
||||||
|
apiErrorMessage: 'Missing credentials',
|
||||||
|
detail: err.detail,
|
||||||
|
};
|
||||||
|
}
|
||||||
return { apiErrorMessage: err.message };
|
return { apiErrorMessage: err.message };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -69,7 +77,15 @@ module.exports = function useController(app, electron, route, controller) {
|
|||||||
res.json(data);
|
res.json(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
82
packages/web/src/modals/DatabaseLoginModal.svelte
Normal file
82
packages/web/src/modals/DatabaseLoginModal.svelte
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
let currentModalConid = null;
|
||||||
|
|
||||||
|
export function isDatabaseLoginVisible() {
|
||||||
|
return !!currentModalConid;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
|
import FormPasswordField from '../forms/FormPasswordField.svelte';
|
||||||
|
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
|
import { apiCall, setVolatileConnectionRemapping } from '../utility/api';
|
||||||
|
|
||||||
|
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
|
||||||
|
export let conid;
|
||||||
|
export let passwordMode;
|
||||||
|
|
||||||
|
const values = writable({});
|
||||||
|
let connection;
|
||||||
|
|
||||||
|
currentModalConid = conid;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
connection = await getConnectionInfo({ conid });
|
||||||
|
if (passwordMode == 'askPassword') {
|
||||||
|
$values = {
|
||||||
|
...$values,
|
||||||
|
user: connection.user,
|
||||||
|
server: connection.server,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
currentModalConid = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleSubmit(ev) {
|
||||||
|
const con = await apiCall('connections/save-volatile', {
|
||||||
|
conid,
|
||||||
|
user: ev.detail.user,
|
||||||
|
password: ev.detail.password,
|
||||||
|
});
|
||||||
|
setVolatileConnectionRemapping(conid, con._id);
|
||||||
|
closeCurrentModal();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProviderCore {values}>
|
||||||
|
<ModalBase {...$$restProps} simple>
|
||||||
|
<svelte:fragment slot="header">Database Log In</svelte:fragment>
|
||||||
|
|
||||||
|
<FormTextField label="Server" name="server" disabled />
|
||||||
|
<FormTextField
|
||||||
|
label="Username"
|
||||||
|
name="user"
|
||||||
|
autocomplete="username"
|
||||||
|
disabled={passwordMode == 'askPassword'}
|
||||||
|
focused={passwordMode == 'askUser'}
|
||||||
|
/>
|
||||||
|
<FormPasswordField
|
||||||
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
focused={passwordMode == 'askPassword'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<FormSubmit value="Connect" on:click={handleSubmit} />
|
||||||
|
<FormStyledButton value="Cancel" on:click={closeCurrentModal} />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProviderCore>
|
||||||
@@ -28,8 +28,12 @@
|
|||||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
$: defaultDatabase = $values.defaultDatabase;
|
$: defaultDatabase = $values.defaultDatabase;
|
||||||
|
|
||||||
$: showUser = driver?.showConnectionField('user', $values);
|
$: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
|
||||||
$: showPassword = driver?.showConnectionField('password', $values);
|
$: 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);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@
|
|||||||
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
|
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !disabledFields.includes('password') && showPassword}
|
{#if !disabledFields.includes('password') && showPasswordMode}
|
||||||
<FormSelectField
|
<FormSelectField
|
||||||
label="Password mode"
|
label="Password mode"
|
||||||
isNative
|
isNative
|
||||||
@@ -169,6 +173,8 @@
|
|||||||
options={[
|
options={[
|
||||||
{ value: 'saveEncrypted', label: 'Save and encrypt' },
|
{ value: 'saveEncrypted', label: 'Save and encrypt' },
|
||||||
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
|
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
|
||||||
|
{ value: 'askPassword', label: "Don't save, ask for password" },
|
||||||
|
{ value: 'askUser', label: "Don't save, ask for login and password" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import getElectron from './getElectron';
|
|||||||
// import socket from './socket';
|
// import socket from './socket';
|
||||||
import { showSnackbarError } from '../utility/snackbar';
|
import { showSnackbarError } from '../utility/snackbar';
|
||||||
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
let eventSource;
|
let eventSource;
|
||||||
let apiLogging = false;
|
let apiLogging = false;
|
||||||
@@ -12,6 +15,8 @@ let apiLogging = false;
|
|||||||
let apiDisabled = false;
|
let apiDisabled = false;
|
||||||
const disabledOnOauth = isOauthCallback();
|
const disabledOnOauth = isOauthCallback();
|
||||||
|
|
||||||
|
const volatileConnectionMap = {};
|
||||||
|
|
||||||
export function disableApi() {
|
export function disableApi() {
|
||||||
apiDisabled = true;
|
apiDisabled = true;
|
||||||
}
|
}
|
||||||
@@ -20,6 +25,10 @@ export function enableApi() {
|
|||||||
apiDisabled = false;
|
apiDisabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
|
||||||
|
volatileConnectionMap[existingConnectionId] = volatileConnectionId;
|
||||||
|
}
|
||||||
|
|
||||||
function wantEventSource() {
|
function wantEventSource() {
|
||||||
if (!eventSource) {
|
if (!eventSource) {
|
||||||
eventSource = new EventSource(`${resolveApi()}/stream`);
|
eventSource = new EventSource(`${resolveApi()}/stream`);
|
||||||
@@ -32,7 +41,16 @@ function processApiResponse(route, args, resp) {
|
|||||||
// console.log('<<< API RESPONSE', 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);
|
showSnackbarError('API error:' + resp?.apiErrorMessage);
|
||||||
return {
|
return {
|
||||||
errorMessage: resp.apiErrorMessage,
|
errorMessage: resp.apiErrorMessage,
|
||||||
@@ -42,6 +60,10 @@ function processApiResponse(route, args, resp) {
|
|||||||
return 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) {
|
export async function apiCall(route: string, args: {} = undefined) {
|
||||||
if (apiLogging) {
|
if (apiLogging) {
|
||||||
console.log('>>> API CALL', route, args);
|
console.log('>>> API CALL', route, args);
|
||||||
@@ -55,6 +77,8 @@ export async function apiCall(route: string, args: {} = undefined) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = transformApiArgs(args);
|
||||||
|
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron) {
|
if (electron) {
|
||||||
const resp = await electron.invoke(route.replace('/', '-'), args);
|
const resp = await electron.invoke(route.replace('/', '-'), args);
|
||||||
|
|||||||
Reference in New Issue
Block a user