You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	This commit is contained in:
		| @@ -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", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user