ask password logic & modal

This commit is contained in:
Jan Prochazka 2022-12-25 10:21:19 +01:00
parent fa13990189
commit d66fc06403
8 changed files with 176 additions and 5 deletions

View File

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

View File

@ -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',

View File

@ -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',

View File

@ -0,0 +1,9 @@
class MissingCredentialsError {
constructor(detail) {
this.detail = detail;
}
}
module.exports = {
MissingCredentialsError,
};

View File

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

View 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>

View File

@ -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);
</script>
@ -159,7 +163,7 @@
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
{/if}
{#if !disabledFields.includes('password') && showPassword}
{#if !disabledFields.includes('password') && showPasswordMode}
<FormSelectField
label="Password mode"
isNative
@ -169,6 +173,8 @@
options={[
{ value: 'saveEncrypted', label: 'Save and encrypt' },
{ 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}

View File

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