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 = {
|
||||
connectionId: 1,
|
||||
extensionName: 'keeweb-connect',
|
||||
extensionName: 'KeeWeb Connect',
|
||||
supportsNotifications: true
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import { Alerts } from 'comp/ui/alerts';
|
|||
import { Locale } from 'util/locale';
|
||||
import { RuntimeInfo } from 'const/runtime-info';
|
||||
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 KeeWebHash = '398d9c782ec76ae9e9877c2321cbda2b31fc6d18ccf0fed5ca4bd746bab4d64a'; // sha256('KeeWeb')
|
||||
|
@ -136,7 +138,7 @@ function checkContentRequestPermissions(request) {
|
|||
ensureAtLeastOneFileIsOpen();
|
||||
|
||||
const client = getClient(request);
|
||||
if (client.authorized) {
|
||||
if (client.permissions) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -147,31 +149,41 @@ function checkContentRequestPermissions(request) {
|
|||
|
||||
focusKeeWeb();
|
||||
|
||||
// TODO: make a proper dialog here instead of a simple question
|
||||
|
||||
if (Launcher) {
|
||||
Alerts.yesno({
|
||||
header: 'Extension connection',
|
||||
body: 'Allow this extension to connect?',
|
||||
success: () => {
|
||||
resolve();
|
||||
},
|
||||
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 config = RuntimeDataModel.extensionConnectConfig;
|
||||
const files = appModel.files.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
checked: !config || config.allFiles || config.files.includes(f.id)
|
||||
}));
|
||||
if (!files.some((f) => f.checked)) {
|
||||
for (const f of files) {
|
||||
f.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
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(() => {
|
||||
client.authorized = true;
|
||||
Launcher.hideApp();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
@ -186,7 +198,7 @@ function getVersion(request) {
|
|||
}
|
||||
|
||||
function isKeeWebConnect(request) {
|
||||
return getClient(request).connection.extensionName === 'keeweb-connect';
|
||||
return getClient(request).connection.extensionName === 'KeeWeb Connect';
|
||||
}
|
||||
|
||||
function focusKeeWeb() {
|
||||
|
|
|
@ -17,6 +17,12 @@ const Alerts = {
|
|||
return Locale.alertYes;
|
||||
}
|
||||
},
|
||||
allow: {
|
||||
result: 'yes',
|
||||
get title() {
|
||||
return Locale.alertAllow;
|
||||
}
|
||||
},
|
||||
no: {
|
||||
result: '',
|
||||
get title() {
|
||||
|
@ -28,6 +34,12 @@ const Alerts = {
|
|||
get title() {
|
||||
return Locale.alertCancel;
|
||||
}
|
||||
},
|
||||
deny: {
|
||||
result: '',
|
||||
get title() {
|
||||
return Locale.alertDeny;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
"alertCopy": "Copy",
|
||||
"alertClose": "Close",
|
||||
"alertDoNotAsk": "Don't ask me anymore",
|
||||
"alertAllow": "Allow",
|
||||
"alertDeny": "Deny",
|
||||
|
||||
"appBeta": "WARNING: beta version, only for preview",
|
||||
|
||||
|
@ -755,5 +757,14 @@
|
|||
|
||||
"extensionErrorNoOpenFiles": "No open files",
|
||||
"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 {
|
||||
@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-window-maximize: next-fa-glyph();
|
||||
$fa-var-download: next-fa-glyph();
|
||||
$fa-var-exchange-alt: next-fa-glyph();
|
||||
|
|
|
@ -51,6 +51,9 @@
|
|||
&__buttons {
|
||||
align-self: center;
|
||||
width: 40%;
|
||||
.modal--wide & {
|
||||
width: 80%;
|
||||
}
|
||||
@include tablet {
|
||||
width: 90%;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ $fa-font-path: '~font-awesome/fonts';
|
|||
@import 'areas/app';
|
||||
@import 'areas/auto-type';
|
||||
@import 'areas/details';
|
||||
@import 'areas/extension';
|
||||
@import 'areas/footer';
|
||||
@import 'areas/grp';
|
||||
@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">
|
||||
{{#if icon}}
|
||||
<i class="modal__icon fa fa-{{icon}}"></i>
|
||||
|
|
|
@ -15,14 +15,19 @@ ipcMain.handle('browserExtensionConnectorSocketEvent', browserExtensionConnector
|
|||
|
||||
const logger = new Logger('browser-extension-connector');
|
||||
|
||||
const BrowserExtensionNames = {
|
||||
KWC: 'KeeWeb Connect',
|
||||
KPXC: 'KeePassXC-Browser'
|
||||
};
|
||||
|
||||
const MaxIncomingDataLength = 10_000;
|
||||
const ExtensionOrigins = {
|
||||
'safari-keeweb-connect': 'keeweb-connect',
|
||||
'keeweb-connect@keeweb.info': 'keeweb-connect',
|
||||
'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': 'keeweb-connect',
|
||||
'keepassxc-browser@keepassxc.org': 'keepassxc-browser',
|
||||
'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': 'keepassxc-browser',
|
||||
'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': 'keepassxc-browser'
|
||||
'safari-keeweb-connect': BrowserExtensionNames.KWC,
|
||||
'keeweb-connect@keeweb.info': BrowserExtensionNames.KWC,
|
||||
'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': BrowserExtensionNames.KWC,
|
||||
'keepassxc-browser@keepassxc.org': BrowserExtensionNames.KPXC,
|
||||
'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': BrowserExtensionNames.KPXC,
|
||||
'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': BrowserExtensionNames.KPXC
|
||||
};
|
||||
|
||||
const AppNames = {
|
||||
|
|
Loading…
Reference in New Issue