mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
This commit is contained in:
parent
5082181c49
commit
26ce102113
@ -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
6
.gitignore
vendored
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
|
16
CliClient/package-lock.json
generated
16
CliClient/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
60
CliClient/tests/services_keychainService.js
Normal file
60
CliClient/tests/services_keychainService.js
Normal 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');
|
||||
}));
|
||||
|
||||
});
|
@ -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;
|
||||
|
@ -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}`);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
227
ElectronClient/package-lock.json
generated
227
ElectronClient/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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_);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
21
ReactNativeClient/lib/services/SettingUtils.ts
Normal file
21
ReactNativeClient/lib/services/SettingUtils.ts
Normal 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();
|
||||
}
|
56
ReactNativeClient/lib/services/keychain/KeychainService.ts
Normal file
56
ReactNativeClient/lib/services/keychain/KeychainService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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`);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -452,6 +452,7 @@ function shimInit() {
|
||||
shim.pathRelativeToCwd = (path) => {
|
||||
return toRelative(process.cwd(), path);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
module.exports = { shimInit };
|
||||
|
@ -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 = () => {
|
||||
|
@ -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');
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user