mirror of https://github.com/keeweb/keeweb.git
extension connection dialog
This commit is contained in:
parent
b2e828e7ef
commit
15936fcef0
|
@ -7,7 +7,7 @@ import { Features } from 'util/features';
|
||||||
|
|
||||||
const WebConnectionInfo = {
|
const WebConnectionInfo = {
|
||||||
connectionId: 1,
|
connectionId: 1,
|
||||||
extensionName: 'keeweb-connect',
|
extensionName: 'KeeWeb Connect',
|
||||||
supportsNotifications: true
|
supportsNotifications: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { Alerts } from 'comp/ui/alerts';
|
||||||
import { Locale } from 'util/locale';
|
import { Locale } from 'util/locale';
|
||||||
import { RuntimeInfo } from 'const/runtime-info';
|
import { RuntimeInfo } from 'const/runtime-info';
|
||||||
import { KnownAppVersions } from 'const/known-app-versions';
|
import { KnownAppVersions } from 'const/known-app-versions';
|
||||||
|
import { ExtensionConnectView } from 'views/extension/extension-connect-view';
|
||||||
|
import { RuntimeDataModel } from 'models/runtime-data-model';
|
||||||
|
|
||||||
const KeeWebAssociationId = 'KeeWeb';
|
const KeeWebAssociationId = 'KeeWeb';
|
||||||
const KeeWebHash = '398d9c782ec76ae9e9877c2321cbda2b31fc6d18ccf0fed5ca4bd746bab4d64a'; // sha256('KeeWeb')
|
const KeeWebHash = '398d9c782ec76ae9e9877c2321cbda2b31fc6d18ccf0fed5ca4bd746bab4d64a'; // sha256('KeeWeb')
|
||||||
|
@ -136,7 +138,7 @@ function checkContentRequestPermissions(request) {
|
||||||
ensureAtLeastOneFileIsOpen();
|
ensureAtLeastOneFileIsOpen();
|
||||||
|
|
||||||
const client = getClient(request);
|
const client = getClient(request);
|
||||||
if (client.authorized) {
|
if (client.permissions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,31 +149,41 @@ function checkContentRequestPermissions(request) {
|
||||||
|
|
||||||
focusKeeWeb();
|
focusKeeWeb();
|
||||||
|
|
||||||
// TODO: make a proper dialog here instead of a simple question
|
const config = RuntimeDataModel.extensionConnectConfig;
|
||||||
|
const files = appModel.files.map((f) => ({
|
||||||
if (Launcher) {
|
id: f.id,
|
||||||
Alerts.yesno({
|
name: f.name,
|
||||||
header: 'Extension connection',
|
checked: !config || config.allFiles || config.files.includes(f.id)
|
||||||
body: 'Allow this extension to connect?',
|
}));
|
||||||
success: () => {
|
if (!files.some((f) => f.checked)) {
|
||||||
resolve();
|
for (const f of files) {
|
||||||
},
|
f.checked = true;
|
||||||
cancel: () => reject(makeError(Errors.userRejected))
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// it's 'confirm' here because other browser extensions can't interact with browser alerts
|
|
||||||
// while they can easily press a button on our alert
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
const allowed = confirm('Allow this extension to connect?');
|
|
||||||
if (allowed) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject(makeError(Errors.userRejected));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extensionConnectView = new ExtensionConnectView({
|
||||||
|
extensionName: `${client.connection.extensionName} (${client.connection.appName})`,
|
||||||
|
identityVerified: !Launcher,
|
||||||
|
files,
|
||||||
|
allFiles: config?.allFiles ?? true,
|
||||||
|
askGet: config?.askGet || 'multiple'
|
||||||
|
});
|
||||||
|
Alerts.alert({
|
||||||
|
header: Locale.extensionConnectHeader,
|
||||||
|
icon: 'exchange-alt',
|
||||||
|
view: extensionConnectView,
|
||||||
|
wide: true,
|
||||||
|
opaque: true,
|
||||||
|
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
|
||||||
|
success: () => {
|
||||||
|
RuntimeDataModel.extensionConnectConfig = extensionConnectView.config;
|
||||||
|
client.permissions = extensionConnectView.config;
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
cancel: () => reject(makeError(Errors.userRejected))
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
client.authorized = true;
|
|
||||||
Launcher.hideApp();
|
Launcher.hideApp();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -186,7 +198,7 @@ function getVersion(request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isKeeWebConnect(request) {
|
function isKeeWebConnect(request) {
|
||||||
return getClient(request).connection.extensionName === 'keeweb-connect';
|
return getClient(request).connection.extensionName === 'KeeWeb Connect';
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusKeeWeb() {
|
function focusKeeWeb() {
|
||||||
|
|
|
@ -17,6 +17,12 @@ const Alerts = {
|
||||||
return Locale.alertYes;
|
return Locale.alertYes;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
allow: {
|
||||||
|
result: 'yes',
|
||||||
|
get title() {
|
||||||
|
return Locale.alertAllow;
|
||||||
|
}
|
||||||
|
},
|
||||||
no: {
|
no: {
|
||||||
result: '',
|
result: '',
|
||||||
get title() {
|
get title() {
|
||||||
|
@ -28,6 +34,12 @@ const Alerts = {
|
||||||
get title() {
|
get title() {
|
||||||
return Locale.alertCancel;
|
return Locale.alertCancel;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
deny: {
|
||||||
|
result: '',
|
||||||
|
get title() {
|
||||||
|
return Locale.alertDeny;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,8 @@
|
||||||
"alertCopy": "Copy",
|
"alertCopy": "Copy",
|
||||||
"alertClose": "Close",
|
"alertClose": "Close",
|
||||||
"alertDoNotAsk": "Don't ask me anymore",
|
"alertDoNotAsk": "Don't ask me anymore",
|
||||||
|
"alertAllow": "Allow",
|
||||||
|
"alertDeny": "Deny",
|
||||||
|
|
||||||
"appBeta": "WARNING: beta version, only for preview",
|
"appBeta": "WARNING: beta version, only for preview",
|
||||||
|
|
||||||
|
@ -755,5 +757,14 @@
|
||||||
|
|
||||||
"extensionErrorNoOpenFiles": "No open files",
|
"extensionErrorNoOpenFiles": "No open files",
|
||||||
"extensionErrorUserRejected": "The request was denied",
|
"extensionErrorUserRejected": "The request was denied",
|
||||||
"extensionErrorAlertDisplayed": "Cannot ask user a question now, please try again"
|
"extensionErrorAlertDisplayed": "Cannot ask user a question now, please try again",
|
||||||
|
"extensionConnectHeader": "Extension data exchange",
|
||||||
|
"extensionConnectIntro": "A browser extension that identifies itself as {} tries to exchange data with KeeWeb.",
|
||||||
|
"extensionConnectUnknownActivity": "KeeWeb doesn't verify that the connected application is what it pretends to be. Approve the request only if you recognize this activity.",
|
||||||
|
"extensionConnectFiles": "In this session, allow access to files:",
|
||||||
|
"extensionConnectAllFiles": "All other files",
|
||||||
|
"extensionConnectAskGet": "Ask before returning passwords to the extension:",
|
||||||
|
"extensionConnectAskGetMultiple": "if there's more than one match",
|
||||||
|
"extensionConnectAskGetAlways": "always",
|
||||||
|
"extensionConnectSettingsAreForSession": "Settings you select here are valid only for the active session. You can view and manage sessions in KeeWeb settings."
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { View } from 'framework/views/view';
|
||||||
|
import template from 'templates/extension/extension-connect.hbs';
|
||||||
|
|
||||||
|
class ExtensionConnectView extends View {
|
||||||
|
template = template;
|
||||||
|
|
||||||
|
events = {
|
||||||
|
'change #extension-connect__ask-get': 'askGetChanged',
|
||||||
|
'change .extension-connect__file-check': 'fileChecked'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(model) {
|
||||||
|
super(model);
|
||||||
|
this.config = {
|
||||||
|
askGet: this.model.askGet,
|
||||||
|
allFiles: this.model.allFiles,
|
||||||
|
files: this.model.files.filter((f) => f.checked).map((f) => f.id)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
super.render({
|
||||||
|
...this.model,
|
||||||
|
...this.config,
|
||||||
|
files: this.model.files.map((f) => ({
|
||||||
|
id: f.id,
|
||||||
|
name: f.name,
|
||||||
|
checked: this.config.files.includes(f.id)
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChecked(e) {
|
||||||
|
const fileId = e.target.dataset.file;
|
||||||
|
const checked = e.target.checked;
|
||||||
|
|
||||||
|
if (fileId === 'all') {
|
||||||
|
this.config.allFiles = checked;
|
||||||
|
this.config.files = this.model.files.map((f) => f.id);
|
||||||
|
} else {
|
||||||
|
if (checked) {
|
||||||
|
this.config.files.push(fileId);
|
||||||
|
} else {
|
||||||
|
this.config.files = this.config.files.filter((f) => f !== fileId);
|
||||||
|
this.config.allFiles = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
const atLeastOneFileSelected = this.config.files.length > 0 || this.config.allFiles;
|
||||||
|
|
||||||
|
const allowButton = document.querySelector('.modal button[data-result=yes]');
|
||||||
|
allowButton.classList.toggle('hide', !atLeastOneFileSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
askGetChanged(e) {
|
||||||
|
this.config.askGet = e.target.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ExtensionConnectView };
|
|
@ -0,0 +1,10 @@
|
||||||
|
.extension-connect {
|
||||||
|
&__files {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__file {
|
||||||
|
margin: $base-padding;
|
||||||
|
}
|
||||||
|
}
|
|
@ -284,3 +284,7 @@ input[type='range'] {
|
||||||
.input-base {
|
.input-base {
|
||||||
@extend .input-size-base;
|
@extend .input-size-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select.input-base {
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
|
|
@ -202,3 +202,4 @@ $fa-var-titlebar-minimize: next-fa-glyph();
|
||||||
$fa-var-titlebar-restore: next-fa-glyph();
|
$fa-var-titlebar-restore: next-fa-glyph();
|
||||||
$fa-var-window-maximize: next-fa-glyph();
|
$fa-var-window-maximize: next-fa-glyph();
|
||||||
$fa-var-download: next-fa-glyph();
|
$fa-var-download: next-fa-glyph();
|
||||||
|
$fa-var-exchange-alt: next-fa-glyph();
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
&__buttons {
|
&__buttons {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
.modal--wide & {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
@include tablet {
|
@include tablet {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ $fa-font-path: '~font-awesome/fonts';
|
||||||
@import 'areas/app';
|
@import 'areas/app';
|
||||||
@import 'areas/auto-type';
|
@import 'areas/auto-type';
|
||||||
@import 'areas/details';
|
@import 'areas/details';
|
||||||
|
@import 'areas/extension';
|
||||||
@import 'areas/footer';
|
@import 'areas/footer';
|
||||||
@import 'areas/grp';
|
@import 'areas/grp';
|
||||||
@import 'areas/tag';
|
@import 'areas/tag';
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<div class="extension-connect">
|
||||||
|
<p>{{#res 'extensionConnectIntro'}}{{extensionName}}{{/res}}</p>
|
||||||
|
{{#unless identityVerified}}
|
||||||
|
<p class="muted-color">{{res 'extensionConnectUnknownActivity'}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
<div><label>{{res 'extensionConnectFiles'}}</label></div>
|
||||||
|
<div class="extension-connect__files">
|
||||||
|
{{#each files as |file|}}
|
||||||
|
<div class="extension-connect__file">
|
||||||
|
<input type="checkbox" class="input-base extension-connect__file-check"
|
||||||
|
data-file="{{file.id}}"
|
||||||
|
id="extension-connect__file-check--name-{{file.id}}"
|
||||||
|
{{#if file.checked}}checked{{/if}}
|
||||||
|
/>
|
||||||
|
<label for="extension-connect__file-check--name-{{file.id}}">{{file.name}}</label>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
<div class="extension-connect__file">
|
||||||
|
<input type="checkbox" class="input-base extension-connect__file-check"
|
||||||
|
data-file="all"
|
||||||
|
id="extension-connect__file-check--all"
|
||||||
|
{{#if allFiles}}checked{{/if}}
|
||||||
|
/>
|
||||||
|
<label for="extension-connect__file-check--all">{{res 'extensionConnectAllFiles'}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><label for="extension-connect__ask-get">{{res 'extensionConnectAskGet'}}</label></div>
|
||||||
|
<select id="extension-connect__ask-get" class="input-base">
|
||||||
|
<option value="multiple" {{#ifeq askGet 'multiple'}}selected{{/ifeq}}>
|
||||||
|
{{res 'extensionConnectAskGetMultiple'}}
|
||||||
|
</option>
|
||||||
|
<option value="always" {{#ifeq askGet 'always'}}selected{{/ifeq}}>
|
||||||
|
{{res 'extensionConnectAskGetAlways'}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p>{{res 'extensionConnectSettingsAreForSession'}}</p>
|
||||||
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="modal modal--hidden {{#if opaque}}modal--opaque{{/if}}">
|
<div class="modal modal--hidden {{#if opaque}}modal--opaque{{/if}} {{#if wide}}modal--wide{{/if}}">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
{{#if icon}}
|
{{#if icon}}
|
||||||
<i class="modal__icon fa fa-{{icon}}"></i>
|
<i class="modal__icon fa fa-{{icon}}"></i>
|
||||||
|
|
|
@ -15,14 +15,19 @@ ipcMain.handle('browserExtensionConnectorSocketEvent', browserExtensionConnector
|
||||||
|
|
||||||
const logger = new Logger('browser-extension-connector');
|
const logger = new Logger('browser-extension-connector');
|
||||||
|
|
||||||
|
const BrowserExtensionNames = {
|
||||||
|
KWC: 'KeeWeb Connect',
|
||||||
|
KPXC: 'KeePassXC-Browser'
|
||||||
|
};
|
||||||
|
|
||||||
const MaxIncomingDataLength = 10_000;
|
const MaxIncomingDataLength = 10_000;
|
||||||
const ExtensionOrigins = {
|
const ExtensionOrigins = {
|
||||||
'safari-keeweb-connect': 'keeweb-connect',
|
'safari-keeweb-connect': BrowserExtensionNames.KWC,
|
||||||
'keeweb-connect@keeweb.info': 'keeweb-connect',
|
'keeweb-connect@keeweb.info': BrowserExtensionNames.KWC,
|
||||||
'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': 'keeweb-connect',
|
'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': BrowserExtensionNames.KWC,
|
||||||
'keepassxc-browser@keepassxc.org': 'keepassxc-browser',
|
'keepassxc-browser@keepassxc.org': BrowserExtensionNames.KPXC,
|
||||||
'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': 'keepassxc-browser',
|
'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': BrowserExtensionNames.KPXC,
|
||||||
'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': 'keepassxc-browser'
|
'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': BrowserExtensionNames.KPXC
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppNames = {
|
const AppNames = {
|
||||||
|
|
Loading…
Reference in New Issue