1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Desktop: Resolves #2773: Add support for system keychain to save sensitive settings (#3207)

This commit is contained in:
Laurent Cozic 2020-06-03 17:07:50 +01:00 committed by GitHub
parent 5082181c49
commit 26ce102113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 661 additions and 40 deletions

View File

@ -97,7 +97,13 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/ResourceEditWatcher.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

6
.gitignore vendored
View File

@ -87,7 +87,13 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/ResourceEditWatcher.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@ -42,6 +42,8 @@ before_install:
# Silence apt-get update errors (for example when a module doesn't exist) since
# otherwise it will make the whole build fails, even though all we need is yarn.
# libsecret-1-dev is required for keytar - https://github.com/atom/node-keytar
- |
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
HOMEBREW_NO_AUTO_UPDATE=1 brew install yarn
@ -51,6 +53,7 @@ before_install:
sudo apt-get update || true
sudo apt-get install -y yarn
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
fi
script:

View File

@ -29,6 +29,9 @@ const { shimInit } = require('lib/shim-init-node.js');
const { _ } = require('lib/locale.js');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const EncryptionService = require('lib/services/EncryptionService');
const envFromArgs = require('lib/envFromArgs');
const env = envFromArgs(process.argv);
const fsDriver = new FsDriverNode();
Logger.fsDriver_ = fsDriver;
@ -46,9 +49,11 @@ BaseItem.loadClass('NoteTag', NoteTag);
BaseItem.loadClass('MasterKey', MasterKey);
BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appId', `net.cozic.joplin${env === 'dev' ? 'dev' : ''}-cli`);
Setting.setConstant('appType', 'cli');
console.info(Setting.value('appId'));
shimInit();
const application = app();

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const utils = require('../Tools/gulp/utils');
const tasks = {
copyLib: require('../Tools/gulp/tasks/copyLib'),
tsc: require('../Tools/gulp/tasks/tsc'),
};
tasks.build = {
@ -45,5 +46,16 @@ tasks.buildTests = {
},
};
const buildTestSeries = [
tasks.buildTests.fn,
];
if (require('os').platform() === 'win32') {
gulp.task('copyLib', tasks.copyLib.fn);
gulp.task('tsc', tasks.tsc.fn);
buildTestSeries.push('copyLib');
buildTestSeries.push('tsc');
}
gulp.task('build', tasks.build.fn);
gulp.task('buildTests', tasks.buildTests.fn);
gulp.task('buildTests', gulp.series(...buildTestSeries));

View File

@ -3859,6 +3859,22 @@
}
}
},
"keytar": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz",
"integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==",
"requires": {
"nan": "2.14.1",
"prebuild-install": "5.3.3"
},
"dependencies": {
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
}
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",

View File

@ -4,7 +4,7 @@
"license": "MIT",
"author": "Laurent Cozic",
"scripts": {
"test": "gulp buildTests -L && jasmine --config=tests/support/jasmine.json",
"test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json",
"postinstall": "npm run build && patch-package --patch-dir ../patches",
"build": "gulp build",
"start": "gulp build -L && node 'build/main.js' --stack-trace-enabled --log-level debug --env dev"
@ -60,6 +60,7 @@
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.0",
"katex": "^0.11.1",
"keytar": "^5.4.0",
"levenshtein": "^1.0.5",
"markdown-it": "^10.0.0",
"markdown-it-abbr": "^1.0.4",

View File

@ -4,6 +4,7 @@ require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting.js');
process.on('unhandledRejection', (reason, p) => {
@ -13,6 +14,8 @@ process.on('unhandledRejection', (reason, p) => {
describe('models_Setting', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});

View File

@ -0,0 +1,60 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting');
const KeychainService = require('lib/services/keychain/KeychainService').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function describeIfCompatible(name, fn) {
if (['win32', 'darwin'].includes(shim.platformName())) {
return describe(name, fn);
}
}
describeIfCompatible('services_KeychainService', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1, { keychainEnabled: true });
await switchClient(1, { keychainEnabled: true });
await Setting.deleteKeychainPasswords();
done();
});
afterEach(async (done) => {
await Setting.deleteKeychainPasswords();
done();
});
it('should be enabled on macOS and Windows', asyncTest(async () => {
expect(Setting.value('keychain.supported')).toBe(1);
}));
it('should set, get and delete passwords', asyncTest(async () => {
const service = KeychainService.instance();
const isSet = await service.setPassword('zz_testunit', 'password');
expect(isSet).toBe(true);
const password = await service.password('zz_testunit');
expect(password).toBe('password');
await service.deletePassword('zz_testunit');
expect(await service.password('zz_testunit')).toBe(null);
}));
it('should save and load secure settings', asyncTest(async () => {
Setting.setObjectKey('encryption.passwordCache', 'testing', '123456');
await Setting.saveAll();
await Setting.load();
const passwords = Setting.value('encryption.passwordCache');
expect(passwords.testing).toBe('123456');
}));
});

View File

@ -41,6 +41,9 @@ const ResourceFetcher = require('lib/services/ResourceFetcher.js');
const KvStore = require('lib/services/KvStore.js');
const WebDavApi = require('lib/WebDavApi');
const DropboxApi = require('lib/DropboxApi');
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
const KeychainServiceDriver = require('lib/services/keychain/KeychainServiceDriver.node').default;
const KeychainServiceDriverDummy = require('lib/services/keychain/KeychainServiceDriver.dummy').default;
const md5 = require('md5');
const databases_ = [];
@ -109,7 +112,7 @@ BaseItem.loadClass('NoteTag', NoteTag);
BaseItem.loadClass('MasterKey', MasterKey);
BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appId', 'net.cozic.joplintest-cli');
Setting.setConstant('appType', 'cli');
Setting.setConstant('tempDir', tempDir);
@ -133,7 +136,9 @@ function currentClientId() {
return currentClient_;
}
async function switchClient(id) {
async function switchClient(id, options = null) {
options = Object.assign({}, { keychainEnabled: false }, options);
if (!databases_[id]) throw new Error(`Call setupDatabaseAndSynchronizer(${id}) first!!`);
await time.msleep(sleepTime); // Always leave a little time so that updated_time properties don't overlap
@ -149,9 +154,8 @@ async function switchClient(id) {
Setting.setConstant('resourceDirName', resourceDirName(id));
Setting.setConstant('resourceDir', resourceDir(id));
await Setting.load();
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
Setting.setValue('sync.wipeOutFailSafe', false); // To keep things simple, always disable fail-safe unless explicitely set in the test itself
}
@ -186,7 +190,9 @@ async function clearDatabase(id = null) {
await databases_[id].transactionExecBatch(queries);
}
async function setupDatabase(id = null) {
async function setupDatabase(id = null, options = null) {
options = Object.assign({}, { keychainEnabled: false }, options);
if (id === null) id = currentClient_;
Setting.cancelScheduleSave();
@ -195,8 +201,7 @@ async function setupDatabase(id = null) {
if (databases_[id]) {
BaseModel.setDb(databases_[id]);
await clearDatabase(id);
await Setting.load();
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
return;
}
@ -213,8 +218,7 @@ async function setupDatabase(id = null) {
await databases_[id].open({ name: filePath });
BaseModel.setDb(databases_[id]);
await Setting.load();
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
}
function resourceDirName(id = null) {
@ -227,12 +231,12 @@ function resourceDir(id = null) {
return `${__dirname}/data/${resourceDirName(id)}`;
}
async function setupDatabaseAndSynchronizer(id = null) {
async function setupDatabaseAndSynchronizer(id = null, options = null) {
if (id === null) id = currentClient_;
BaseService.logger_ = logger;
await setupDatabase(id);
await setupDatabase(id, options);
EncryptionService.instance_ = null;
DecryptionWorker.instance_ = null;

View File

@ -686,6 +686,7 @@ class Application extends BaseApplication {
_('Client ID: %s', Setting.value('clientId')),
_('Sync Version: %s', Setting.value('syncVersion')),
_('Profile Version: %s', reg.db().version()),
_('Keychain Supported: %s', Setting.value('keychain.supported') >= 1 ? _('Yes') : _('No')),
];
if (gitInfo) {
message.push(`\n${gitInfo}`);

View File

@ -74,9 +74,12 @@ BaseItem.loadClass('NoteTag', NoteTag);
BaseItem.loadClass('MasterKey', MasterKey);
BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', 'net.cozic.joplin-desktop');
Setting.setConstant('appId', `net.cozic.joplin${bridge().env() === 'dev' ? 'dev' : ''}-desktop`);
Setting.setConstant('appType', 'desktop');
console.info(`appId: ${Setting.value('appId')}`);
console.info(`appType: ${Setting.value('appType')}`);
shimInit();
// Disable drag and drop of links inside application (which would

View File

@ -8,7 +8,7 @@ const { ElectronAppWrapper } = require('./ElectronAppWrapper');
const { initBridge } = require('./bridge');
const { Logger } = require('lib/logger.js');
const { FsDriverNode } = require('lib/fs-driver-node.js');
const envFromArgs = require('./envFromArgs');
const envFromArgs = require('lib/envFromArgs');
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled promise rejection', p, 'reason:', reason);

View File

@ -1901,6 +1901,11 @@
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64-stream/-/base64-stream-1.0.0.tgz",
@ -1931,6 +1936,46 @@
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
}
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -2086,6 +2131,15 @@
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@ -4145,7 +4199,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
}
@ -4411,6 +4464,11 @@
}
}
},
"expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@ -4843,6 +4901,11 @@
"map-cache": "^0.2.2"
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
@ -4940,6 +5003,11 @@
"assert-plus": "^1.0.0"
}
},
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -6353,6 +6421,11 @@
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"ignore-walk": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
@ -7045,6 +7118,22 @@
}
}
},
"keytar": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz",
"integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==",
"requires": {
"nan": "2.14.1",
"prebuild-install": "5.3.3"
},
"dependencies": {
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
}
}
},
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@ -7801,6 +7890,11 @@
"minimist": "0.0.8"
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"modify-filename": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
@ -7882,6 +7976,11 @@
"to-regex": "^3.0.1"
}
},
"napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"needle": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
@ -7931,7 +8030,6 @@
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.16.0.tgz",
"integrity": "sha512-+sa0XNlWDA6T+bDLmkCUYn6W5k5W6BPRL6mqzSCs6H/xUgtl4D5x2fORKDzopKiU6wsyn/+wXlRXwXeSp+mtoA==",
"dev": true,
"requires": {
"semver": "^5.4.1"
}
@ -8165,6 +8263,11 @@
"tar": "^4"
}
},
"noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
},
"nopt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
@ -9219,6 +9322,35 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
"dev": true
},
"prebuild-install": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
"integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -9305,7 +9437,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@ -10274,6 +10405,36 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
},
"simple-get": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
},
"dependencies": {
"decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"requires": {
"mimic-response": "^2.0.0"
}
},
"mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
}
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@ -10804,6 +10965,61 @@
}
}
},
"tar-fs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
},
"dependencies": {
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}
}
},
"tar-stream": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
"integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
"requires": {
"bl": "^4.0.1",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
}
}
},
"tcp-port-used": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-0.1.2.tgz",
@ -11673,6 +11889,11 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",

View File

@ -127,6 +127,7 @@
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.1",
"katex": "^0.11.1",
"keytar": "^5.4.0",
"levenshtein": "^1.0.5",
"lodash": "^4.17.15",
"mark.js": "^8.11.1",

View File

@ -15,7 +15,6 @@ const { reg } = require('lib/registry.js');
const { time } = require('lib/time-utils.js');
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { shim } = require('lib/shim.js');
const { uuid } = require('lib/uuid.js');
const { _, setLocale } = require('lib/locale.js');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const os = require('os');
@ -38,6 +37,8 @@ const ResourceService = require('lib/services/RevisionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
const SearchEngine = require('lib/services/SearchEngine');
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
const KeychainServiceDriver = require('lib/services/keychain/KeychainServiceDriver.node').default;
const KvStore = require('lib/services/KvStore');
const MigrationService = require('lib/services/MigrationService');
const { toSystemSlashes } = require('lib/path-utils.js');
@ -642,15 +643,21 @@ class BaseApplication {
initArgs = Object.assign(initArgs, extraFlags);
this.logger_.addTarget('file', { path: `${profileDir}/log.txt` });
if (Setting.value('env') === 'dev' && Setting.value('appType') === 'desktop') this.logger_.addTarget('console', { level: Logger.LEVEL_DEBUG });
this.logger_.setLevel(initArgs.logLevel);
reg.setLogger(this.logger_);
reg.dispatch = () => {};
BaseService.logger_ = this.logger_;
this.dbLogger_.addTarget('file', { path: `${profileDir}/log-database.txt` });
this.dbLogger_.setLevel(initArgs.logLevel);
if (Setting.value('env') === 'dev' && Setting.value('appType') === 'desktop') {
this.logger_.addTarget('console', { level: Logger.LEVEL_DEBUG });
this.dbLogger_.addTarget('console', { level: Logger.LEVEL_WARN });
}
if (Setting.value('env') === 'dev') {
this.dbLogger_.setLevel(Logger.LEVEL_INFO);
}
@ -667,9 +674,9 @@ class BaseApplication {
reg.setDb(this.database_);
BaseModel.setDb(this.database_);
await Setting.load();
await loadKeychainServiceAndSettings(KeychainServiceDriver);
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
this.logger_.info(`Client ID: ${Setting.value('clientId')}`);
if (Setting.value('firstStart')) {
const locale = shim.detectAndSetLocale(Setting);
@ -712,7 +719,6 @@ class BaseApplication {
KvStore.instance().setDb(reg.db());
BaseService.logger_ = this.logger_;
EncryptionService.instance().setLogger(this.logger_);
BaseItem.encryptionService_ = EncryptionService.instance();
DecryptionWorker.instance().setLogger(this.logger_);

View File

@ -18,6 +18,15 @@ class Setting extends BaseModel {
return BaseModel.TYPE_SETTING;
}
static keychainService() {
if (!this.keychainService_) throw new Error('keychainService has not been set!!');
return this.keychainService_;
}
static setKeychainService(s) {
this.keychainService_ = s;
}
static metadata() {
if (this.metadata_) return this.metadata_;
@ -295,7 +304,8 @@ class Setting extends BaseModel {
options: () => themeOptions(),
},
showNoteCounts: { value: true, type: Setting.TYPE_BOOL, public: true, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
showNoteCounts: { value: true, type: Setting.TYPE_BOOL, public: false, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
layoutButtonSequence: {
value: Setting.LAYOUT_ALL,
type: Setting.TYPE_INT,
@ -441,6 +451,7 @@ class Setting extends BaseModel {
collapsedFolderIds: { value: [], type: Setting.TYPE_ARRAY, public: false },
'keychain.supported': { value: -1, type: Setting.TYPE_INT, public: false },
'db.ftsEnabled': { value: -1, type: Setting.TYPE_INT, public: false },
'encryption.enabled': { value: false, type: Setting.TYPE_BOOL, public: false },
'encryption.activeMasterKeyId': { value: '', type: Setting.TYPE_STRING, public: false },
@ -735,7 +746,15 @@ class Setting extends BaseModel {
return md.description(appType);
}
static keys(publicOnly = false, appType = null) {
static isSecureKey(key) {
return this.metadata()[key] && this.metadata()[key].secure === true;
}
static keys(publicOnly = false, appType = null, options = null) {
options = Object.assign({}, {
secureOnly: false,
}, options);
if (!this.keys_) {
const metadata = this.metadata();
this.keys_ = [];
@ -745,12 +764,13 @@ class Setting extends BaseModel {
}
}
if (appType || publicOnly) {
if (appType || publicOnly || options.secureOnly) {
const output = [];
for (let i = 0; i < this.keys_.length; i++) {
const md = this.settingMetadata(this.keys_[i]);
if (publicOnly && !md.public) continue;
if (appType && md.appTypes && md.appTypes.indexOf(appType) < 0) continue;
if (options.secureOnly && !md.secure) continue;
output.push(md.key);
}
return output;
@ -763,22 +783,53 @@ class Setting extends BaseModel {
return this.keys(true).indexOf(key) >= 0;
}
// Low-level method to load a setting directly from the database. Should not be used in most cases.
static loadOne(key) {
return this.modelSelectOne('SELECT * FROM settings WHERE key = ?', [key]);
}
static load() {
this.cancelScheduleSave();
this.cache_ = [];
return this.modelSelectAll('SELECT * FROM settings').then(rows => {
return this.modelSelectAll('SELECT * FROM settings').then(async (rows) => {
this.cache_ = [];
for (let i = 0; i < rows.length; i++) {
const c = rows[i];
const pushItemsToCache = (items) => {
for (let i = 0; i < items.length; i++) {
const c = items[i];
if (!this.keyExists(c.key)) continue;
c.value = this.formatValue(c.key, c.value);
c.value = this.filterValue(c.key, c.value);
if (!this.keyExists(c.key)) continue;
this.cache_.push(c);
c.value = this.formatValue(c.key, c.value);
c.value = this.filterValue(c.key, c.value);
this.cache_.push(c);
}
};
// Keys in the database takes precedence over keys in the keychain because
// they are more likely to be up to date (saving to keychain can fail, but
// saving to database shouldn't). When the keychain works, the secure keys
// are deleted from the database and transfered to the keychain in saveAll().
const rowKeys = rows.map(r => r.key);
const secureKeys = this.keys(false, null, { secureOnly: true });
const secureItems = [];
for (const key of secureKeys) {
if (rowKeys.includes(key)) continue;
const password = await this.keychainService().password(`setting.${key}`);
if (password) {
secureItems.push({
key: key,
value: password,
});
}
}
pushItemsToCache(rows);
pushItemsToCache(secureItems);
this.dispatchUpdateAll();
});
}
@ -878,6 +929,13 @@ class Setting extends BaseModel {
this.setValue(settingKey, o);
}
static async deleteKeychainPasswords() {
const secureKeys = this.keys(false, null, { secureOnly: true });
for (const key of secureKeys) {
await this.keychainService().deletePassword(`setting.${key}`);
}
}
static valueToString(key, value) {
const md = this.settingMetadata(key);
value = this.formatValue(key, value);
@ -947,7 +1005,7 @@ class Setting extends BaseModel {
if (key in this.constants_) {
const v = this.constants_[key];
const output = typeof v === 'function' ? v() : v;
if (output == 'SET_ME') throw new Error(`Setting constant has not been set: ${key}`);
if (output == 'SET_ME') throw new Error(`SET_ME constant has not been set: ${key}`);
return output;
}
@ -1028,7 +1086,7 @@ class Setting extends BaseModel {
}
static async saveAll() {
if (!this.saveTimeoutId_) return Promise.resolve();
if (Setting.autoSaveEnabled && !this.saveTimeoutId_) return Promise.resolve();
this.logger().info('Saving settings...');
clearTimeout(this.saveTimeoutId_);
@ -1039,6 +1097,31 @@ class Setting extends BaseModel {
for (let i = 0; i < this.cache_.length; i++) {
const s = Object.assign({}, this.cache_[i]);
s.value = this.valueToString(s.key, s.value);
if (this.isSecureKey(s.key)) {
// We need to be careful here because there's a bug in the macOS keychain that can
// make it fail to save a password. https://github.com/desktop/desktop/issues/3263
// So we try to set it and if it fails, we set it on the database instead. This is not
// ideal because they won't be crypted, but better than losing all the user's passwords.
// The passwords would be set again on the keychain once it starts working again (probably
// after the user switch their computer off and on again).
//
// Also we don't control what happens on the keychain - the values can be edited or deleted
// outside the application. For that reason, we rewrite it every time the values are saved,
// even if, internally, they haven't changed.
// As an optimisation, we check if the value exists on the keychain before writing it again.
try {
const passwordName = `setting.${s.key}`;
const currentValue = await this.keychainService().password(passwordName);
if (currentValue !== s.value) {
const wasSet = await this.keychainService().setPassword(passwordName, s.value);
if (wasSet) continue;
}
} catch (error) {
this.logger().error(`Could not set setting on the keychain. Will be saved to database instead: ${s.key}:`, error);
}
}
queries.push(Database.insertQuery(this.tableName(), s));
}
@ -1052,8 +1135,12 @@ class Setting extends BaseModel {
if (this.saveTimeoutId_) clearTimeout(this.saveTimeoutId_);
this.saveTimeoutId_ = setTimeout(() => {
this.saveAll();
this.saveTimeoutId_ = setTimeout(async () => {
try {
await this.saveAll();
} catch (error) {
this.logger().error('Could not save settings', error);
}
}, 500);
}

View File

@ -0,0 +1,21 @@
import KeychainService from './keychain/KeychainService';
const Setting = require('lib/models/Setting');
const { uuid } = require('lib/uuid.js');
// This function takes care of initialising both the keychain service and settings.
//
// Loading the settings became more complicated with the keychain integration. This is because
// the settings needs a keychain service, and the keychain service needs a clientId, which
// is set dynamically and saved to the settings.
// In other words, it's not possible to load the settings without the KS service and it's not
// possible to initialise the KS service without the settings.
// The solution is to fetch just the client ID directly from the database.
export async function loadKeychainServiceAndSettings(KeychainServiceDriver:any) {
const clientIdSetting = await Setting.loadOne('clientId');
const clientId = clientIdSetting ? clientIdSetting.value : uuid.create();
KeychainService.instance().initialize(new KeychainServiceDriver(Setting.value('appId'), clientId));
Setting.setKeychainService(KeychainService.instance());
await Setting.load();
if (!clientIdSetting) Setting.setValue('clientId', clientId);
await KeychainService.instance().detectIfKeychainSupported();
}

View File

@ -0,0 +1,56 @@
import KeychainServiceDriverBase from './KeychainServiceDriverBase';
const Setting = require('lib/models/Setting');
const BaseService = require('lib/services/BaseService');
export default class KeychainService extends BaseService {
private driver:KeychainServiceDriverBase;
private static instance_:KeychainService;
static instance():KeychainService {
if (!this.instance_) this.instance_ = new KeychainService();
return this.instance_;
}
initialize(driver:KeychainServiceDriverBase) {
if (!driver.appId || !driver.clientId) throw new Error('appId and clientId must be set on the KeychainServiceDriver');
this.driver = driver;
}
async setPassword(name:string, password:string):Promise<boolean> {
// Due to a bug in macOS, this may throw an exception "The user name or passphrase you entered is not correct."
// The fix is to open Keychain Access.app. Right-click on the login keychain and try locking it and then unlocking it again.
// https://github.com/atom/node-keytar/issues/76
return this.driver.setPassword(name, password);
}
async password(name:string):Promise<string> {
return this.driver.password(name);
}
async deletePassword(name:string):Promise<void> {
await this.driver.deletePassword(name);
}
async detectIfKeychainSupported() {
this.logger().info('KeychainService: checking if keychain supported');
if (Setting.value('keychain.supported') >= 0) {
this.logger().info('KeychainService: check was already done - skipping. Supported:', Setting.value('keychain.supported'));
return;
}
const passwordIsSet = await this.setPassword('zz_testingkeychain', 'mytest');
if (!passwordIsSet) {
this.logger().info('KeychainService: could not set test password - keychain support will be disabled');
Setting.setValue('keychain.supported', 0);
} else {
const result = await this.password('zz_testingkeychain');
await this.deletePassword('zz_testingkeychain');
this.logger().info('KeychainService: tried to set and get password. Result was:', result);
Setting.setValue('keychain.supported', result === 'mytest' ? 1 : 0);
}
}
}

View File

@ -0,0 +1,17 @@
import KeychainServiceDriverBase from './KeychainServiceDriverBase';
export default class KeychainServiceDriver extends KeychainServiceDriverBase {
async setPassword(/* name:string, password:string*/):Promise<boolean> {
return false;
}
async password(/* name:string*/):Promise<string> {
return null;
}
async deletePassword(/* name:string*/):Promise<void> {
}
}

View File

@ -0,0 +1,17 @@
import KeychainServiceDriverBase from './KeychainServiceDriverBase';
export default class KeychainServiceDriver extends KeychainServiceDriverBase {
async setPassword(/* name:string, password:string*/):Promise<boolean> {
return false;
}
async password(/* name:string*/):Promise<string> {
return null;
}
async deletePassword(/* name:string*/):Promise<void> {
}
}

View File

@ -0,0 +1,44 @@
import KeychainServiceDriverBase from './KeychainServiceDriverBase';
const { shim } = require('lib/shim.js');
// keytar throws an error when system keychain is not present;
// even when keytar itself is installed.
// try/catch to ensure system keychain is present and no error is thrown.
// For now, keychain support is disabled on Linux because when keytar is loaded
// it seems to cause the following error when loading Sharp:
//
// Something went wrong installing the "sharp" module
// /lib/x86_64-linux-gnu/libz.so.1: version `ZLIB_1.2.9' not found (required by /home/travis/build/laurent22/joplin/CliClient/node_modules/sharp/build/Release/../../vendor/lib/libpng16.so.16)
//
// See: https://travis-ci.org/github/laurent22/joplin/jobs/686222036
//
// Also disabled in portable mode obviously.
let keytar:any;
try {
keytar = shim.isLinux() || shim.isPortable() ? null : require('keytar');
} catch (error) {
console.error('Cannot load keytar - keychain support will be disabled', error);
keytar = null;
}
export default class KeychainServiceDriver extends KeychainServiceDriverBase {
async setPassword(name:string, password:string):Promise<boolean> {
if (!keytar) return false;
await keytar.setPassword(`${this.appId}.${name}`, `${this.clientId}@joplin`, password);
return true;
}
async password(name:string):Promise<string> {
if (!keytar) return null;
return keytar.getPassword(`${this.appId}.${name}`, `${this.clientId}@joplin`);
}
async deletePassword(name:string):Promise<void> {
if (!keytar) return;
await keytar.deletePassword(`${this.appId}.${name}`, `${this.clientId}@joplin`);
}
}

View File

@ -0,0 +1,25 @@
abstract class KeychainServiceDriverBase {
private appId_:string;
private clientId_:string;
constructor(appId:string, clientId:string) {
this.appId_ = appId;
this.clientId_ = clientId;
}
get appId():string {
return this.appId_;
}
get clientId():string {
return this.clientId_;
}
abstract async setPassword(name:string, password:string):Promise<boolean>;
abstract async password(name:string):Promise<string>;
abstract async deletePassword(name:string):Promise<void>;
}
export default KeychainServiceDriverBase;

View File

@ -452,6 +452,7 @@ function shimInit() {
shim.pathRelativeToCwd = (path) => {
return toRelative(process.cwd(), path);
};
}
module.exports = { shimInit };

View File

@ -5,7 +5,7 @@ const shim = {};
shim.isNode = () => {
if (typeof process === 'undefined') return false;
if (shim.isElectron()) return true;
return process.title == 'node';
return process.title == 'node' || (process.title && process.title.indexOf('gulp') === 0);
};
shim.isReactNative = () => {

View File

@ -18,7 +18,7 @@ utils.execCommand = function(command) {
const exec = require('child_process').exec;
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
exec(command, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
if (error) {
if (error.signal == 'SIGTERM') {
resolve('Process was killed');

View File

@ -44,6 +44,11 @@
"ElectronClient/gui/NoteEditor/utils/useDropHandler.js",
"ElectronClient/gui/NoteEditor/utils/useMessageHandler.js",
"ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js",
"ReactNativeClient/lib/services/keychain/KeychainService.js",
"ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js",
"ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js",
"ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js",
"ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js",
"locales/*.json",
"log.txt",
"package-lock.json",