basic cloud signin workflow

This commit is contained in:
SPRINX0\prochazka
2025-05-16 12:19:26 +02:00
parent 4dc2627da2
commit 9329345d98
8 changed files with 101 additions and 18 deletions

View File

@@ -13,7 +13,8 @@ const {
} = require('../auth/authProvider'); } = require('../auth/authProvider');
const storage = require('./storage'); const storage = require('./storage');
const { decryptPasswordString } = require('../utility/crypting'); const { decryptPasswordString } = require('../utility/crypting');
const { createDbGateIdentitySession, getIdentitySigninUrl } = require('../utility/cloudIntf'); const { createDbGateIdentitySession, startCloudTokenChecking } = require('../utility/cloudIntf');
const socket = require('../utility/socket');
const logger = getLogger('auth'); const logger = getLogger('auth');
@@ -138,10 +139,11 @@ module.exports = {
createCloudLoginSession_meta: true, createCloudLoginSession_meta: true,
async createCloudLoginSession({ client }) { async createCloudLoginSession({ client }) {
const sid = await createDbGateIdentitySession(client); const res = await createDbGateIdentitySession(client);
return { startCloudTokenChecking(res.sid, token => {
url: getIdentitySigninUrl(sid), socket.emit('got-cloud-token', { token });
}; });
return res;
}, },
authMiddleware, authMiddleware,

View File

@@ -1,8 +1,11 @@
const axios = require('axios'); const axios = require('axios');
const { getExternalParamsWithLicense } = require('./authProxy'); const { getExternalParamsWithLicense } = require('./authProxy');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const logger = getLogger('cloudIntf');
const DBGATE_IDENTITY_URL = process.env.LOCAL_DBGATE_IDENTITY const DBGATE_IDENTITY_URL = process.env.LOCAL_DBGATE_IDENTITY
? 'http://localhost:3001' ? 'http://localhost:3103'
: process.env.DEVWEB || process.env.DEVMODE : process.env.DEVWEB || process.env.DEVMODE
? 'https://identity.dbgate.udolni.net' ? 'https://identity.dbgate.udolni.net'
: 'https://identity.dbgate.io'; : 'https://identity.dbgate.io';
@@ -21,14 +24,37 @@ async function createDbGateIdentitySession(client) {
}, },
getExternalParamsWithLicense() getExternalParamsWithLicense()
); );
return resp.data.sid; return {
sid: resp.data.sid,
url: `${DBGATE_IDENTITY_URL}/api/signin/${resp.data.sid}`,
};
} }
function getIdentitySigninUrl(sid) { function startCloudTokenChecking(sid, callback) {
return `${DBGATE_IDENTITY_URL}/signin/${sid}`; const started = Date.now();
const interval = setInterval(async () => {
if (Date.now() - started > 60 * 1000) {
clearInterval(interval);
return;
}
try {
const resp = await axios.default.get(
`${DBGATE_IDENTITY_URL}/api/get-token/${sid}`,
getExternalParamsWithLicense()
);
if (resp.data.status == 'ok') {
clearInterval(interval);
callback(resp.data.token);
}
} catch (err) {
logger.error(extractErrorLogData(err), 'Error checking cloud token');
}
}, 500);
} }
module.exports = { module.exports = {
createDbGateIdentitySession, createDbGateIdentitySession,
getIdentitySigninUrl, startCloudTokenChecking,
}; };

View File

@@ -14,7 +14,7 @@
// import { shouldWaitForElectronInitialize } from './utility/getElectron'; // import { shouldWaitForElectronInitialize } from './utility/getElectron';
import { subscribeConnectionPingers } from './utility/connectionsPinger'; import { subscribeConnectionPingers } from './utility/connectionsPinger';
import { subscribePermissionCompiler } from './utility/hasPermission'; import { subscribePermissionCompiler } from './utility/hasPermission';
import { apiCall, installNewVolatileConnectionListener } from './utility/api'; import { apiCall, installNewCloudTokenListener, installNewVolatileConnectionListener } from './utility/api';
import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders'; import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders';
import AppTitleProvider from './utility/AppTitleProvider.svelte'; import AppTitleProvider from './utility/AppTitleProvider.svelte';
import getElectron from './utility/getElectron'; import getElectron from './utility/getElectron';
@@ -51,6 +51,7 @@
subscribeConnectionPingers(); subscribeConnectionPingers();
subscribePermissionCompiler(); subscribePermissionCompiler();
installNewVolatileConnectionListener(); installNewVolatileConnectionListener();
installNewCloudTokenListener();
initializeAppUpdates(); initializeAppUpdates();
} }

View File

@@ -1,4 +1,5 @@
import { import {
cloudSigninToken,
currentDatabase, currentDatabase,
currentTheme, currentTheme,
emptyConnectionGroupNames, emptyConnectionGroupNames,
@@ -662,6 +663,15 @@ if (hasPermission('settings/change')) {
}); });
} }
registerCommand({
id: 'cloud.logout',
category: 'Cloud',
name: 'Logout',
onClick: () => {
cloudSigninToken.set(null);
},
});
registerCommand({ registerCommand({
id: 'file.exit', id: 'file.exit',
category: 'File', category: 'File',

View File

@@ -39,6 +39,7 @@
'icon minus-thick': 'mdi mdi-minus-thick', 'icon minus-thick': 'mdi mdi-minus-thick',
'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible', 'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible',
'icon cloud-upload': 'mdi mdi-cloud-upload', 'icon cloud-upload': 'mdi mdi-cloud-upload',
'icon cloud': 'mdi mdi-cloud',
'icon import': 'mdi mdi-application-import', 'icon import': 'mdi mdi-application-import',
'icon export': 'mdi mdi-application-export', 'icon export': 'mdi mdi-application-export',
'icon new-connection': 'mdi mdi-database-plus', 'icon new-connection': 'mdi mdi-database-plus',

View File

@@ -182,6 +182,8 @@ export const focusedConnectionOrDatabase = writable<{ conid: string; database?:
export const focusedTreeDbKey = writable<{ key: string; root: string; type: string; text: string }>(null); export const focusedTreeDbKey = writable<{ key: string; root: string; type: string; text: string }>(null);
export const cloudSigninToken = writableWithStorage(null, 'cloudSigninToken');
export const DEFAULT_OBJECT_SEARCH_SETTINGS = { export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
pureName: true, pureName: true,
schemaName: false, schemaName: false,

View File

@@ -14,6 +14,7 @@ import { batchDispatchCacheTriggers, dispatchCacheChange } from './cache';
import { isAdminPage, isOneOfPage } from './pageDefs'; import { isAdminPage, isOneOfPage } from './pageDefs';
import { openWebLink } from './simpleTools'; import { openWebLink } from './simpleTools';
import { serializeJsTypesReplacer } from 'dbgate-tools'; import { serializeJsTypesReplacer } from 'dbgate-tools';
import { cloudSigninToken } from '../stores';
export const strmid = uuidv1(); export const strmid = uuidv1();
@@ -279,6 +280,12 @@ export function installNewVolatileConnectionListener() {
}); });
} }
export function installNewCloudTokenListener() {
apiOn('got-cloud-token', async ({ token }) => {
cloudSigninToken.set(token);
});
}
export function getAuthCategory(config) { export function getAuthCategory(config) {
if (config.isBasicAuth) { if (config.isBasicAuth) {
return 'basic'; return 'basic';

View File

@@ -9,6 +9,7 @@
visibleHamburgerMenuWidget, visibleHamburgerMenuWidget,
lockedDatabaseMode, lockedDatabaseMode,
getCurrentConfig, getCurrentConfig,
cloudSigninToken,
} from '../stores'; } from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition'; import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import hasPermission from '../utility/hasPermission'; import hasPermission from '../utility/hasPermission';
@@ -18,6 +19,7 @@
import getElectron from '../utility/getElectron'; import getElectron from '../utility/getElectron';
let domSettings; let domSettings;
let domCloudAccount;
let domMainMenu; let domMainMenu;
const widgets = [ const widgets = [
@@ -61,9 +63,10 @@
title: 'Selected cell data detail view', title: 'Selected cell data detail view',
}, },
{ {
icon: 'icon app', icon: 'icon cloud',
name: 'app', name: 'cloud',
title: 'Application layers', title: 'DbGate Cloud',
isCloud: true,
}, },
{ {
icon: 'icon premium', icon: 'icon premium',
@@ -95,7 +98,26 @@
const rect = domSettings.getBoundingClientRect(); const rect = domSettings.getBoundingClientRect();
const left = rect.right; const left = rect.right;
const top = rect.bottom; const top = rect.bottom;
const items = [{ command: 'settings.show' }, { command: 'theme.changeTheme' }, { command: 'settings.commands' }]; const items = [
{ command: 'settings.show' },
{ command: 'theme.changeTheme' },
{ command: 'settings.commands' },
{
text: 'View applications',
onClick: () => {
$selectedWidget = 'app';
$visibleWidgetSideBar = true;
},
},
];
currentDropDownMenu.set({ left, top, items });
}
function handleCloudAccountMenu() {
const rect = domCloudAccount.getBoundingClientRect();
const left = rect.right;
const top = rect.bottom;
const items = [{ command: 'cloud.logout' }];
currentDropDownMenu.set({ left, top, items }); currentDropDownMenu.set({ left, top, items });
} }
@@ -121,6 +143,7 @@
{/if} {/if}
{#each widgets {#each widgets
.filter(x => x && hasPermission(`widgets/${x.name}`)) .filter(x => x && hasPermission(`widgets/${x.name}`))
.filter(x => !x.isCloud || $cloudSigninToken)
.filter(x => !x.isPremiumPromo || !isProApp()) as item} .filter(x => !x.isPremiumPromo || !isProApp()) as item}
<div <div
class="wrapper" class="wrapper"
@@ -148,9 +171,20 @@
<FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} /> <FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} />
</div> --> </div> -->
<div class="wrapper" on:click={handleOpenCloudLogin} data-testid="WidgetIconPanel_cloudAccount"> {#if $cloudSigninToken}
<FontIcon icon="icon cloud-account" /> <div
</div> class="wrapper"
on:click={handleCloudAccountMenu}
bind:this={domCloudAccount}
data-testid="WidgetIconPanel_cloudAccount"
>
<FontIcon icon="icon cloud-account-connected" />
</div>
{:else}
<div class="wrapper" on:click={handleOpenCloudLogin} data-testid="WidgetIconPanel_cloudAccount">
<FontIcon icon="icon cloud-account" />
</div>
{/if}
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings} data-testid="WidgetIconPanel_settings"> <div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings} data-testid="WidgetIconPanel_settings">
<FontIcon icon="icon settings" /> <FontIcon icon="icon settings" />