special pages workflow changed

This commit is contained in:
Jan Prochazka
2024-10-21 17:36:46 +02:00
parent 967615b6e5
commit 32c7919885
14 changed files with 339 additions and 109 deletions

View File

@@ -36,8 +36,9 @@ function authMiddleware(req, res, next) {
'/auth/login', '/auth/login',
'/auth/redirect', '/auth/redirect',
'/stream', '/stream',
'storage/get-connections-for-login-page', '/storage/get-connections-for-login-page',
'auth/get-providers', '/storage/set-admin-password',
'/auth/get-providers',
'/connections/dblogin-web', '/connections/dblogin-web',
'/connections/dblogin-app', '/connections/dblogin-app',
'/connections/dblogin-auth', '/connections/dblogin-auth',
@@ -69,6 +70,7 @@ function authMiddleware(req, res, next) {
return next(); return next();
} catch (err) { } catch (err) {
if (skipAuth) { if (skipAuth) {
req.isInvalidToken = true;
return next(); return next();
} }

View File

@@ -60,6 +60,14 @@ module.exports = {
const checkedLicense = storageConnectionError ? null : await checkLicense(); const checkedLicense = storageConnectionError ? null : await checkLicense();
const isLicenseValid = checkedLicense?.status == 'ok'; const isLicenseValid = checkedLicense?.status == 'ok';
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl(); const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
const isAdminPasswordMissing = !!(
process.env.STORAGE_DATABASE &&
!process.env.ADMIN_PASSWORD &&
!process.env.BASIC_AUTH &&
!adminConfig?.adminPasswordState
);
return { return {
runAsPortal: !!connections.portalConnections, runAsPortal: !!connections.portalConnections,
@@ -87,6 +95,9 @@ module.exports = {
!process.env.BASIC_AUTH && !process.env.BASIC_AUTH &&
checkedLicense?.type == 'premium' checkedLicense?.type == 'premium'
), ),
isAdminPasswordMissing,
isInvalidToken: req.isInvalidToken,
adminPasswordState: adminConfig?.adminPasswordState,
storageDatabase: process.env.STORAGE_DATABASE, storageDatabase: process.env.STORAGE_DATABASE,
logsFilePath: getLogsFilePath(), logsFilePath: getLogsFilePath(),
connectionsFilePath: path.join(datadir(), 'connections.jsonl'), connectionsFilePath: path.join(datadir(), 'connections.jsonl'),

View File

@@ -20,5 +20,10 @@ module.exports = {
getStorageConnectionError() { getStorageConnectionError() {
return null; return null;
} },
readConfig_meta: true,
async readConfig({ group }) {
return {};
},
}; };

View File

@@ -2,7 +2,17 @@ const fs = require('fs');
const template = fs.readFileSync('./index.html.tpl', 'utf-8'); const template = fs.readFileSync('./index.html.tpl', 'utf-8');
for (const page of ['', 'not-logged', 'error', 'admin-login', 'login', 'admin', 'license']) { for (const page of [
'',
'not-logged',
'error',
'admin-login',
'login',
'admin',
'license',
'set-admin-password',
'redirect',
]) {
const text = template.replace(/{{page}}/g, page); const text = template.replace(/{{page}}/g, page);
fs.writeFileSync(`public/${page || 'index'}.html`, text); fs.writeFileSync(`public/${page || 'index'}.html`, text);
} }

View File

@@ -38,7 +38,7 @@
// console.log('************** LOADING API'); // console.log('************** LOADING API');
const config = await getConfig(); const config = await getConfig();
await handleAuthOnStartup(config, isAdminPage); await handleAuthOnStartup(config);
const connections = await apiCall('connections/list'); const connections = await apiCall('connections/list');
const settings = await getSettings(); const settings = await getSettings();

View File

@@ -11,6 +11,8 @@
import getElectron from './utility/getElectron'; import getElectron from './utility/getElectron';
import { openWebLink } from './utility/exportFileTools'; import { openWebLink } from './utility/exportFileTools';
import SpecialPageLayout from './widgets/SpecialPageLayout.svelte'; import SpecialPageLayout from './widgets/SpecialPageLayout.svelte';
import hasPermission from './utility/hasPermission';
import ErrorInfo from './elements/ErrorInfo.svelte';
const config = useConfig(); const config = useConfig();
const values = writable({ amoid: null, databaseServer: null }); const values = writable({ amoid: null, databaseServer: null });
@@ -25,89 +27,95 @@
errorMessage = 'Your license is expired'; errorMessage = 'Your license is expired';
expiredMessageSet = true; expiredMessageSet = true;
} }
// $: console.log('CONFIG', $config);
</script> </script>
<FormProviderCore {values}> <FormProviderCore {values}>
<SpecialPageLayout> <SpecialPageLayout>
<div class="heading">License</div> {#if getElectron() || ($config?.storageDatabase && hasPermission('admin/license'))}
<FormTextAreaField label="Enter your license key" name="licenseKey" rows={5} /> <div class="heading">License</div>
<FormTextAreaField label="Enter your license key" name="licenseKey" rows={5} />
<div class="submit">
<FormSubmit
value="Save license"
on:click={async e => {
sessionStorage.setItem('continueTrialConfirmed', '1');
const { licenseKey } = e.detail;
const resp = await apiCall('config/save-license-key', { licenseKey });
if (resp?.status == 'ok') {
internalRedirectTo('/index.html');
} else {
errorMessage = resp?.errorMessage || 'Error saving license key';
}
}}
/>
</div>
{#if !isExpired && trialDaysLeft == null}
<div class="submit"> <div class="submit">
<FormStyledButton <FormSubmit
value="Start 30-day trial" value="Save license"
on:click={async e => { on:click={async e => {
errorMessage = ''; sessionStorage.setItem('continueTrialConfirmed', '1');
const license = await apiCall('config/start-trial'); const { licenseKey } = e.detail;
if (license?.status == 'ok') { const resp = await apiCall('config/save-license-key', { licenseKey });
sessionStorage.setItem('continueTrialConfirmed', '1'); if (resp?.status == 'ok') {
internalRedirectTo('/index.html'); internalRedirectTo('/index.html');
} else { } else {
errorMessage = license?.errorMessage || 'Error starting trial'; errorMessage = resp?.errorMessage || 'Error saving license key';
} }
}} }}
/> />
</div> </div>
{/if}
{#if trialDaysLeft > 0} {#if !isExpired && trialDaysLeft == null}
<div class="submit">
<FormStyledButton
value="Start 30-day trial"
on:click={async e => {
errorMessage = '';
const license = await apiCall('config/start-trial');
if (license?.status == 'ok') {
sessionStorage.setItem('continueTrialConfirmed', '1');
internalRedirectTo('/index.html');
} else {
errorMessage = license?.errorMessage || 'Error starting trial';
}
}}
/>
</div>
{/if}
{#if trialDaysLeft > 0}
<div class="submit">
<FormStyledButton
value={`Continue trial (${trialDaysLeft} days left)`}
on:click={async e => {
sessionStorage.setItem('continueTrialConfirmed', '1');
internalRedirectTo('/index.html');
}}
/>
</div>
{/if}
<div class="submit"> <div class="submit">
<FormStyledButton <FormStyledButton
value={`Continue trial (${trialDaysLeft} days left)`} value="Purchase DbGate Premium"
on:click={async e => { on:click={async e => {
sessionStorage.setItem('continueTrialConfirmed', '1'); openWebLink(
internalRedirectTo('/index.html'); `https://auth.dbgate.eu/create-checkout-session-simple?source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}`
);
}} }}
/> />
</div> </div>
{/if}
<div class="submit"> {#if getElectron()}
<FormStyledButton <div class="submit">
value="Purchase DbGate Premium" <FormStyledButton
on:click={async e => { value="Exit"
openWebLink( on:click={e => {
`https://auth.dbgate.eu/create-checkout-session-simple?source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}` getElectron().send('quit-app');
); }}
}} />
/> </div>
</div> {/if}
{#if getElectron()} {#if errorMessage}
<div class="submit"> <div class="error">{errorMessage}</div>
<FormStyledButton {/if}
value="Exit"
on:click={e => { <div class="purchase-info">
getElectron().send('quit-app'); For more info about DbGate licensing, you could visit <Link href="https://dbgate.eu/">dbgate.eu</Link> web or contact
}} us at <Link href="mailto:sales@dbgate.eu">sales@dbgate.eu</Link>
/>
</div> </div>
{:else}
<ErrorInfo message="License for DbGate is not valid. Please contact administrator." />
{/if} {/if}
{#if errorMessage}
<div class="error">{errorMessage}</div>
{/if}
<div class="purchase-info">
For more info about DbGate licensing, you could visit <Link href="https://dbgate.eu/">dbgate.eu</Link> web or contact
us at <Link href="mailto:sales@dbgate.eu">sales@dbgate.eu</Link>
</div>
</SpecialPageLayout> </SpecialPageLayout>
</FormProviderCore> </FormProviderCore>

View File

@@ -0,0 +1,3 @@
<script lang='ts'>
</script>

View File

@@ -0,0 +1,79 @@
<script lang="ts">
import { writable } from 'svelte/store';
import FormCheckboxField from './forms/FormCheckboxField.svelte';
import FormPasswordField from './forms/FormPasswordField.svelte';
import FormSubmit from './forms/FormSubmit.svelte';
import SpecialPageLayout from './widgets/SpecialPageLayout.svelte';
import FormProviderCore from './forms/FormProviderCore.svelte';
import { apiCall } from './utility/api';
import { useConfig } from './utility/metadataLoaders';
import ErrorInfo from './elements/ErrorInfo.svelte';
import { internalRedirectTo } from './clientAuth';
const values = writable({ denyUseAdminPassword: false });
const config = useConfig();
let error;
</script>
<SpecialPageLayout>
<FormProviderCore {values}>
<div class="heading">Set admin password</div>
<div class="text">
Please set password for DbGate administrator account. If you lose this paassword, you can change it later in
DbGate internal database, in table config.
</div>
<FormCheckboxField label="Don't use admin password" name="denyUseAdminPassword" />
{#if $values?.denyUseAdminPassword}
<div class="text">
You have selected to not use admin password. You can change this setting later in DbGate internal database, in
table config. Please assign to some regular user admin role, to be able to perform admin tasks.
</div>
{:else}
{#if $config?.adminPasswordState == 'set'}
<FormPasswordField label="Current password" name="oldPassword" autocomplete="current-password" saveOnInput />
{/if}
<FormPasswordField label="New password" name="newPassword" autocomplete="current-password" saveOnInput />
<FormPasswordField label="Repeat password" name="repeatPassword" autocomplete="current-password" saveOnInput />
{/if}
{#if error}
<ErrorInfo message={error} />
{/if}
<div class="submit">
<FormSubmit
value="Set password"
on:click={async e => {
const resp = await apiCall('storage/set-admin-password', e.detail);
if (resp?.status == 'error') {
error = resp?.errorMessage;
return;
}
internalRedirectTo('/admin.html');
}}
/>
</div>
</FormProviderCore>
</SpecialPageLayout>
<style>
.heading {
text-align: center;
margin: 1em;
font-size: xx-large;
}
.submit {
margin: var(--dim-large-form-margin);
display: flex;
}
.text {
margin-left: var(--dim-large-form-margin);
margin-right: var(--dim-large-form-margin);
}
</style>

View File

@@ -116,48 +116,120 @@ export function handleOauthCallback() {
return false; return false;
} }
export async function handleAuthOnStartup(config, isAdminPage = false) { export async function handleAuthOnStartup(config) {
if (config.configurationError) { const page = window['dbgate_page'];
internalRedirectTo(`/error.html`);
return;
}
if (!config.isLicenseValid) { function checkConfigError() {
if (config.storageDatabase || getElectron()) { if (config.configurationError) {
internalRedirectTo(`/license.html`);
} else {
internalRedirectTo(`/error.html`); internalRedirectTo(`/error.html`);
return true;
} }
} }
if ( function checkInvalidLicense() {
config.trialDaysLeft != null && if (!config.isLicenseValid) {
config.trialDaysLeft <= 14 && if (config.storageDatabase || getElectron()) {
!sessionStorage.getItem('continueTrialConfirmed') && internalRedirectTo(`/license.html`);
getElectron() } else {
) { internalRedirectTo(`/error.html`);
internalRedirectTo(`/license.html`); }
return true;
}
} }
if (getAuthCategory(config) == 'admin') { function checkTrialDaysLeft() {
if (localStorage.getItem('adminAccessToken')) { if (
return; config.trialDaysLeft != null &&
config.trialDaysLeft <= 14 &&
!sessionStorage.getItem('continueTrialConfirmed') &&
getElectron()
) {
internalRedirectTo(`/license.html`);
return true;
}
}
function checkLoggedUser() {
if (getAuthCategory(config) == 'admin') {
if (!config.isInvalidToken && localStorage.getItem('adminAccessToken')) {
return false;
}
redirectToAdminLogin();
return true;
} }
redirectToAdminLogin(); if (getAuthCategory(config) == 'token') {
return; if (!config.isInvalidToken && localStorage.getItem('accessToken')) {
return false;
}
redirectToLogin(config);
return true;
}
} }
// if (config.oauth) { function checkAdminPasswordSet() {
// console.log('OAUTH callback URL:', location.origin + location.pathname); if (config.isAdminPasswordMissing) {
internalRedirectTo(`/set-admin-password.html`);
return true;
}
}
if (page == 'error') return;
if (checkConfigError()) return;
if (page == 'set-admin-password') return;
if (checkAdminPasswordSet()) return;
if (page == 'login' || page == 'admin-login' || page == 'not-logged') return;
if (checkLoggedUser()) return;
if (page == 'license') return;
if (checkTrialDaysLeft()) return;
if (checkInvalidLicense()) return;
// if (config.configurationError) {
// internalRedirectTo(`/error.html`);
// return;
// } // }
if (getAuthCategory(config) == 'token') {
if (localStorage.getItem('accessToken')) {
return;
}
redirectToLogin(config); // if (!config.isLicenseValid) {
} // if (config.storageDatabase || getElectron()) {
// internalRedirectTo(`/license.html`);
// } else {
// internalRedirectTo(`/error.html`);
// }
// }
// if (
// config.trialDaysLeft != null &&
// config.trialDaysLeft <= 14 &&
// !sessionStorage.getItem('continueTrialConfirmed') &&
// getElectron()
// ) {
// internalRedirectTo(`/license.html`);
// }
// if (getAuthCategory(config) == 'admin') {
// if (localStorage.getItem('adminAccessToken')) {
// return;
// }
// redirectToAdminLogin();
// return;
// }
// // if (config.oauth) {
// // console.log('OAUTH callback URL:', location.origin + location.pathname);
// // }
// if (getAuthCategory(config) == 'token') {
// if (localStorage.getItem('accessToken')) {
// return;
// }
// redirectToLogin(config);
// }
} }
export async function redirectToAdminLogin() { export async function redirectToAdminLogin() {

View File

@@ -36,7 +36,7 @@ import runCommand from './runCommand';
import { openWebLink } from '../utility/exportFileTools'; import { openWebLink } from '../utility/exportFileTools';
import { getSettings } from '../utility/metadataLoaders'; import { getSettings } from '../utility/metadataLoaders';
import { isMac, switchCurrentDatabase } from '../utility/common'; import { isMac, switchCurrentDatabase } from '../utility/common';
import { doLogout, internalRedirectTo } from '../clientAuth'; import { doLogout } from '../clientAuth';
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte'; import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
import UploadErrorModal from '../modals/UploadErrorModal.svelte'; import UploadErrorModal from '../modals/UploadErrorModal.svelte';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte'; import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';

View File

@@ -8,6 +8,8 @@ import LoginPage from './LoginPage.svelte';
import NotLoggedPage from './NotLoggedPage.svelte'; import NotLoggedPage from './NotLoggedPage.svelte';
import ErrorPage from './ErrorPage.svelte'; import ErrorPage from './ErrorPage.svelte';
import EnterLicensePage from './EnterLicensePage.svelte'; import EnterLicensePage from './EnterLicensePage.svelte';
import SetAdminPasswordPage from './SetAdminPasswordPage.svelte';
import RedirectPage from './RedirectPage.svelte';
const isOauthCallback = handleOauthCallback(); const isOauthCallback = handleOauthCallback();
@@ -43,11 +45,20 @@ function createApp() {
isAdminPage: true, isAdminPage: true,
}, },
}); });
case 'redirect':
return new RedirectPage({
target: document.body,
});
case 'not-logged': case 'not-logged':
return new NotLoggedPage({ return new NotLoggedPage({
target: document.body, target: document.body,
props: {}, props: {},
}); });
case 'set-admin-password':
return new SetAdminPasswordPage({
target: document.body,
props: {},
});
case 'admin': case 'admin':
return new App({ return new App({
target: document.body, target: document.body,

View File

@@ -4,7 +4,7 @@ import { writable } from 'svelte/store';
import getElectron from './getElectron'; import getElectron from './getElectron';
// import socket from './socket'; // import socket from './socket';
import { showSnackbarError } from '../utility/snackbar'; import { showSnackbarError } from '../utility/snackbar';
import { isOauthCallback, redirectToAdminLogin, redirectToLogin } from '../clientAuth'; import { handleAuthOnStartup, isOauthCallback, redirectToAdminLogin, redirectToLogin } from '../clientAuth';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte'; import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
import _ from 'lodash'; import _ from 'lodash';
@@ -144,17 +144,23 @@ export function transformApiArgsInv(args) {
}); });
} }
export async function apiCall(route: string, args: {} = undefined) { export async function apiCall(
route: string,
args: {} = undefined,
options: { skipDisableChecks: boolean } = undefined
) {
if (apiLogging) { if (apiLogging) {
console.log('>>> API CALL', route, args); console.log('>>> API CALL', route, args);
} }
if (apiDisabled) { if (!options?.skipDisableChecks) {
console.log('API disabled!!', route); if (apiDisabled) {
return; console.log('API disabled!!', route);
} return;
if (disabledOnOauth && route != 'auth/oauth-token') { }
console.log('API disabled because oauth callback!!', route); if (disabledOnOauth && route != 'auth/oauth-token') {
return; console.log('API disabled because oauth callback!!', route);
return;
}
} }
args = transformApiArgs(args); args = transformApiArgs(args);
@@ -180,12 +186,15 @@ export async function apiCall(route: string, args: {} = undefined) {
disableApi(); disableApi();
console.log('Disabling API', route); console.log('Disabling API', route);
if (page != 'login' && page != 'admin-login' && page != 'not-logged') { if (page != 'login' && page != 'admin-login' && page != 'not-logged') {
// unauthorized const config = await apiCall('config/get', {}, { skipDisableChecks: true });
if (page == 'admin') { await handleAuthOnStartup(config);
redirectToAdminLogin();
} else { // // unauthorized
redirectToLogin(); // if (page == 'admin') {
} // redirectToAdminLogin();
// } else {
// redirectToLogin();
// }
} }
return; return;
} }

View File

@@ -18,3 +18,7 @@ export function subscribePermissionCompiler() {
// console.log('COMPILED PERMS', compiled); // console.log('COMPILED PERMS', compiled);
}); });
} }
export function setConfigForPermissions(config) {
compiled = compilePermissions(config?.permissions || {});
}

View File

@@ -1,9 +1,25 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { getConfig } from '../utility/metadataLoaders';
import { handleAuthOnStartup } from '../clientAuth';
import { setConfigForPermissions } from '../utility/hasPermission';
async function loadApi() {
try {
const config = await getConfig();
setConfigForPermissions(config);
await handleAuthOnStartup(config);
} catch (e) {
console.log('Error calling API, trying again in 1s');
setTimeout(loadApi, 1000);
}
}
onMount(() => { onMount(() => {
const removed = document.getElementById('starting_dbgate_zero'); const removed = document.getElementById('starting_dbgate_zero');
if (removed) removed.remove(); if (removed) removed.remove();
loadApi();
}); });
</script> </script>