From 9826965803c9f8cda8cee9a5355c617241a2c62b Mon Sep 17 00:00:00 2001 From: antelle Date: Sun, 25 Apr 2021 18:08:16 +0200 Subject: [PATCH] session management in settings --- .../extension/browser-extension-connector.js | 14 ++++++ app/scripts/comp/extension/protocol-impl.js | 38 ++++++++++++++-- app/scripts/locales/base.json | 11 +++++ .../views/settings/settings-browser-view.js | 22 +++++++++- app/styles/areas/_settings.scss | 11 +++++ app/templates/settings/settings-browser.hbs | 43 +++++++++++++++++++ .../browser-extension-connector.js | 6 +++ 7 files changed, 140 insertions(+), 5 deletions(-) diff --git a/app/scripts/comp/extension/browser-extension-connector.js b/app/scripts/comp/extension/browser-extension-connector.js index fc592b24..8123152f 100644 --- a/app/scripts/comp/extension/browser-extension-connector.js +++ b/app/scripts/comp/extension/browser-extension-connector.js @@ -216,6 +216,20 @@ const BrowserExtensionConnector = { } this.sendSocketResult(socketId, result); + }, + + get sessions() { + return ProtocolImpl.sessions; + }, + + terminateConnection(connectionId) { + connectionId = +connectionId; + if (Launcher) { + const { ipcRenderer } = Launcher.electron(); + ipcRenderer.invoke('browserExtensionConnectorCloseSocket', connectionId); + } else { + ProtocolImpl.deleteConnection(connectionId); + } } }; diff --git a/app/scripts/comp/extension/protocol-impl.js b/app/scripts/comp/extension/protocol-impl.js index f0b929e0..d148d7a6 100644 --- a/app/scripts/comp/extension/protocol-impl.js +++ b/app/scripts/comp/extension/protocol-impl.js @@ -183,10 +183,13 @@ function checkContentRequestPermissions(request) { clearTimeout(inactivityTimer); RuntimeDataModel.extensionConnectConfig = extensionConnectView.config; client.permissions = extensionConnectView.config; + Events.emit('browser-extension-sessions-changed'); resolve(); }, cancel: () => { + client.permissionsDenied = true; clearTimeout(inactivityTimer); + Events.emit('browser-extension-sessions-changed'); reject(makeError(Errors.userRejected)); } }); @@ -242,7 +245,13 @@ const ProtocolHandlers = { const keys = tweetnaclBox.keyPair(); publicKey = kdbxweb.ByteUtils.base64ToBytes(publicKey); - connectedClients.set(clientId, { connection, publicKey, version, keys }); + const stats = { + connectedDate: new Date() + }; + + connectedClients.set(clientId, { connection, publicKey, version, keys, stats }); + + Events.emit('browser-extension-sessions-changed'); logger.info('New client key created', clientId, version); @@ -420,15 +429,22 @@ const ProtocolImpl = { }, cleanup() { + const wasNotEmpty = connectedClients.size; + connectedClients.clear(); + + if (wasNotEmpty) { + Events.emit('browser-extension-sessions-changed'); + } }, deleteConnection(connectionId) { - for (const client of connectedClients.values()) { + for (const [clientId, client] of connectedClients.entries()) { if (client.connection.connectionId === connectionId) { - connectedClients.delete(client); + connectedClients.delete(clientId); } } + Events.emit('browser-extension-sessions-changed'); }, errorToResponse(e, request) { @@ -449,6 +465,22 @@ const ProtocolImpl = { } catch (e) { return this.errorToResponse(e, request); } + }, + + get sessions() { + return [...connectedClients.entries()] + .map(([clientId, client]) => ({ + clientId, + connectionId: client.connection.connectionId, + appName: client.connection.appName, + extensionName: client.connection.extensionName, + connectedDate: client.stats.connectedDate, + passwordsRead: client.stats.passwordsRead, + passwordsWritten: client.stats.passwordsWritten, + permissions: client.permissions, + permissionsDenied: client.permissionsDenied + })) + .sort((x, y) => y.connectedDate - x.connectedDate); } }; diff --git a/app/scripts/locales/base.json b/app/scripts/locales/base.json index 6b8359b4..c3d58619 100644 --- a/app/scripts/locales/base.json +++ b/app/scripts/locales/base.json @@ -657,6 +657,17 @@ "setBrowserExtensionKPXCWarnHeader": "{} will stop working", "setBrowserExtensionKPXCWarnBody1": "Unfortunately it's not possible to connect one extension to several apps. If you connect the extension to KeeWeb, we'll overwrite its app association, which means that integration with {} will stop working. Even if you uncheck this checkbox, the association with {} won't be restored. To make it work again, configure browser integration in {} settings.", "setBrowserExtensionKPXCWarnBody2": "Configure the extension to use KeeWeb?", + "setBrowserSessions": "Sessions", + "setBrowserSessionsEmpty": "No connected sessions.", + "setBrowserSessionsIntro": "These extensions are now connected to KeeWeb.", + "setBrowserSessionsActiveTooltip": "Active session", + "setBrowserSessionsActiveText": "This session is active. It can exchange data with KeeWeb based on the following permissions:", + "setBrowserSessionsInactiveTooltip": "Inactive session", + "setBrowserSessionsInactiveText": "This session is inactive. The extension is connected to KeeWeb, however, it hasn't tried to exchange data. When the extension requests anything, you will be able to choose what you want to share.", + "setBrowserSessionsDeniedTooltip": "Access denied", + "setBrowserSessionsDeniedText": "This session is inactive. The extension is connected to KeeWeb, however, you denied access to data.", + "setBrowserConnectedDate": "Connected", + "setBrowserTerminateSession": "Terminate this session", "setDevicesTitle": "Devices", "setDevicesEnableUsb": "Enable interaction with USB devices", diff --git a/app/scripts/views/settings/settings-browser-view.js b/app/scripts/views/settings/settings-browser-view.js index e442f112..81f782f3 100644 --- a/app/scripts/views/settings/settings-browser-view.js +++ b/app/scripts/views/settings/settings-browser-view.js @@ -1,3 +1,4 @@ +import { Events } from 'framework/events'; import { View } from 'framework/views/view'; import template from 'templates/settings/settings-browser.hbs'; import { Features } from 'util/features'; @@ -11,20 +12,32 @@ import { SupportedExtensions } from 'comp/extension/browser-extension-connector'; import { Alerts } from 'comp/ui/alerts'; +import { DateFormat } from 'comp/i18n/date-format'; class SettingsBrowserView extends View { template = template; events = { 'change .check-enable-for-browser': 'changeEnableForBrowser', - 'change .settings__browser-focus-if-locked': 'changeFocusIfLocked' + 'change .settings__browser-focus-if-locked': 'changeFocusIfLocked', + 'click .settings__browser-btn-terminate-session': 'terminateSession' }; + constructor(model, options) { + super(model, options); + + this.listenTo(Events, 'browser-extension-sessions-changed', this.render); + } + render() { const data = { desktop: Features.isDesktop, icon: Features.browserIcon, - focusIfLocked: AppSettingsModel.extensionFocusIfLocked + focusIfLocked: AppSettingsModel.extensionFocusIfLocked, + sessions: BrowserExtensionConnector.sessions.map((session) => ({ + ...session, + connectedDate: DateFormat.dtStr(session.connectedDate) + })) }; if (Features.isDesktop) { data.extensionNames = ['KeeWeb Connect', 'KeePassXC-Browser']; @@ -103,6 +116,11 @@ class SettingsBrowserView extends View { AppSettingsModel.extensionFocusIfLocked = e.target.checked; this.render(); } + + terminateSession(e) { + const connectionId = e.target.dataset.connectionId; + BrowserExtensionConnector.terminateConnection(connectionId); + } } export { SettingsBrowserView }; diff --git a/app/styles/areas/_settings.scss b/app/styles/areas/_settings.scss index 7c0b667f..4bb3389d 100644 --- a/app/styles/areas/_settings.scss +++ b/app/styles/areas/_settings.scss @@ -314,6 +314,17 @@ padding: $base-padding; } } + &-extension-status { + display: inline-block; + width: 0; + height: 0; + border-radius: 100%; + border: 5px solid; + margin-right: 0.2em; + } + &-session-buttons { + margin-top: $base-padding-h; + } } &__donate-btn { background: #fff; diff --git a/app/templates/settings/settings-browser.hbs b/app/templates/settings/settings-browser.hbs index 9a2ffbb1..b66ec8d6 100644 --- a/app/templates/settings/settings-browser.hbs +++ b/app/templates/settings/settings-browser.hbs @@ -73,4 +73,47 @@ {{#if focusIfLocked}}checked{{/if}} /> + +

{{res 'setBrowserSessions'}}

+ {{#if sessions.length}} +

{{res 'setBrowserSessionsIntro'}}

+ + {{#each sessions as |session|}} +

+
+ {{session.extensionName}} ({{session.appName}}) +

+

{{res 'setBrowserConnectedDate'}}: {{session.connectedDate}}

+ {{#if session.permissions}} + {{res 'setBrowserSessionsActiveText'}} + {{else if session.permissionsDenied}} + {{res 'setBrowserSessionsDeniedText'}} + {{else}} + {{res 'setBrowserSessionsInactiveText'}} + {{/if}} +
+ +
+ {{/each}} + {{else}} +

{{res 'setBrowserSessionsEmpty'}}

+ {{/if}} diff --git a/desktop/scripts/ipc-handlers/browser-extension-connector.js b/desktop/scripts/ipc-handlers/browser-extension-connector.js index 2e039610..7539b00a 100644 --- a/desktop/scripts/ipc-handlers/browser-extension-connector.js +++ b/desktop/scripts/ipc-handlers/browser-extension-connector.js @@ -12,6 +12,7 @@ ipcMain.handle('browserExtensionConnectorStop', browserExtensionConnectorStop); ipcMain.handle('browserExtensionConnectorEnable', browserExtensionConnectorEnable); ipcMain.handle('browserExtensionConnectorSocketResult', browserExtensionConnectorSocketResult); ipcMain.handle('browserExtensionConnectorSocketEvent', browserExtensionConnectorSocketEvent); +ipcMain.handle('browserExtensionConnectorCloseSocket', browserExtensionConnectorCloseSocket); const logger = new Logger('browser-extension-connector'); @@ -105,6 +106,11 @@ function browserExtensionConnectorSocketEvent(e, data) { sendEventToAllSockets(data); } +function browserExtensionConnectorCloseSocket(e, socketId) { + const socket = connectedSockets.get(socketId); + socket?.destroy(); +} + function getBrowserExtensionSocketName(config) { const { username, uid } = os.userInfo(); if (process.platform === 'darwin') {