Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43d5279acf | ||
|
|
e674607fce | ||
|
|
2e6239f637 | ||
|
|
bcb8ddc17b | ||
|
|
cd619582ec | ||
|
|
3b49562f64 | ||
|
|
674ed2cc72 | ||
|
|
5621fd8a06 | ||
|
|
af27e0b111 | ||
|
|
5a78e12f7d | ||
|
|
47b19d1d74 | ||
|
|
070573e68a | ||
|
|
946d7769f1 | ||
|
|
f0426a3ec2 | ||
|
|
b2d67818a8 | ||
|
|
c0b159d824 | ||
|
|
d7e11e20c1 | ||
|
|
d590318153 | ||
|
|
5816ca9c4d | ||
|
|
6627e8c0ec | ||
|
|
379e28e8a4 | ||
|
|
4fb25451a2 | ||
|
|
58a6a97130 | ||
|
|
f59b846643 | ||
|
|
28d7dfeb67 | ||
|
|
6cc39d2107 | ||
|
|
a92a01095b | ||
|
|
3f4cddd2a2 | ||
|
|
aa734ea976 | ||
|
|
30628004fd | ||
|
|
a6e87b52fd | ||
|
|
6d06d825e5 | ||
|
|
0800d5f33a | ||
|
|
6929931cae |
@@ -7,6 +7,8 @@
|
||||
}],
|
||||
"@babel/plugin-proposal-optional-catch-binding",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-external-helpers"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment**
|
||||
Your user-agent (from Settings/Help section)
|
||||
Please copy all version information from Settings/Help
|
||||
|
||||
**Kdbx File**
|
||||
Does it happen on Demo or New database?
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>KeeWeb</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.opener.postMessage(
|
||||
{ storage: 'teams', search: location.search },
|
||||
window.location.origin
|
||||
);
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -258,7 +258,7 @@ const AutoType = {
|
||||
if (entries.length === 0) {
|
||||
if (evt.filter.useUrl) {
|
||||
evt.filter.useUrl = false;
|
||||
if (evt.filter.title) {
|
||||
if (evt.filter.title && AppSettingsModel.autoTypeTitleFilterEnabled) {
|
||||
evt.filter.useTitle = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ async function findEntry(request, returnIfOneMatch, filterOptions) {
|
||||
if (!entries.length) {
|
||||
if (AppSettingsModel.extensionFocusIfEmpty) {
|
||||
filter.useUrl = false;
|
||||
if (filter.title) {
|
||||
if (filter.title && AppSettingsModel.autoTypeTitleFilterEnabled) {
|
||||
filter.useTitle = true;
|
||||
entries = filter.getEntries();
|
||||
if (!entries.length) {
|
||||
|
||||
@@ -191,7 +191,7 @@ const Launcher = {
|
||||
}
|
||||
},
|
||||
quitOnRealQuitEventIfMinimizeOnQuitIsEnabled() {
|
||||
return !!(this.platform() === 'darwin' || this.pendingUpdateFile);
|
||||
return !!this.pendingUpdateFile;
|
||||
},
|
||||
minimizeApp() {
|
||||
this.remoteApp().minimizeApp({
|
||||
|
||||
@@ -37,4 +37,17 @@ const OneDriveApps = {
|
||||
}
|
||||
};
|
||||
|
||||
export { DropboxApps, GDriveApps, OneDriveApps };
|
||||
const MsTeamsApps = {
|
||||
Local: {
|
||||
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635'
|
||||
},
|
||||
Production: {
|
||||
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635'
|
||||
},
|
||||
Desktop: {
|
||||
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635',
|
||||
secret: 'F02~HYaWs-~7MndJcVRtv9~h-50Brk_9ho'
|
||||
}
|
||||
};
|
||||
|
||||
export { DropboxApps, GDriveApps, OneDriveApps, MsTeamsApps };
|
||||
|
||||
@@ -18,6 +18,7 @@ const DefaultAppSettings = {
|
||||
colorfulIcons: false, // use colorful custom icons instead of grayscale
|
||||
useMarkdown: true, // use Markdown in Notes field
|
||||
directAutotype: true, // if only one matching entry is found, select that one automatically
|
||||
autoTypeTitleFilterEnabled: true, // enable the title filtering in auto-type by default
|
||||
titlebarStyle: 'default', // window titlebar style
|
||||
lockOnMinimize: true, // lock the app when it's minimized
|
||||
lockOnCopy: false, // lock the app after a password was copied
|
||||
@@ -86,7 +87,13 @@ const DefaultAppSettings = {
|
||||
|
||||
onedrive: true, // enable OneDrive integration
|
||||
onedriveClientId: null, // custom OneDrive client id
|
||||
onedriveClientSecret: null // custom OneDrive client secret
|
||||
onedriveClientSecret: null, // custom OneDrive client secret
|
||||
onedriveTenantId: null, // custom OneDrive tenant id
|
||||
|
||||
msteams: false, // enable Microsoft Teams integration
|
||||
msteamsClientId: null, // custom Microsoft Teams client id
|
||||
msteamsClientSecret: null, // custom Microsoft Teams client secret
|
||||
msteamsTenantId: null // custom Microsoft Teams tenant id
|
||||
};
|
||||
|
||||
export { DefaultAppSettings };
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"dropbox": "Dropbox",
|
||||
"gdrive": "Google Drive",
|
||||
"onedrive": "OneDrive",
|
||||
"msteams": "Microsoft Teams",
|
||||
"menuAllItems": "All Items",
|
||||
"menuColors": "Colors",
|
||||
"menuTrash": "Trash",
|
||||
@@ -432,7 +433,8 @@
|
||||
"setGenColorfulIcons": "Colorful custom icons in the list",
|
||||
"setGenUseMarkdown": "Markdown in notes",
|
||||
"setGenUseGroupIconForEntries": "Automatically use group icon for new entries",
|
||||
"setGenDirectAutotype": "If only one matching entry is found, select that one automatically for Autotype",
|
||||
"setGenDirectAutotype": "If only one matching entry is found, select that one automatically for auto-type",
|
||||
"setGenAutoTypeTitleFilterEnabled": "Filter entries by title in auto-type",
|
||||
"setGenFunction": "Function",
|
||||
"setGenAutoSyncOnClose": "Automatically save and sync on close",
|
||||
"setGenAutoSyncTimer": "Automatically save and sync periodically",
|
||||
|
||||
@@ -677,10 +677,10 @@ class FileModel extends Model {
|
||||
|
||||
addCustomIcon(iconData) {
|
||||
const uuid = kdbxweb.KdbxUuid.random();
|
||||
this.db.meta.customIcons[uuid] = {
|
||||
this.db.meta.customIcons.set(uuid.id, {
|
||||
data: kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(iconData)),
|
||||
lastModified: new Date()
|
||||
};
|
||||
});
|
||||
return uuid.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -221,22 +221,30 @@ class StorageOneDrive extends StorageBase {
|
||||
_getOAuthConfig() {
|
||||
let clientId = this.appSettings.onedriveClientId;
|
||||
let clientSecret = this.appSettings.onedriveClientSecret;
|
||||
let tenant = this.appSettings.onedriveTenantId;
|
||||
|
||||
if (!clientId) {
|
||||
if (Features.isDesktop) {
|
||||
({ id: clientId, secret: clientSecret } = OneDriveApps.Desktop);
|
||||
({ id: clientId, secret: clientSecret, tenantId: tenant } = OneDriveApps.Desktop);
|
||||
} else if (Features.isLocal) {
|
||||
({ id: clientId, secret: clientSecret } = OneDriveApps.Local);
|
||||
({ id: clientId, secret: clientSecret, tenantId: tenant } = OneDriveApps.Local);
|
||||
} else {
|
||||
({ id: clientId, secret: clientSecret } = OneDriveApps.Production);
|
||||
({
|
||||
id: clientId,
|
||||
secret: clientSecret,
|
||||
tenantId: tenant
|
||||
} = OneDriveApps.Production);
|
||||
}
|
||||
}
|
||||
tenant = tenant || 'common';
|
||||
|
||||
let scope = 'files.readwrite';
|
||||
if (!this.appSettings.shortLivedStorageToken) {
|
||||
scope += ' offline_access';
|
||||
}
|
||||
return {
|
||||
url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
||||
url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,
|
||||
tokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
import { StorageBase } from 'storage/storage-base';
|
||||
import { MsTeamsApps } from 'const/cloud-storage-apps';
|
||||
import { Features } from 'util/features';
|
||||
|
||||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
||||
|
||||
// https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true
|
||||
// https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified')
|
||||
// https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified')&$orderby=displayName
|
||||
// https://graph.microsoft.com/v1.0/groups?$orderby=displayName
|
||||
// /me/joinedTeams
|
||||
// https://graph.microsoft.com/v1.0/groups/{group id}/drive/root/children
|
||||
|
||||
class StorageTeams extends StorageBase {
|
||||
name = 'msteams';
|
||||
enabled = true;
|
||||
uipos = 50;
|
||||
icon = 'user-friends';
|
||||
|
||||
_graphUrl = 'https://graph.microsoft.com/v1.0';
|
||||
_groupsUrl = `${this._graphUrl}/me/joinedTeams`;
|
||||
_baseUrl = `${this._graphUrl}/groups`;
|
||||
|
||||
getPathForName(fileName) {
|
||||
return '/drive/root:/' + fileName + '.kdbx';
|
||||
}
|
||||
|
||||
genUrlAddress(groupId, path) {
|
||||
if (groupId) {
|
||||
return this._baseUrl + '/' + groupId + (path ? '/' + path.replace(/^\/+/, '') : '');
|
||||
} else {
|
||||
return this._groupsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
genUrl(path) {
|
||||
if (!path) {
|
||||
const groupId = null;
|
||||
const dir = null;
|
||||
const url = this.genUrlAddress(groupId, dir);
|
||||
return [groupId, dir, url];
|
||||
}
|
||||
|
||||
const parts = path.replace(/^\/+/, '').split('/');
|
||||
if (parts.length === 0) {
|
||||
const groupId = null;
|
||||
const dir = null;
|
||||
const url = this.genUrlAddress(groupId, dir);
|
||||
return [groupId, dir, url];
|
||||
} else if (parts.length === 1) {
|
||||
const groupId = parts[0];
|
||||
const dir = null;
|
||||
const url = this.genUrlAddress(groupId, dir);
|
||||
return [groupId, dir, url];
|
||||
} else {
|
||||
path = path.replace(/\/drive\/root\:/, '');
|
||||
const groupId = parts[0];
|
||||
const dir = ('/' + parts.slice(1).join('/')).replace(/^\/+/, '');
|
||||
const url = this.genUrlAddress(groupId, dir);
|
||||
return [groupId, dir, url];
|
||||
}
|
||||
}
|
||||
|
||||
load(path, opts, callback) {
|
||||
this._oauthAuthorize((err) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
this.logger.debug('Load', path);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(path);
|
||||
const groupId = urlParts[0];
|
||||
path = urlParts[1];
|
||||
const url = urlParts[2];
|
||||
if (!groupId) {
|
||||
const err = 'no group id defined';
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
this._xhr({
|
||||
url,
|
||||
responseType: 'json',
|
||||
success: (response) => {
|
||||
const downloadUrl = response['@microsoft.graph.downloadUrl'];
|
||||
let rev = response.eTag;
|
||||
if (!downloadUrl || !response.eTag) {
|
||||
this.logger.debug(
|
||||
'Load error',
|
||||
path,
|
||||
'no download url',
|
||||
response,
|
||||
this.logger.ts(ts)
|
||||
);
|
||||
return callback && callback('no download url');
|
||||
}
|
||||
this._xhr({
|
||||
url: downloadUrl,
|
||||
responseType: 'arraybuffer',
|
||||
skipAuth: true,
|
||||
success: (response, xhr) => {
|
||||
rev = xhr.getResponseHeader('ETag') || rev;
|
||||
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
|
||||
return callback && callback(null, response, { rev });
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('Load error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('Load error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stat(path, opts, callback) {
|
||||
this._oauthAuthorize((err) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
this.logger.debug('Stat', path);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(path);
|
||||
const groupId = urlParts[0];
|
||||
path = urlParts[1];
|
||||
const url = urlParts[2];
|
||||
if (!groupId) {
|
||||
const err = 'no group id defined';
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
this._xhr({
|
||||
url,
|
||||
responseType: 'json',
|
||||
success: (response) => {
|
||||
const rev = response.eTag;
|
||||
if (!rev) {
|
||||
this.logger.error('Stat error', path, 'no eTag', this.logger.ts(ts));
|
||||
return callback && callback('no eTag');
|
||||
}
|
||||
this.logger.debug('Stated', path, rev, this.logger.ts(ts));
|
||||
return callback && callback(null, { rev });
|
||||
},
|
||||
error: (err, xhr) => {
|
||||
if (xhr.status === 404) {
|
||||
this.logger.debug('Stated not found', path, this.logger.ts(ts));
|
||||
return callback && callback({ notFound: true });
|
||||
}
|
||||
this.logger.error('Stat error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
save(path, opts, data, callback, rev) {
|
||||
this._oauthAuthorize((err) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
this.logger.debug('Save', path, rev);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(path);
|
||||
const groupId = urlParts[0];
|
||||
path = urlParts[1];
|
||||
const url = urlParts[2] + ':/content';
|
||||
if (!groupId) {
|
||||
const err = 'no group id defined';
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
this._xhr({
|
||||
url,
|
||||
method: 'PUT',
|
||||
responseType: 'json',
|
||||
headers: rev ? { 'If-Match': rev } : null,
|
||||
data,
|
||||
statuses: [200, 201, 412],
|
||||
success: (response, xhr) => {
|
||||
rev = response.eTag;
|
||||
if (!rev) {
|
||||
this.logger.error('Save error', path, 'no eTag', this.logger.ts(ts));
|
||||
return callback && callback('no eTag');
|
||||
}
|
||||
if (xhr.status === 412) {
|
||||
this.logger.debug('Save conflict', path, rev, this.logger.ts(ts));
|
||||
return callback && callback({ revConflict: true }, { rev });
|
||||
}
|
||||
this.logger.debug('Saved', path, rev, this.logger.ts(ts));
|
||||
return callback && callback(null, { rev });
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('Save error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
list(dir, callback) {
|
||||
this._oauthAuthorize((err) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
this.logger.debug('List', dir);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(dir);
|
||||
const groupId = urlParts[0];
|
||||
dir = urlParts[1];
|
||||
const urlPath = groupId ? (dir ? ':/children' : '/drive/root/children') : '';
|
||||
const url = urlParts[2] + urlPath;
|
||||
|
||||
const self = this;
|
||||
self._groupId = groupId;
|
||||
|
||||
this._xhr({
|
||||
url,
|
||||
responseType: 'json',
|
||||
success: (response) => {
|
||||
if (!response || !response.value) {
|
||||
this.logger.error('List error', this.logger.ts(ts), response);
|
||||
return callback && callback('list error');
|
||||
}
|
||||
this.logger.debug('Listed', this.logger.ts(ts));
|
||||
let fileList;
|
||||
if (!self._groupId) {
|
||||
fileList = response.value
|
||||
.filter((f) => f.displayName)
|
||||
.map((f) => ({
|
||||
name: f.displayName,
|
||||
path: '/' + f.id,
|
||||
rev: f.id,
|
||||
dir: true
|
||||
}));
|
||||
} else {
|
||||
fileList = response.value
|
||||
.filter((f) => f.name)
|
||||
.map((f) => ({
|
||||
name: f.name,
|
||||
path: `/${self._groupId}${f.parentReference.path}/${f.name}`,
|
||||
rev: f.eTag,
|
||||
dir: !!f.folder
|
||||
}));
|
||||
}
|
||||
return callback && callback(null, fileList);
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('List error', this.logger.ts(ts), err);
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
remove(path, callback) {
|
||||
this.logger.debug('Remove', path);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(path);
|
||||
const groupId = urlParts[0];
|
||||
path = urlParts[1];
|
||||
const url = urlParts[2];
|
||||
if (!groupId) {
|
||||
const err = 'no group id defined';
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
this._xhr({
|
||||
url,
|
||||
method: 'DELETE',
|
||||
responseType: 'json',
|
||||
statuses: [200, 204],
|
||||
success: () => {
|
||||
this.logger.debug('Removed', path, this.logger.ts(ts));
|
||||
return callback && callback();
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('Remove error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mkdir(path, callback) {
|
||||
this._oauthAuthorize((err) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
this.logger.debug('Make dir', path);
|
||||
const ts = this.logger.ts();
|
||||
|
||||
const urlParts = this.genUrl(path);
|
||||
const groupId = urlParts[0];
|
||||
path = urlParts[1];
|
||||
const url = urlParts[2] + '/drive/root/children';
|
||||
if (!groupId) {
|
||||
const err = 'no group id defined';
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
const data = JSON.stringify({ name: path.replace('/drive/root:/', ''), folder: {} });
|
||||
this._xhr({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
statuses: [200, 204],
|
||||
data,
|
||||
dataType: 'application/json',
|
||||
success: () => {
|
||||
this.logger.debug('Made dir', path, this.logger.ts(ts));
|
||||
return callback && callback();
|
||||
},
|
||||
error: (err) => {
|
||||
this.logger.error('Make dir error', path, err, this.logger.ts(ts));
|
||||
return callback && callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout(enabled) {
|
||||
this._oauthRevokeToken();
|
||||
}
|
||||
|
||||
_getOAuthConfig() {
|
||||
let clientId = this.appSettings.msteamsClientId;
|
||||
let clientSecret = this.appSettings.msteamsClientSecret;
|
||||
let tenant = this.appSettings.msteamsTenantId;
|
||||
|
||||
if (!clientId) {
|
||||
if (Features.isDesktop) {
|
||||
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Desktop);
|
||||
} else if (Features.isLocal) {
|
||||
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Local);
|
||||
} else {
|
||||
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Production);
|
||||
}
|
||||
}
|
||||
tenant = tenant || 'common';
|
||||
|
||||
let scope = 'Sites.ReadWrite.All Team.ReadBasic.All';
|
||||
if (!this.appSettings.shortLivedStorageToken) {
|
||||
scope += ' offline_access';
|
||||
}
|
||||
return {
|
||||
url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,
|
||||
tokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
pkce: true,
|
||||
width: 600,
|
||||
height: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { StorageTeams };
|
||||
@@ -5,6 +5,7 @@ import { StorageFile } from 'storage/impl/storage-file';
|
||||
import { StorageFileCache } from 'storage/impl/storage-file-cache';
|
||||
import { StorageGDrive } from 'storage/impl/storage-gdrive';
|
||||
import { StorageOneDrive } from 'storage/impl/storage-onedrive';
|
||||
import { StorageTeams } from 'storage/impl/storage-teams';
|
||||
import { StorageWebDav } from 'storage/impl/storage-webdav';
|
||||
import { createOAuthSession } from 'storage/pkce';
|
||||
|
||||
@@ -17,6 +18,7 @@ const ThirdPartyStorage = {
|
||||
dropbox: new StorageDropbox(),
|
||||
gdrive: new StorageGDrive(),
|
||||
onedrive: new StorageOneDrive(),
|
||||
msteams: new StorageTeams(),
|
||||
webdav: new StorageWebDav()
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ const ExpectedFieldRefByteLength = ExpectedFieldRefChars.length;
|
||||
kdbxweb.ProtectedValue.prototype.isProtected = true;
|
||||
|
||||
kdbxweb.ProtectedValue.prototype.forEachChar = function (fn) {
|
||||
const value = this._value;
|
||||
const salt = this._salt;
|
||||
const value = this.value;
|
||||
const salt = this.salt;
|
||||
let b, b1, b2, b3;
|
||||
for (let i = 0, len = value.length; i < len; i++) {
|
||||
b = value[i] ^ salt[i];
|
||||
@@ -154,7 +154,7 @@ kdbxweb.ProtectedValue.prototype.equals = function (other) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if ((this._value[i] ^ this._salt[i]) !== (other._value[i] ^ other._salt[i])) {
|
||||
if ((this.value[i] ^ this.salt[i]) !== (other.value[i] ^ other.salt[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -181,8 +181,8 @@ kdbxweb.ProtectedValue.prototype.saltedValue = function () {
|
||||
if (!this.byteLength) {
|
||||
return 0;
|
||||
}
|
||||
const value = this._value;
|
||||
const salt = this._salt;
|
||||
const value = this.value;
|
||||
const salt = this.salt;
|
||||
let salted = '';
|
||||
for (let i = 0, len = value.length; i < len; i++) {
|
||||
const byte = value[i] ^ salt[i];
|
||||
@@ -193,8 +193,8 @@ kdbxweb.ProtectedValue.prototype.saltedValue = function () {
|
||||
|
||||
kdbxweb.ProtectedValue.prototype.dataAndSalt = function () {
|
||||
return {
|
||||
data: [...this._value],
|
||||
salt: [...this._salt]
|
||||
data: [...this.value],
|
||||
salt: [...this.salt]
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class SettingsGeneralView extends View {
|
||||
'change .settings__general-use-markdown': 'changeUseMarkdown',
|
||||
'change .settings__general-use-group-icon-for-entries': 'changeUseGroupIconForEntries',
|
||||
'change .settings__general-direct-autotype': 'changeDirectAutotype',
|
||||
'change .settings__general-autotype-title-filter': 'changeAutoTypeTitleFilter',
|
||||
'change .settings__general-field-label-dblclick-autotype':
|
||||
'changeFieldLabelDblClickAutoType',
|
||||
'change .settings__general-device-owner-auth': 'changeDeviceOwnerAuth',
|
||||
@@ -135,6 +136,7 @@ class SettingsGeneralView extends View {
|
||||
useMarkdown: AppSettingsModel.useMarkdown,
|
||||
useGroupIconForEntries: AppSettingsModel.useGroupIconForEntries,
|
||||
directAutotype: AppSettingsModel.directAutotype,
|
||||
autoTypeTitleFilterEnabled: AppSettingsModel.autoTypeTitleFilterEnabled,
|
||||
fieldLabelDblClickAutoType: AppSettingsModel.fieldLabelDblClickAutoType,
|
||||
supportsTitleBarStyles: Features.supportsTitleBarStyles,
|
||||
supportsCustomTitleBarAndDraggableWindow:
|
||||
@@ -431,6 +433,11 @@ class SettingsGeneralView extends View {
|
||||
AppSettingsModel.directAutotype = directAutotype;
|
||||
}
|
||||
|
||||
changeAutoTypeTitleFilter(e) {
|
||||
const autoTypeTitleFilterEnabled = e.target.checked || false;
|
||||
AppSettingsModel.autoTypeTitleFilterEnabled = autoTypeTitleFilterEnabled;
|
||||
}
|
||||
|
||||
changeFieldLabelDblClickAutoType(e) {
|
||||
const fieldLabelDblClickAutoType = e.target.checked || false;
|
||||
AppSettingsModel.fieldLabelDblClickAutoType = fieldLabelDblClickAutoType;
|
||||
|
||||
@@ -334,8 +334,11 @@
|
||||
}
|
||||
}
|
||||
&__donate-btn {
|
||||
$donate-border-color: #89abed;
|
||||
|
||||
background: #fff;
|
||||
border: 2px solid #89abed;
|
||||
border: 2px solid $donate-border-color;
|
||||
border-bottom: 2px solid $donate-border-color;
|
||||
border-radius: 2.8rem;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
@@ -345,6 +348,10 @@
|
||||
height: 2.8rem;
|
||||
margin-bottom: $base-padding-v * 2;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid $donate-border-color;
|
||||
}
|
||||
|
||||
&-top,
|
||||
&-bottom {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -82,6 +82,7 @@ $fa-var-file-image: next-fa-glyph();
|
||||
$fa-var-file-video: next-fa-glyph();
|
||||
$fa-var-file-audio: next-fa-glyph();
|
||||
$fa-var-onedrive: next-fa-glyph();
|
||||
$fa-var-user-friends: next-fa-glyph();
|
||||
$fa-var-question: next-fa-glyph();
|
||||
$fa-var-sign-out-alt: next-fa-glyph();
|
||||
$fa-var-sync-alt: next-fa-glyph();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<p>{{#res 'setAboutFirst'}}<a href="https://antelle.net" target="_blank">Antelle</a>{{/res~}}
|
||||
{{~#res 'setAboutSecond'}}<a href="{{licenseLink}}" target="_blank">MIT</a>{{/res}}
|
||||
{{#res 'setAboutSource'}}<a href="{{repoLink}}" target="_blank">GitHub <i class="fa fa-github-alt bottom"></i></a>{{/res}}</p>
|
||||
<a href="{{donationLink}}" target="_blank" class="settings__donate-btn no-border">
|
||||
<a href="{{donationLink}}" target="_blank" class="settings__donate-btn">
|
||||
<span class="settings__donate-btn-top">Become a</span><span class="settings__donate-btn-bottom">Backer</span>
|
||||
</a>
|
||||
<p>{{res 'setAboutBuilt'}}:</p>
|
||||
|
||||
@@ -169,6 +169,11 @@
|
||||
id="settings__general-direct-autotype" {{#if directAutotype}}checked{{/if}} />
|
||||
<label for="settings__general-direct-autotype">{{res 'setGenDirectAutotype'}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="settings__input input-base settings__general-autotype-title-filter"
|
||||
id="settings__general-autotype-title-filter" {{#if autoTypeTitleFilterEnabled}}checked{{/if}} />
|
||||
<label for="settings__general-autotype-title-filter">{{res 'setGenAutoTypeTitleFilterEnabled'}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="settings__input input-base settings__general-field-label-dblclick-autotype"
|
||||
id="settings__general-field-label-dblclick-autotype" {{#if fieldLabelDblClickAutoType}}checked{{/if}} />
|
||||
|
||||
+6
-1
@@ -202,7 +202,9 @@ main.minimizeApp = function (menuItemLabels) {
|
||||
if (!appIcon) {
|
||||
const image = electron.nativeImage.createFromPath(path.join(__dirname, 'img', imagePath));
|
||||
appIcon = new electron.Tray(image);
|
||||
appIcon.on('click', restoreMainWindow);
|
||||
if (process.platform !== 'darwin') {
|
||||
appIcon.on('click', restoreMainWindow);
|
||||
}
|
||||
const contextMenu = electron.Menu.buildFromTemplate([
|
||||
{ label: menuItemLabels.restore, click: restoreMainWindow },
|
||||
{ label: menuItemLabels.quit, click: closeMainWindow }
|
||||
@@ -376,6 +378,9 @@ function restoreMainWindow() {
|
||||
}
|
||||
|
||||
function showAndFocusMainWindow() {
|
||||
if (appIcon) {
|
||||
restoreMainWindow();
|
||||
}
|
||||
if (mainWindowMaximized) {
|
||||
mainWindow.maximize();
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.18.3",
|
||||
"version": "1.18.6",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "main.js",
|
||||
"homepage": "https://keeweb.info",
|
||||
|
||||
@@ -306,24 +306,29 @@ async function processFirstMessageFromSocket(socket, message) {
|
||||
|
||||
let parentProcessInfo;
|
||||
try {
|
||||
parentProcessInfo = await getProcessInfo(message.ppid);
|
||||
} catch (e) {
|
||||
logger.error(`Cannot get info for PID ${message.ppid}: ${e}`);
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && parentProcessInfo.appName === 'cmd') {
|
||||
try {
|
||||
parentProcessInfo = await getProcessInfo(parentProcessInfo.ppid);
|
||||
parentProcessInfo = await getProcessInfo(message.ppid);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Cannot get info for PID ${parentProcessInfo.ppid}: ${e}, assuming cmd is the launcher`
|
||||
);
|
||||
logger.error(`Cannot get info for PID ${message.ppid}: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && parentProcessInfo.appName === 'cmd') {
|
||||
try {
|
||||
parentProcessInfo = await getProcessInfo(parentProcessInfo.ppid);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Cannot get info for PID ${parentProcessInfo.ppid}: ${e}, assuming cmd is the launcher`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Cannot get process info, assuming the connection is not identified');
|
||||
}
|
||||
|
||||
appName = AppNames[parentProcessInfo.appName] ?? parentProcessInfo.appName;
|
||||
appName = parentProcessInfo
|
||||
? AppNames[parentProcessInfo.appName] ?? parentProcessInfo.appName
|
||||
: 'Unidentified browser';
|
||||
appName = appName[0].toUpperCase() + appName.substr(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ function installDarwinUpdate(updateFilePath) {
|
||||
`--app=${appPath}`
|
||||
]);
|
||||
|
||||
electron.app.exit(0);
|
||||
electron.app.quit();
|
||||
}
|
||||
|
||||
function installWin32Update(updateFilePath) {
|
||||
@@ -52,7 +52,7 @@ function installWin32Update(updateFilePath) {
|
||||
|
||||
const ps = spawnDetached('cmd');
|
||||
ps.stdin.end(`${updateCommand}\nexit\n`, 'utf8', () => {
|
||||
electron.app.exit(0);
|
||||
electron.app.quit();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,42 +5,67 @@ const { isDev } = require('./app-info');
|
||||
const { app } = require('electron');
|
||||
const { ExtensionIds } = require('../const/extension-ids');
|
||||
|
||||
function getManifestDir(browser) {
|
||||
function getManifestDirs(browser) {
|
||||
const home = app.getPath('home');
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
switch (browser) {
|
||||
case 'Chrome':
|
||||
return `${home}/Library/Application Support/Google/Chrome/NativeMessagingHosts/`;
|
||||
return [
|
||||
`${home}/Library/Application Support/Google/Chrome/NativeMessagingHosts/`,
|
||||
`${home}/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/`,
|
||||
`${home}/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/`,
|
||||
`${home}/Library/Application Support/Chromium/NativeMessagingHosts/`,
|
||||
];
|
||||
case 'Firefox':
|
||||
return `${home}/Library/Application Support/Mozilla/NativeMessagingHosts/`;
|
||||
return [
|
||||
`${home}/Library/Application Support/Mozilla/NativeMessagingHosts/`,
|
||||
];
|
||||
case 'Edge':
|
||||
return `${home}/Library/Application Support/Microsoft Edge/NativeMessagingHosts/`;
|
||||
return [
|
||||
`${home}/Library/Application Support/Microsoft Edge/NativeMessagingHosts/`,
|
||||
];
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
case 'linux':
|
||||
switch (browser) {
|
||||
case 'Chrome':
|
||||
return `${home}/.config/google-chrome/NativeMessagingHosts/`;
|
||||
return [
|
||||
`${home}/.config/google-chrome/NativeMessagingHosts/`,
|
||||
`${home}/.config/google-chrome-dev/NativeMessagingHosts/`,
|
||||
`${home}/.config/google-chrome-unstable/NativeMessagingHosts/`,
|
||||
`${home}/.config/chromium/NativeMessagingHosts/`,
|
||||
];
|
||||
case 'Firefox':
|
||||
return `${home}/.mozilla/native-messaging-hosts/`;
|
||||
return [
|
||||
`${home}/.mozilla/native-messaging-hosts/`,
|
||||
];
|
||||
case 'Edge':
|
||||
return `${home}/.config/microsoft-edge/NativeMessagingHosts/`;
|
||||
return [
|
||||
`${home}/.config/microsoft-edge/NativeMessagingHosts/`,
|
||||
];
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWindowsRegistryPath(browser) {
|
||||
function getWindowsRegistryPaths(browser) {
|
||||
switch (browser) {
|
||||
case 'Chrome':
|
||||
return 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\';
|
||||
return [
|
||||
'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\',
|
||||
'HKCU\\Software\\Chromium\\NativeMessagingHosts\\',
|
||||
];
|
||||
case 'Firefox':
|
||||
return 'HKCU\\Software\\Mozilla\\NativeMessagingHosts\\';
|
||||
return [
|
||||
'HKCU\\Software\\Mozilla\\NativeMessagingHosts\\',
|
||||
];
|
||||
case 'Edge':
|
||||
return 'HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\';
|
||||
return [
|
||||
'HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\',
|
||||
];
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -130,8 +155,8 @@ module.exports.install = async function (browser, extension) {
|
||||
manifest.path = getNativeMessagingHostPath();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const registryPath = getWindowsRegistryPath(browser);
|
||||
if (!registryPath) {
|
||||
const registryPaths = getWindowsRegistryPaths(browser);
|
||||
if (!registryPaths) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,30 +168,34 @@ module.exports.install = async function (browser, extension) {
|
||||
const manifestFileName = getWindowsManifestFileName(browser, extension);
|
||||
await fs.promises.writeFile(manifestFileName, JSON.stringify(manifest, null, 4));
|
||||
|
||||
windowsRegistry.createKey(registryPath + registryKeyName, manifestFileName);
|
||||
for (const path of registryPaths) {
|
||||
windowsRegistry.createKey(path + registryKeyName, manifestFileName);
|
||||
}
|
||||
} else {
|
||||
const manifestDir = getManifestDir(browser);
|
||||
if (!manifestDir) {
|
||||
const manifestDirs = getManifestDirs(browser);
|
||||
if (!manifestDirs) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.promises.mkdir(manifestDir, { recursive: true });
|
||||
|
||||
const manifestFileName = getManifestFileName(extension);
|
||||
if (!manifestFileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPath = path.join(manifestDir, manifestFileName);
|
||||
for (const dir of manifestDirs) {
|
||||
await fs.promises.mkdir(dir, { recursive: true });
|
||||
|
||||
await fs.promises.writeFile(fullPath, JSON.stringify(manifest, null, 4));
|
||||
const fullPath = path.join(dir, manifestFileName);
|
||||
|
||||
await fs.promises.writeFile(fullPath, JSON.stringify(manifest, null, 4));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.uninstall = async function (browser, extension) {
|
||||
if (process.platform === 'win32') {
|
||||
const registryPath = getWindowsRegistryPath(browser);
|
||||
if (!registryPath) {
|
||||
const registryPaths = getWindowsRegistryPaths(browser);
|
||||
if (!registryPaths) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,10 +204,12 @@ module.exports.uninstall = async function (browser, extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
windowsRegistry.deleteKey(registryPath + registryKeyName);
|
||||
for (const path of registryPaths) {
|
||||
windowsRegistry.deleteKey(path + registryKeyName);
|
||||
}
|
||||
} else {
|
||||
const manifestDir = getManifestDir(browser);
|
||||
if (!manifestDir) {
|
||||
const manifestDirs = getManifestDirs(browser);
|
||||
if (!manifestDirs) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,8 +217,11 @@ module.exports.uninstall = async function (browser, extension) {
|
||||
if (!manifestFileName) {
|
||||
return;
|
||||
}
|
||||
const fullPath = path.join(manifestDir, manifestFileName);
|
||||
|
||||
await fs.promises.unlink(fullPath);
|
||||
for (const dir of manifestDirs) {
|
||||
const fullPath = path.join(dir, manifestFileName);
|
||||
|
||||
await fs.promises.unlink(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Generated
+440
-21426
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keeweb",
|
||||
"version": "1.18.3",
|
||||
"version": "1.18.6",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "Gruntfile.js",
|
||||
"private": true,
|
||||
@@ -16,7 +16,7 @@
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||
"@babel/preset-env": "^7.14.1",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@keeweb/keeweb-native-messaging-host": "https://github.com/keeweb/keeweb-connect/releases/download/0.3.3/keeweb-native-messaging-host.tgz",
|
||||
"@keeweb/keeweb-native-messaging-host": "https://github.com/keeweb/keeweb-connect/releases/download/0.3.5/keeweb-native-messaging-host.tgz",
|
||||
"@keeweb/keeweb-native-modules": "https://github.com/keeweb/keeweb-native-modules/releases/download/0.11.6/keeweb-native-modules.tgz",
|
||||
"adm-zip": "^0.5.5",
|
||||
"argon2-browser": "1.15.4",
|
||||
@@ -32,7 +32,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.4",
|
||||
"dompurify": "^2.2.8",
|
||||
"electron": "^12.0.7",
|
||||
"electron": "^12.2.2",
|
||||
"electron-builder": "^22.11.1",
|
||||
"electron-evil-feature-patcher": "^1.2.1",
|
||||
"electron-notarize": "^1.0.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"jquery": "3.6.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"jsqrcode": "github:antelle/jsqrcode#0.1.3",
|
||||
"kdbxweb": "^2.0.1",
|
||||
"kdbxweb": "^2.0.4",
|
||||
"load-grunt-tasks": "5.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^2.0.3",
|
||||
@@ -123,7 +123,7 @@
|
||||
"author": {
|
||||
"name": "Antelle",
|
||||
"email": "antelle.net@gmail.com",
|
||||
"url": "http://antelle.net"
|
||||
"url": "https://antelle.net"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
Release notes
|
||||
-------------
|
||||
##### v1.18.6 (2021-05-19)
|
||||
`-` fix #1824: saving KDBX3 files with compression disabled
|
||||
`-` fix #1818: extension connection error if browser cannot be identified
|
||||
`-` fix #1820: minimize on close on macOS
|
||||
|
||||
##### v1.18.5 (2021-05-14)
|
||||
`-` fix #1816: old Chromium support, such as Android Edge
|
||||
`-` fix #1817: crash on files with large attachments as KDBX3
|
||||
|
||||
##### v1.18.4 (2021-05-12)
|
||||
`+` #1814: option to disable auto-type title filter by default
|
||||
`-` #1808: restore KeeWeb from system tray on extension request
|
||||
`-` fix #1810: extension connection on old macOS (10.11)
|
||||
`-` fix #1813: custom icon selection
|
||||
`-` fix #1811: app doesn't quit during update
|
||||
|
||||
##### v1.18.3 (2021-05-09)
|
||||
`-` fix #1804: filling OTP in browser extensions
|
||||
`*` fix #1805: auto-unchecking auto-type filters if nothing found
|
||||
|
||||
Reference in New Issue
Block a user