You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-17 23:27:48 +02:00
Compare commits
13 Commits
mobile_plu
...
e2ee_info_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
118d65e819 | ||
|
|
d302eb6fc1 | ||
|
|
b92427dca3 | ||
|
|
4e0e159427 | ||
|
|
b87f673317 | ||
|
|
9b15b9b8eb | ||
|
|
2a78f606be | ||
|
|
5801f2373e | ||
|
|
1bf17a6d87 | ||
|
|
05e4b32d9b | ||
|
|
400ede122d | ||
|
|
0d930128c4 | ||
|
|
c8ad3ffaf0 |
@@ -69,6 +69,9 @@ readme/
|
|||||||
packages/app-cli/app/LinkSelector.d.ts
|
packages/app-cli/app/LinkSelector.d.ts
|
||||||
packages/app-cli/app/LinkSelector.js
|
packages/app-cli/app/LinkSelector.js
|
||||||
packages/app-cli/app/LinkSelector.js.map
|
packages/app-cli/app/LinkSelector.js.map
|
||||||
|
packages/app-cli/app/command-e2ee.d.ts
|
||||||
|
packages/app-cli/app/command-e2ee.js
|
||||||
|
packages/app-cli/app/command-e2ee.js.map
|
||||||
packages/app-cli/app/command-settingschema.d.ts
|
packages/app-cli/app/command-settingschema.d.ts
|
||||||
packages/app-cli/app/command-settingschema.js
|
packages/app-cli/app/command-settingschema.js
|
||||||
packages/app-cli/app/command-settingschema.js.map
|
packages/app-cli/app/command-settingschema.js.map
|
||||||
@@ -195,6 +198,9 @@ packages/app-desktop/gui/DialogTitle.js.map
|
|||||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.d.ts
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.js
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.js.map
|
||||||
packages/app-desktop/gui/ErrorBoundary.d.ts
|
packages/app-desktop/gui/ErrorBoundary.d.ts
|
||||||
packages/app-desktop/gui/ErrorBoundary.js
|
packages/app-desktop/gui/ErrorBoundary.js
|
||||||
packages/app-desktop/gui/ErrorBoundary.js.map
|
packages/app-desktop/gui/ErrorBoundary.js.map
|
||||||
@@ -720,6 +726,9 @@ packages/app-mobile/components/screens/Note.js.map
|
|||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||||
|
packages/app-mobile/components/screens/encryption-config.d.ts
|
||||||
|
packages/app-mobile/components/screens/encryption-config.js
|
||||||
|
packages/app-mobile/components/screens/encryption-config.js.map
|
||||||
packages/app-mobile/root.d.ts
|
packages/app-mobile/root.d.ts
|
||||||
packages/app-mobile/root.js
|
packages/app-mobile/root.js
|
||||||
packages/app-mobile/root.js.map
|
packages/app-mobile/root.js.map
|
||||||
@@ -942,6 +951,9 @@ packages/lib/markdownUtils2.test.js.map
|
|||||||
packages/lib/markupLanguageUtils.d.ts
|
packages/lib/markupLanguageUtils.d.ts
|
||||||
packages/lib/markupLanguageUtils.js
|
packages/lib/markupLanguageUtils.js
|
||||||
packages/lib/markupLanguageUtils.js.map
|
packages/lib/markupLanguageUtils.js.map
|
||||||
|
packages/lib/migrations/40.d.ts
|
||||||
|
packages/lib/migrations/40.js
|
||||||
|
packages/lib/migrations/40.js.map
|
||||||
packages/lib/models/Alarm.d.ts
|
packages/lib/models/Alarm.d.ts
|
||||||
packages/lib/models/Alarm.js
|
packages/lib/models/Alarm.js
|
||||||
packages/lib/models/Alarm.js.map
|
packages/lib/models/Alarm.js.map
|
||||||
@@ -1062,6 +1074,9 @@ packages/lib/services/DecryptionWorker.js.map
|
|||||||
packages/lib/services/EncryptionService.d.ts
|
packages/lib/services/EncryptionService.d.ts
|
||||||
packages/lib/services/EncryptionService.js
|
packages/lib/services/EncryptionService.js
|
||||||
packages/lib/services/EncryptionService.js.map
|
packages/lib/services/EncryptionService.js.map
|
||||||
|
packages/lib/services/EncryptionService.test.d.ts
|
||||||
|
packages/lib/services/EncryptionService.test.js
|
||||||
|
packages/lib/services/EncryptionService.test.js.map
|
||||||
packages/lib/services/ExternalEditWatcher.d.ts
|
packages/lib/services/ExternalEditWatcher.d.ts
|
||||||
packages/lib/services/ExternalEditWatcher.js
|
packages/lib/services/ExternalEditWatcher.js
|
||||||
packages/lib/services/ExternalEditWatcher.js.map
|
packages/lib/services/ExternalEditWatcher.js.map
|
||||||
@@ -1083,6 +1098,9 @@ packages/lib/services/KvStore.js.map
|
|||||||
packages/lib/services/MigrationService.d.ts
|
packages/lib/services/MigrationService.d.ts
|
||||||
packages/lib/services/MigrationService.js
|
packages/lib/services/MigrationService.js
|
||||||
packages/lib/services/MigrationService.js.map
|
packages/lib/services/MigrationService.js.map
|
||||||
|
packages/lib/services/MigrationService.test.d.ts
|
||||||
|
packages/lib/services/MigrationService.test.js
|
||||||
|
packages/lib/services/MigrationService.test.js.map
|
||||||
packages/lib/services/NavService.d.ts
|
packages/lib/services/NavService.d.ts
|
||||||
packages/lib/services/NavService.js
|
packages/lib/services/NavService.js
|
||||||
packages/lib/services/NavService.js.map
|
packages/lib/services/NavService.js.map
|
||||||
@@ -1155,6 +1173,9 @@ packages/lib/services/database/types.js.map
|
|||||||
packages/lib/services/debug/populateDatabase.d.ts
|
packages/lib/services/debug/populateDatabase.d.ts
|
||||||
packages/lib/services/debug/populateDatabase.js
|
packages/lib/services/debug/populateDatabase.js
|
||||||
packages/lib/services/debug/populateDatabase.js.map
|
packages/lib/services/debug/populateDatabase.js.map
|
||||||
|
packages/lib/services/e2ee/utils.d.ts
|
||||||
|
packages/lib/services/e2ee/utils.js
|
||||||
|
packages/lib/services/e2ee/utils.js.map
|
||||||
packages/lib/services/interop/InteropService.d.ts
|
packages/lib/services/interop/InteropService.d.ts
|
||||||
packages/lib/services/interop/InteropService.js
|
packages/lib/services/interop/InteropService.js
|
||||||
packages/lib/services/interop/InteropService.js.map
|
packages/lib/services/interop/InteropService.js.map
|
||||||
@@ -1449,6 +1470,9 @@ packages/lib/services/synchronizer/LockHandler.js.map
|
|||||||
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
||||||
packages/lib/services/synchronizer/MigrationHandler.js
|
packages/lib/services/synchronizer/MigrationHandler.js
|
||||||
packages/lib/services/synchronizer/MigrationHandler.js.map
|
packages/lib/services/synchronizer/MigrationHandler.js.map
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.d.ts
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.js
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.js.map
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
||||||
@@ -1482,12 +1506,15 @@ packages/lib/services/synchronizer/migrations/1.js.map
|
|||||||
packages/lib/services/synchronizer/migrations/2.d.ts
|
packages/lib/services/synchronizer/migrations/2.d.ts
|
||||||
packages/lib/services/synchronizer/migrations/2.js
|
packages/lib/services/synchronizer/migrations/2.js
|
||||||
packages/lib/services/synchronizer/migrations/2.js.map
|
packages/lib/services/synchronizer/migrations/2.js.map
|
||||||
|
packages/lib/services/synchronizer/migrations/3.d.ts
|
||||||
|
packages/lib/services/synchronizer/migrations/3.js
|
||||||
|
packages/lib/services/synchronizer/migrations/3.js.map
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.d.ts
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.js
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.d.ts
|
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js.map
|
|
||||||
packages/lib/services/synchronizer/tools.d.ts
|
packages/lib/services/synchronizer/tools.d.ts
|
||||||
packages/lib/services/synchronizer/tools.js
|
packages/lib/services/synchronizer/tools.js
|
||||||
packages/lib/services/synchronizer/tools.js.map
|
packages/lib/services/synchronizer/tools.js.map
|
||||||
@@ -1500,6 +1527,9 @@ packages/lib/services/synchronizer/utils/types.js.map
|
|||||||
packages/lib/shim.d.ts
|
packages/lib/shim.d.ts
|
||||||
packages/lib/shim.js
|
packages/lib/shim.js
|
||||||
packages/lib/shim.js.map
|
packages/lib/shim.js.map
|
||||||
|
packages/lib/testing/syncTargetUtils.d.ts
|
||||||
|
packages/lib/testing/syncTargetUtils.js
|
||||||
|
packages/lib/testing/syncTargetUtils.js.map
|
||||||
packages/lib/testing/test-utils-synchronizer.d.ts
|
packages/lib/testing/test-utils-synchronizer.d.ts
|
||||||
packages/lib/testing/test-utils-synchronizer.js
|
packages/lib/testing/test-utils-synchronizer.js
|
||||||
packages/lib/testing/test-utils-synchronizer.js.map
|
packages/lib/testing/test-utils-synchronizer.js.map
|
||||||
|
|||||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -54,6 +54,9 @@ lerna-debug.log
|
|||||||
packages/app-cli/app/LinkSelector.d.ts
|
packages/app-cli/app/LinkSelector.d.ts
|
||||||
packages/app-cli/app/LinkSelector.js
|
packages/app-cli/app/LinkSelector.js
|
||||||
packages/app-cli/app/LinkSelector.js.map
|
packages/app-cli/app/LinkSelector.js.map
|
||||||
|
packages/app-cli/app/command-e2ee.d.ts
|
||||||
|
packages/app-cli/app/command-e2ee.js
|
||||||
|
packages/app-cli/app/command-e2ee.js.map
|
||||||
packages/app-cli/app/command-settingschema.d.ts
|
packages/app-cli/app/command-settingschema.d.ts
|
||||||
packages/app-cli/app/command-settingschema.js
|
packages/app-cli/app/command-settingschema.js
|
||||||
packages/app-cli/app/command-settingschema.js.map
|
packages/app-cli/app/command-settingschema.js.map
|
||||||
@@ -180,6 +183,9 @@ packages/app-desktop/gui/DialogTitle.js.map
|
|||||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.d.ts
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.js
|
||||||
|
packages/app-desktop/gui/EncryptionConfigScreen.js.map
|
||||||
packages/app-desktop/gui/ErrorBoundary.d.ts
|
packages/app-desktop/gui/ErrorBoundary.d.ts
|
||||||
packages/app-desktop/gui/ErrorBoundary.js
|
packages/app-desktop/gui/ErrorBoundary.js
|
||||||
packages/app-desktop/gui/ErrorBoundary.js.map
|
packages/app-desktop/gui/ErrorBoundary.js.map
|
||||||
@@ -705,6 +711,9 @@ packages/app-mobile/components/screens/Note.js.map
|
|||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||||
|
packages/app-mobile/components/screens/encryption-config.d.ts
|
||||||
|
packages/app-mobile/components/screens/encryption-config.js
|
||||||
|
packages/app-mobile/components/screens/encryption-config.js.map
|
||||||
packages/app-mobile/root.d.ts
|
packages/app-mobile/root.d.ts
|
||||||
packages/app-mobile/root.js
|
packages/app-mobile/root.js
|
||||||
packages/app-mobile/root.js.map
|
packages/app-mobile/root.js.map
|
||||||
@@ -927,6 +936,9 @@ packages/lib/markdownUtils2.test.js.map
|
|||||||
packages/lib/markupLanguageUtils.d.ts
|
packages/lib/markupLanguageUtils.d.ts
|
||||||
packages/lib/markupLanguageUtils.js
|
packages/lib/markupLanguageUtils.js
|
||||||
packages/lib/markupLanguageUtils.js.map
|
packages/lib/markupLanguageUtils.js.map
|
||||||
|
packages/lib/migrations/40.d.ts
|
||||||
|
packages/lib/migrations/40.js
|
||||||
|
packages/lib/migrations/40.js.map
|
||||||
packages/lib/models/Alarm.d.ts
|
packages/lib/models/Alarm.d.ts
|
||||||
packages/lib/models/Alarm.js
|
packages/lib/models/Alarm.js
|
||||||
packages/lib/models/Alarm.js.map
|
packages/lib/models/Alarm.js.map
|
||||||
@@ -1047,6 +1059,9 @@ packages/lib/services/DecryptionWorker.js.map
|
|||||||
packages/lib/services/EncryptionService.d.ts
|
packages/lib/services/EncryptionService.d.ts
|
||||||
packages/lib/services/EncryptionService.js
|
packages/lib/services/EncryptionService.js
|
||||||
packages/lib/services/EncryptionService.js.map
|
packages/lib/services/EncryptionService.js.map
|
||||||
|
packages/lib/services/EncryptionService.test.d.ts
|
||||||
|
packages/lib/services/EncryptionService.test.js
|
||||||
|
packages/lib/services/EncryptionService.test.js.map
|
||||||
packages/lib/services/ExternalEditWatcher.d.ts
|
packages/lib/services/ExternalEditWatcher.d.ts
|
||||||
packages/lib/services/ExternalEditWatcher.js
|
packages/lib/services/ExternalEditWatcher.js
|
||||||
packages/lib/services/ExternalEditWatcher.js.map
|
packages/lib/services/ExternalEditWatcher.js.map
|
||||||
@@ -1068,6 +1083,9 @@ packages/lib/services/KvStore.js.map
|
|||||||
packages/lib/services/MigrationService.d.ts
|
packages/lib/services/MigrationService.d.ts
|
||||||
packages/lib/services/MigrationService.js
|
packages/lib/services/MigrationService.js
|
||||||
packages/lib/services/MigrationService.js.map
|
packages/lib/services/MigrationService.js.map
|
||||||
|
packages/lib/services/MigrationService.test.d.ts
|
||||||
|
packages/lib/services/MigrationService.test.js
|
||||||
|
packages/lib/services/MigrationService.test.js.map
|
||||||
packages/lib/services/NavService.d.ts
|
packages/lib/services/NavService.d.ts
|
||||||
packages/lib/services/NavService.js
|
packages/lib/services/NavService.js
|
||||||
packages/lib/services/NavService.js.map
|
packages/lib/services/NavService.js.map
|
||||||
@@ -1140,6 +1158,9 @@ packages/lib/services/database/types.js.map
|
|||||||
packages/lib/services/debug/populateDatabase.d.ts
|
packages/lib/services/debug/populateDatabase.d.ts
|
||||||
packages/lib/services/debug/populateDatabase.js
|
packages/lib/services/debug/populateDatabase.js
|
||||||
packages/lib/services/debug/populateDatabase.js.map
|
packages/lib/services/debug/populateDatabase.js.map
|
||||||
|
packages/lib/services/e2ee/utils.d.ts
|
||||||
|
packages/lib/services/e2ee/utils.js
|
||||||
|
packages/lib/services/e2ee/utils.js.map
|
||||||
packages/lib/services/interop/InteropService.d.ts
|
packages/lib/services/interop/InteropService.d.ts
|
||||||
packages/lib/services/interop/InteropService.js
|
packages/lib/services/interop/InteropService.js
|
||||||
packages/lib/services/interop/InteropService.js.map
|
packages/lib/services/interop/InteropService.js.map
|
||||||
@@ -1434,6 +1455,9 @@ packages/lib/services/synchronizer/LockHandler.js.map
|
|||||||
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
||||||
packages/lib/services/synchronizer/MigrationHandler.js
|
packages/lib/services/synchronizer/MigrationHandler.js
|
||||||
packages/lib/services/synchronizer/MigrationHandler.js.map
|
packages/lib/services/synchronizer/MigrationHandler.js.map
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.d.ts
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.js
|
||||||
|
packages/lib/services/synchronizer/MigrationHandler.test.js.map
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
||||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
||||||
@@ -1467,12 +1491,15 @@ packages/lib/services/synchronizer/migrations/1.js.map
|
|||||||
packages/lib/services/synchronizer/migrations/2.d.ts
|
packages/lib/services/synchronizer/migrations/2.d.ts
|
||||||
packages/lib/services/synchronizer/migrations/2.js
|
packages/lib/services/synchronizer/migrations/2.js
|
||||||
packages/lib/services/synchronizer/migrations/2.js.map
|
packages/lib/services/synchronizer/migrations/2.js.map
|
||||||
|
packages/lib/services/synchronizer/migrations/3.d.ts
|
||||||
|
packages/lib/services/synchronizer/migrations/3.js
|
||||||
|
packages/lib/services/synchronizer/migrations/3.js.map
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.d.ts
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.js
|
||||||
|
packages/lib/services/synchronizer/syncTargetInfoUtils.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.d.ts
|
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
|
||||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js.map
|
|
||||||
packages/lib/services/synchronizer/tools.d.ts
|
packages/lib/services/synchronizer/tools.d.ts
|
||||||
packages/lib/services/synchronizer/tools.js
|
packages/lib/services/synchronizer/tools.js
|
||||||
packages/lib/services/synchronizer/tools.js.map
|
packages/lib/services/synchronizer/tools.js.map
|
||||||
@@ -1485,6 +1512,9 @@ packages/lib/services/synchronizer/utils/types.js.map
|
|||||||
packages/lib/shim.d.ts
|
packages/lib/shim.d.ts
|
||||||
packages/lib/shim.js
|
packages/lib/shim.js
|
||||||
packages/lib/shim.js.map
|
packages/lib/shim.js.map
|
||||||
|
packages/lib/testing/syncTargetUtils.d.ts
|
||||||
|
packages/lib/testing/syncTargetUtils.js
|
||||||
|
packages/lib/testing/syncTargetUtils.js.map
|
||||||
packages/lib/testing/test-utils-synchronizer.d.ts
|
packages/lib/testing/test-utils-synchronizer.d.ts
|
||||||
packages/lib/testing/test-utils-synchronizer.js
|
packages/lib/testing/test-utils-synchronizer.js
|
||||||
packages/lib/testing/test-utils-synchronizer.js.map
|
packages/lib/testing/test-utils-synchronizer.js.map
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import EncryptionService from '@joplin/lib/services/EncryptionService';
|
||||||
|
import { setupAndDisableEncryption, generateMasterKeyAndEnableEncryption, loadMasterKeysFromSettings } from '@joplin/lib/services/e2ee/utils';
|
||||||
|
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
||||||
|
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import shim from '@joplin/lib/shim';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { encryptionEnabled } from '@joplin/lib/services/synchronizer/syncTargetInfoUtils';
|
||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
|
||||||
const EncryptionService = require('@joplin/lib/services/EncryptionService').default;
|
|
||||||
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker').default;
|
|
||||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
|
||||||
const Setting = require('@joplin/lib/models/Setting').default;
|
|
||||||
const shim = require('@joplin/lib/shim').default;
|
|
||||||
const pathUtils = require('@joplin/lib/path-utils');
|
const pathUtils = require('@joplin/lib/path-utils');
|
||||||
const imageType = require('image-type');
|
const imageType = require('image-type');
|
||||||
const readChunk = require('read-chunk');
|
const readChunk = require('read-chunk');
|
||||||
@@ -28,10 +30,10 @@ class Command extends BaseCommand {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args: any) {
|
||||||
const options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
const askForMasterKey = async error => {
|
const askForMasterKey = async (error: any) => {
|
||||||
const masterKeyId = error.masterKeyId;
|
const masterKeyId = error.masterKeyId;
|
||||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -39,7 +41,7 @@ class Command extends BaseCommand {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKeyId, password);
|
Setting.setObjectValue('encryption.passwordCache', masterKeyId, password);
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,12 +95,12 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
await generateMasterKeyAndEnableEncryption(EncryptionService.instance(), password);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.command === 'disable') {
|
if (args.command === 'disable') {
|
||||||
await EncryptionService.instance().disableEncryption();
|
await setupAndDisableEncryption();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.command === 'status') {
|
if (args.command === 'status') {
|
||||||
this.stdout(_('Encryption is: %s', Setting.value('encryption.enabled') ? _('Enabled') : _('Disabled')));
|
this.stdout(_('Encryption is: %s', encryptionEnabled() ? _('Enabled') : _('Disabled')));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +157,9 @@ class Command extends BaseCommand {
|
|||||||
const targetPath = args.path;
|
const targetPath = args.path;
|
||||||
if (!targetPath) throw new Error('Please specify the sync target path.');
|
if (!targetPath) throw new Error('Please specify the sync target path.');
|
||||||
|
|
||||||
const dirPaths = function(targetPath) {
|
const dirPaths = function(targetPath: string) {
|
||||||
const paths = [];
|
const paths: string[] = [];
|
||||||
fs.readdirSync(targetPath).forEach(path => {
|
fs.readdirSync(targetPath).forEach((path: string) => {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
});
|
});
|
||||||
return paths;
|
return paths;
|
||||||
@@ -187,6 +187,7 @@ class Command extends BaseCommand {
|
|||||||
try {
|
try {
|
||||||
const migrationHandler = new MigrationHandler(
|
const migrationHandler = new MigrationHandler(
|
||||||
sync.api(),
|
sync.api(),
|
||||||
|
reg.db(),
|
||||||
sync.lockHandler(),
|
sync.lockHandler(),
|
||||||
Setting.value('appType'),
|
Setting.value('appType'),
|
||||||
Setting.value('clientId')
|
Setting.value('clientId')
|
||||||
|
|||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/2/e2ee/locks/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/2/e2ee/locks/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/2/e2ee/temp/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/2/e2ee/temp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/2/normal/locks/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/2/normal/locks/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/2/normal/temp/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/2/normal/temp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
2020-07-16: In the new sync format, the version number is stored in /info.json. However, for backward compatibility, we need to keep the old version.txt file here, otherwise old clients will automatically recreate it, and assume a sync target version 1. So we keep it here but set its value to "2", so that old clients know that they need to be upgraded. This directory can be removed after a year or so, once we are confident that all clients have been upgraded to recent versions.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
id: 049906e6805e42ee8794a4aec9d4f4c9
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.685Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c200027c{"iv":"n00mA1fNDl0q5XwaVFHbBQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"MFSpyyISsMiCFSvlyAUS9VjApB/wPq8Gfx3gf4mjBjLgbNMirqrqv4jn6BMB7ZjQN2rzAo0mi0zlPiAkWYyOBiEuYCUE522ZInW5B/aXmdk5LMCSlXNEXYq7MT46ySKuaITzuDfrsrwqHwZchxWuK/wK3QTcCifZmuu80kUfCzIhx7gVfaqvhbMR246qCa8h5toiWESd0MetVgnwXfadLOGrwc50J8Z51MfBDTHT/Zxf/1f3AO8x9ascsXWesB1d41jINf2Uhuo0j4DoRawNIIhOvhDywTOSNo2ALK4SJeWm2rK83s2/nF9vaxcRokJRO7jSfdcMorqBdxSoww9G2apvxs2rJT/yVpGNlhJJvBhCAFlt8u3MIOSxop9aXqujGCR0aT2HQ2AdM7X0IgDZJXoUKODfdUYim4MbvHbXmWfk9vG8izDF7fTu6zCSLCXThXOYI1enTHByTRf35aJI2RbroMgJHA0QIorOPWEoEML9wCrKDKhZ"}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
parent_id:
|
||||||
|
type_: 5
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 06d8599d079e4bca91a9e8e6233ffca8
|
||||||
|
note_id: fbad304e76454894a44ac57fb46c8007
|
||||||
|
tag_id: 049906e6805e42ee8794a4aec9d4f4c9
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.686Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002d8{"iv":"UxbbBkb0easQ9ZxLn4SNUQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"poUjWTnldGEm397xoU6BcqaIM4G/Gz+4ZtAhsN3vLgWYQq4Cf+nXDUk8vCWAuFBkBWKDTfEnFv4cu1KuFq2mn80H/CdtoDLeJqpyTQ/bKL78ZdWtwKjfHGiJCOSybu8XRT7WHf3jnjQeq+549YyoOCPYcShBPEX32Sgoc7m5AmbSSgNculx9DXMzIUupAaAaTUlp4oxOMLR9RT10E+oNou9KQdt+/HVS/8Yq+ZvhcUfzecwwvc6gdSoQi1GMKweBYuYU7f6otfaqsYQ5WrmRS3JOROa4YtA3LHwdXGiAu1EvOyCYgB+OQ+uMqUyFoaRJqoJWe3gjSmnR5Cx8aYRrW5MtWy9QRWz0w2bTJT0I27w8nDyvzyGMim6LwXyxYv36g7tD8uFmRZsjMcxLAMvuE+zx5dEdQR0IcCHjREDrU6qs5NjcGnvrneMuUcWX3g5Ih9q6uTtxkkCTXB5CDgY9TFjoBL7osObbe9y8JGAlPFkCbcOjgeIzHxyDbYpMuCA3/SMBYOu41Uh+Dhijj9f1D2y8LDDvKePGHAHH/dKEQdqLvmT+0NovZmc6D8+gj+LLJdqaxTSv9l2eRA=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: 1cbed8070dd3431bb1d6f341c2f6fa9b
|
||||||
|
parent_id: 3903d5125217468bb781c409fe6a693d
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.688Z
|
||||||
|
is_conflict:
|
||||||
|
latitude:
|
||||||
|
longitude:
|
||||||
|
altitude:
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo:
|
||||||
|
todo_due:
|
||||||
|
todo_completed:
|
||||||
|
source:
|
||||||
|
source_application:
|
||||||
|
application_data:
|
||||||
|
order:
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20004ac{"iv":"3HpsKm/uN84zD7tg1Oy0rg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"zQBIUOWeatdlVug1AZOkn2oO1htfQHbmokWAeMajMjmMQ+S87gZNOrTD7z3Rnfz3EMAakSunUrdmoPtNXl7Wwgr6TUeBslhxcBoHKEBUp/xPdvFzaH6IX3gMhQy5Sp3SxQia8S3aitS/vWdZFjk6pEE0B8TtVoUB4s51ofAm0XVemwHl/SejkbG4sBXasEZHI8xDOgTywbyYVn1FGLsHwFlhG1guj8omXBHf3c9bASSpyqWqIxEyTLrMHJlKPUzQR1Ezt8eHZwiW5AUhocDHBD7AdZlrNvC18PjMYT+/w8ZrwZ4+cLVV0ZDR916E1jdVrcQ7DtsMgdddUSFoL8UMi4/if0IvhvD5tT2W9fdH/Ln7tkVyedRkWh8ylSvkq/1e+pQxca+QEawzcDmAgrUi3oGq1BvAikbDJS+MF2ATATunybqboEhnb0crlDtCCVh2fUUYyCqt2KEqe9r9X0GBympieugI1+jzFeoCyReGkaTIgc5tF084A++8gU0gXBj1y1OZqa3rvaBFkq+8PHWeR8aMwuy/PG69wPn409ZP39dCyl15J83tT10r4v1CMy+7EMYS70R1KGRmfzm1oA1SxpcJipIJEr2TOxD2jdjHuuKeNljtiFFvasQeDv8EMzSIu2d5952tTJRong0+qpXtH5YA+WNavsHYGbmQ7rsrnjUAdslIv+R6A4gdEtT26xeo86KtFWZKxCeGRK9kXBsrsXCE7v9m3zNPENet2EM3i+zs4PIZ8Z5XCiDH6cFokeBUQM9G33I7DpIjmh2rZoy6mc6bwXxjeobfx3fmvUYIKtmRfh+ra0kBvKYIDe9LR7pj0jkH4seXvwyjKtND8+b/MNhGaHGI6jTRvhJl3dX2IZxkHh54r7WYzUgCdsp0SNVldm218rlB/w1eVgs/pXWzUr4nvv3VOEAQzkok8MMJPEYz/MxjB9EAR2jVyjQGY8p74HHD+gB1wVhI2UJrnJCbHXWiRqcEW4hGbo7AFjelXRIHt9slv4eCoGAdWmaYMYqe0cgECs7X5N0QbCSRthMmDIpup3EDVl2L8Q0E"}
|
||||||
|
encryption_applied: 1
|
||||||
|
markup_language:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 3903d5125217468bb781c409fe6a693d
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.557Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000298{"iv":"MofHz+Hj0PN//BzBBqjBCQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"Oq08E7jXlTbPpoAAA8kB+XJ+9B+J14j/cD99EkgXpfgRS9VC0uzk+EkgACaGc5l/5cIPSKDSmKpYSXabfjXily6ju0TI+YJp9atwdOl+EfFBhW5eVOtwhmGOXjfzWvKw3/Js1vUi8gSK1j8WTAEI3y0MpCbPOHb4dDu3jJ0f6kI7hAbjpd4lZxiLPgW4D2mkZjK6hUmeqyBTJrAOa1t5DrMukfLy5g3842VziZafw9oj/b6GdN4KhEHZwHs5P18Qdvjy/738rW+IYwGHGsMx0y9rq4Tc3o9oC71FWVD8BuWIr2bBnYUH6mDfB4EbedoSAc/G1XXn6vZAAY4p/iIZkdjYHGVfZHAX6aIzHBiatQXvhFnhefUqR6eGK6lvKCOVj5yEnLjWgZthIiYftwBM4BAY6XkEL1UZoJtJ8j9ZAFH6VQGzhll+7VJL8Snhbfs1AIDBwYQswxYE43dJ5I/N6TojZ04G+Z+7TXH9dslK24F9eIvk8xBhanjQk8z9Iua7EtizgberLQKF9Jk="}
|
||||||
|
encryption_applied: 1
|
||||||
|
parent_id:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 4714bf1e2bc04edfa32c456bdfdb674d
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.559Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002c8{"iv":"BZ3ouTPKUeAppquqkqJkZQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"oVr2Ai8po6tv8ciPhYGJsNlMpqH1r2lk0G4R/3/Tn093Pk0mVmaAwcIWjrlk6sOTvrA49WZDQrQ9fD6gJ71tfUrSr6OATQ2pnsF4xqIls0GBbioHMhKyMMzrIkvFUBS28qKfv8Ojhc7RxMOFZBseDXZO0E3ocdcxfdOzF0IskRnvIzjTLOJKyHsHe4mA5MBTvWdbXKuO96/JJ/ldLICdlGGVaDyZ1ToLnuww3T+1KGEue6t4ddTc8T9fQYRdeFciPJqGPw2N5p+VjMMA0JK4VbJo+gH3fihwpg3SJ5apDcYRcUMtpnJvWi/hPeqJpgjInn36so6CKN/2YZlqQQTr0tdK9Ko9SVnycsDpubfirpE/eXADovNBO2kLZyn0Taq1+MXbtEaLe42kolQ/9dL6TxUYztwxc4SR+3oM+UXnXwFUvW2lHDQXNLqaojm+Rjet4Nu+bksgumJuhnUmc8FB5LdVLFRPHvmTyqZP8TpLN7h0yfiDW6wW/yQ1R/U7KoJODr1hmRp8ym32H2YAZPK4fyxd5pSE7T3wIF8SRt8elX0HrOwHgNbof09mxxkzfw=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
parent_id: 3903d5125217468bb781c409fe6a693d
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 53fc76b1c3a94e1dafb98bfac07af69b
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.692Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000298{"iv":"lu52Zcf4k4Nv4e/EERw7JQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"YpiWeReRHJY/vIPAjvR09YCwVWC9J+Yn0n6XN/+EDt2qYAzPOKh0QLzuZX9IR1k4S47S2NcmT2DEveZCYAwg9sIiyghxd8yL0+qa+nZUvb6mdSa1s/zACUZUjlsudi9lduStpI39Nhig5OamFgrpkk2iypNOMsFqjUGlNoUc+NPndu0V80/S/Fjoeca+xqgDTbdjxlsgMtq4Rk1KfVcJTCGjbtWoU9jtsXu848iPXTtOZToeEgrbTjSVqBGzPU2v1NEpj516elKngqa7Np32/WrkQAI4PIqk795PuiVa9r1GvP8XGiBrxek/Go/kHOcg0oozx5VvsSdRkP/q8PxIYjLrZ0+3VWipgmoTGTe+8Xeg8fsF2IE0c37jGG/dgH+8PhHCUjqSS5A6uf+Z60UdPky1XlzL+MqQ85f8YzEcy9nB22NvI517lKiT9CKeaK63Th0je4qRw1mrwKzfZdmCNGaraIXnhhGksXmROFVJbVbrj/B12XCATb/qGYFwHfAvt7qgoC75L7oZGsU="}
|
||||||
|
encryption_applied: 1
|
||||||
|
parent_id:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 55fadfe8ecf9411da4c0e842175cd9e1
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.560Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002c8{"iv":"u0WT+phSfRQogAmxUiRHGQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"95XmsRl6+WnBbR0Dfjb8rNtMzUj9THhLm5LdixM5N/18ZkQBR2BFvlcyZq5u+aUsdJ+ZMCUXf294xYb1TWEr3QrYTGpuOwZX42HBksu9IAq5QjHfMh6Gp8hZEHKlO7k84G7i/mvsH7aanQ0EBALoRuxvd3UMZN4JTR6lXOCpwu+H/P/mLErQBx8iU5c4XI3or3fmAQGuhEa4K3S+KIwEbXZT6B3BkDtGQrMk+2HxTB5E2eb8hAnNFsA3zEh5jaYHLAkgmkCVICAck7jtRzryKvuK6T50f4120hT6/WwBKdnqiXjMcNPyVHvNr9k0xkFuZuxIPzp0MCs/bZvB4XCy0fMNe4bOOgpMkiLCyRm32GJe/QXfnhLCp2SWKKnwcs7BbflPeiWFuoFW9zdhLVA9ixfaj0CBLTvmDpecNMaz7pifa5ZZz+/MFXz2Rwhm1sj/DOQpcGtAbWpg7nh00iD52RUY9Lf8LvyVTvDEDSp3iJknF9LJk5Jebxy32O+iOwFnta0twfhKzdaEWp5cy21jDgcWE6omo0renuQGP+p3AUoJrf8nZVGF0YpYBuikOQ=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
parent_id: 3903d5125217468bb781c409fe6a693d
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: 77d3f99b018a4d448a9ac349d70d191d
|
||||||
|
parent_id: 55fadfe8ecf9411da4c0e842175cd9e1
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.673Z
|
||||||
|
is_conflict:
|
||||||
|
latitude:
|
||||||
|
longitude:
|
||||||
|
altitude:
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo:
|
||||||
|
todo_due:
|
||||||
|
todo_completed:
|
||||||
|
source:
|
||||||
|
source_application:
|
||||||
|
application_data:
|
||||||
|
order:
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000504{"iv":"yk/Rry9MdNPPZsseZVBOAw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"4rIuv8fyTYKwx7vcWHTVhDJUN9spWapy1BOYFl0l1XcBGk8sn6kFERBclrDyPgpXOYpS2TL4OOAtZpDYSf1G1HfQGGIdE5DQY8f+EMv8/R4y/iGj1SBnJTUJYT9MgBnz406wWTfI02VV9bGgNNtyVqGExSidl8Te4bu7zIE4Hk2hPipGs4XdZxQCmrpmw80ZcqabErB0Il6rP0VmvSHvnwwcCBO4o8LQWyYD5EdqDxRAIe6XDyNV8sAfsOuOZXN0IxQeggFSYBAg5KCZpHmpWwRXNGkmJKxgwMKunHsw32+8yz0/nSXTjs/tLgxU7dK30JC2wYUt0zdWuXJHzUYNH9U4Y0Iwfck1ILC3ErdlNT+Xn5Dq/C1fAD9tIuvrHHwBXJRjuS0pmI8BRTo4fVFq19sJqjDtNcbcubZnPN4S3e5o/nY8r1yZ7qfGj7H2+FKlYVtJYQqgnnadFUALn3dIf+4lfjngkHx8OvKbaBLt9AKxnusrbtmUtYvNEU+drP1+/DgTMnSZtq8vl53v6lB5dJWhCt0df/gPlHHTkWybBjFCtA0E6MT50/6oD9P5lz4+CKCtI/ICuYmgoOWUmHGrFSJy/WI0rfg9oh9MZjW8VYZWCljgbs09N7AOF8jlSC/7+3/XZS2rU3xLcAG2cR9Z6EBuHNi0aANDjL7Z5zaJyuoBUwkD5H8ZmzycjTuEl+B5kyQjrncjvTCaxPToOiWQ0Dx0pKWVCYX96W1LrFTaW+mqz+Yf/ds9lROaytlWy2B+smYhkh3PZSBVcwfzrWmVCjs6dfJ9ymiNbRHISDtVo6lK77/YVFrzQ2IWnwoc3jI53WjzGGQo0nyKosq7FTV4puD8L7gG3pw+Mj9uYkafyhY8bEKJjTO7N2FOJfxPXq/FhSmWaxVOOABv+wTnLA5jc8yBbRPUz8mYMZDiAlfq86i4NSmtaBWyrkEOjAeyhEjOHK1Pg96sE0Xma16Q672TcKusPJw4UdwpZ9GjitR3b8s7+jCBr/mP/furKHTge6LPNvKMjZFx/r3EQ26GXtygLyDfy1KPVWnCv2gMwPLDMtC0tiZTcdv/LzmAXo3MwFo2y1PWC/krEXXkLaWV7d8FIMp9VGfhrcxZt0MyiW59ANuyM9bOBXZets8Avk/r"}
|
||||||
|
encryption_applied: 1
|
||||||
|
markup_language:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 7ac75ff5191049daaa48c3611f913e9d
|
||||||
|
note_id: 1cbed8070dd3431bb1d6f341c2f6fa9b
|
||||||
|
tag_id: 049906e6805e42ee8794a4aec9d4f4c9
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.690Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002d8{"iv":"q3o7YEgiv8EHVC6fQA1caA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"4ePkY/4wiSOreCbWW9ln0/GppkSM+D0ONPYxBXdqiTv3M/x5gEiXHTYg/J2+XnkUowT7prxtnktw3wh5wNf2LnZJV9WFO79Z0uBT1oVgv/Q/7VsHJSlA+4b79pUYl33e7YYeIV87fUTeESJe1YpDn2HzgFQ6eqQ24VUYJH6OQObubWBPFcUieecqXJCBkYmiJHP8d0XuKfUOtr0bGcQ3ZNZ4gTW35oAwHeIKRjeGsRXa0v8Ci68pTflomltk5C9uEk4blbJ/cQ5WVGWS639NXdz96N3DBbxZXUCZ6ZWKRY+RI6XvYKx4hTELtCxBSdXQmzSO/EzU5g9ncUPpKZLOATcXxiY1Q5ATfIqjvBdetr5hQtmniixi6gvmena62ycVXmqsZw9CU7Tjb9gBuetXl0JT14oF+6w9Z/r5abysYkLTE/cxwcGwdaXohiT6lkiMkyg/k5hOMwqXyKoHq++eonf6gtO8oOUXdTotkujNY/3jvCKxpscZIlwJcFe2kE654d0PF9gXdvptG8z3vH4UUYDbI2EQJAqtxwlvwG8mjYYNLhWmMdv9260n/0nDyuf5286cXOTeKgmI0Q=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: 84b9ed05499d44e58eb448c8a9227adc
|
||||||
|
parent_id: 55fadfe8ecf9411da4c0e842175cd9e1
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.680Z
|
||||||
|
is_conflict:
|
||||||
|
latitude:
|
||||||
|
longitude:
|
||||||
|
altitude:
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo:
|
||||||
|
todo_due:
|
||||||
|
todo_completed:
|
||||||
|
source:
|
||||||
|
source_application:
|
||||||
|
application_data:
|
||||||
|
order:
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20004ac{"iv":"FhP7Z606mGzQE9mjCxYaWQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"ewBythIJ0ZCP5dHjWNQHaJR3YlDlIPFBT37XF94KiUDFiTuy2cD5vw/8laksPzgOM4cy4qAuW0uh/7gxT8bcFHb4XV4kXkM0HcohSkzmcUPUlSbm7YejK98Mry30i/5t4Jb+ZvLWmpTpl2nMmamjWCk5spd1iOy6lcHQK2JRzhCKd0SC7ZANyjzsLYPzZnfDIbKXKM3hjw94fMuAAjQ/rjFlFriijclvM2r40kqiMjusfc6OPPRwGOCefKUzPQfZ0lDD0CHrRN/WhQkePH/24zhRW7v/Gb70H410vz6F6oZ/A2vIWQR1mYZpETaRoCHGBSFQKdrm4ooAtDmws5pqH2i1LyMmqsvt3wnZssHhoLGgxBSWwvL31zMmnOdJ5wXiI26J5t1fynEMGxvl4T7KTDOgxPWEF1LU1DOeToo2kLB+Ch5c3coYj3psO8qhTwuI7tlMLxeZMLQrV7Kxdk0qeG1yqaI3zcmnLRfCWrJ3NUaaHV3V43GjD+/AWk/Q7pU94weTeQYRUyty0ZIt6X6chCgJE/TYKna2Iaxj71IqJxbH5x8gPgb4YBQKcBfU99r2TsNynFt1RAeykLwuBUpDxR1wk9B4s5/WbLFCptKSMWcvgMvm7o0SmI/H4OZgKQfIhVoGCZCZaXr4CLywwmbb++RD1lFM3afdQzOi00QZeeeWM6u7ZIGvSAHoY7hlsXUv86w1TI1V2vJxuwNgEh/PJFkzajqQY6D5Xf1HXzwZJc9lzYtnhPge3zHkgfLotUxtDCMv8eh+V8WvmbvF27/Crs1QQID2RWYQBpzKJp22s0fh2M448ohF+kXsTuoGiO8SLKvedvdTwN1C3EERy1HQDSsU7s8Ff7POHOIphVlJMDVu3ei0c4CdtiR4d9Oiwh3tJZ6Rhe/LBz3Oe24ZSuTjaoXTH2uRmtVXY84X7zkZzQB85INq+yarBUK+gvMvROwJUwgr9ZUfnBbQBn690P7nJCFSS/pwU8PxiC+baI0bMx0VSqemDy/qEp2RkH9FxbvHKXOwAWFCtDou6ChT8FENjA2zGq04v1UqUEuX"}
|
||||||
|
encryption_applied: 1
|
||||||
|
markup_language:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 8da3ba3ee84f4a938c9df1e05253515f
|
||||||
|
note_id: ff770ee627694f9e9ec08f4a218029be
|
||||||
|
tag_id: 049906e6805e42ee8794a4aec9d4f4c9
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.802Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002d8{"iv":"WP147s7xzhqGVoqB8MPMow==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"OiEdFHraFYd8dZ5al6fX+Q6xlx+A4gW2qV9hRTzvraW05RqBOopjnzyD3JxTW/3zkgJTBfFthGquKraThRFS9NvjPkBdiKQP9gsNcSeYAIBgUiNUMzV69lqpX+OhvWWqbHw6Od5TI2beMp62/5DP+AiX7DoJS1O1f0vp9ZAIxd7/tpcX55wVp0nxGScPFXmWa8YK46qH3jiuOl7y+685//VFtrBRsaP3RLbZDcCyK/G+++eoP0uJvH5N3StwSvHYl0Jhahto0jguo+SRU4Y7GU0CIbOZ9Oree7UPkeqLkY+rTD0DZjhcqAV4cDFaza24AY00bpZlLTBWQ/IJim4jKZAbUyLAToAWH7tsCG39HL/QXoVxCxD3qglkSz9NWUyKoG8NKEmVDotH0bcF1qyMF9R8qj4q/Y1gH5n+AJ4CF+qa0Kd9jBOqk1RW/1brVs5tyVakDuARO5jtobOviIuVqj6dYYVybVSLxxwlIbEJxv3R/Wd85g9+tsJXwveeVXKHTueCCVwYSERb49c6uDpe1NzfU3LTKCPkwk8fu+es4L1uWbfpP+kSbi8+5qJF0k34vMIe4FKvNWZvzQ=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 98cdf9cf320742e995fb43d60f3293a5
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.691Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000298{"iv":"7QhZftaIny56x2hpBml9fA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"hnazZV12stsBUFLOObCr4PvR1ao3Nq4SHLcJQrsSUOKT71/CvSgTbTY5tzQPhEwmDUFuauxj33/n1sg+J4oF6E0Nknmx5FIwtm1jOLHuPdOSFU19MWxE79mctVArAYApxns3Hwqi+/lv7P/tb78Uq8TD4ZzCcfIEkfh+/6pyRSNGURJ827J8hP6rSavClS7eoBa2ThOLk5Wyi7xxs/JnG4olu8BvuSlzgDVDaWJ4NLUhnWP025xUTbj7WPFXzifnXHVzmOTB6syOlfLPLugFsdWAle4MktGOx3TXEidTwB1egNdNh0GCIzG4hrp9Z1k05w7dgBL9s8eE9SkqWLVVCItUhqiJWCy7ROVyHGvqhNdh3kvFWcC0NtlWR3TeqerypCPuJZeZ6e7qgLDYvaAjTsegrI3gGpDfdXdofqmmC5q4PSneXhIqrKqsvrhgiEFliHATaIXcd7H9C/2NyuZaJIeIzSHF+WKFt0KV4wmJJMV4jmPU+u+TAGsTquGk9v2tItUhzL6w+H6Zttg="}
|
||||||
|
encryption_applied: 1
|
||||||
|
parent_id:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: a80f47ed538341c39f5364a8ac04ff09
|
||||||
|
note_id: 77d3f99b018a4d448a9ac349d70d191d
|
||||||
|
tag_id: bdbf83546c0a4cf3a38c103595d62ab8
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.678Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002d8{"iv":"NRKiGSTpI5RKLElU8i1nYA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"D2se1ksArb7asKOOPQMLxJ5qZbhGLHjP5aEryds6WmxYGheLCxKUOiPi1BkEr8SqrQu4B9+wLd6Ra96ARHq6vT23SE3GydNvM9vLU4Jdk54db5Dlv4z3i/WD6hbcpo4VboBO3mhXZV46kgbLJGAgCbtnQb3v/10/oeLfeG3z/zM7+9ByZ53HuMtUJMIbITuST327Ozk34X5a/YcUMTpHXq7Vycx/ofM2PQUCI1E25IgPDnw/Hx4qyW8U/cVn+XfCj051PXYgYhtjAypEZtzntKuvMF5gVKHlA0J4bCONHTHNuMhXs5u1o3MQo/XOP9w/WZeYAh1pmKNDaLyRmsmwZ3j65eTMUAPBO1LRbZUUHS5HwGh+zYwxm9zCTbiuYOu0skBLqX2RRK7IhpuGszxGIr6T+op6gnXiPP9M+89LtbqYEaX0sgs+fwuh+uag6420fxzrkCrn1d972MEn/0GDkGLJAyIpOCiS6xsyWp0AlbD79g86u3wiE3s8p8d6uBQjwpwlRxa5BZEQeZVoqpsap/uRvYPLJNrBPnoeozocTaA3o3GZvqNe8S9YwWX8n7y6uIC6kd+eZ+AmeA=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
id: aac6854be54d4cf2978855c64ae13fb3
|
||||||
|
mime:
|
||||||
|
filename:
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.798Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
file_extension:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000320{"iv":"Uj3j/7TEZVAdm1X3uLHCQw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"aajrSBJmpB7WibYSikGWqrinofwx0MVeDF2uoD3ntzFwOAY8QnPhvQB/Kn2Y5ndGFbHA0NeuUVLYd7bwt3iAjm3ZWtEh5U/vXsKLnk5iIjaQyGYw0GkCuxPvMCkTEvDAfgBHjnzavpRTYFWqM1xDCmzSyPEqnOsN2DkH3bYD6vXN1rehulzSQzT8fm96jQe+9qaXpyYPVd9lukYMIGJ0vJgk+LuwmK5DLkW4mD0smQOnF2Copfek2HxntkpykBMIM7cbzSzJeyvAc8G+nZat/ZPKBItei2jhrcy9VF4mOUAkhsl4yFiLLf9anJEMX73IADAAsoqiHNZVOWMYogXIbOweVmfWcMQIiCQWIQMfygakuSxOldER5/mV79FgdyS+1HdVPDbf3z513eRhejMEzNdtfBCuI01YQcnbe3K1xQLJVI7VEp3DPbPBLL4Nb67U3sthVgNKd8Ow0uxVDQrDpIGYEIQl3ssIbvt1gZBzZbfiTkKSRAHAgZr35zDBV3XTdNBNWAmf0GsNnaNnkfuzIaby3Ov7lfp2JjANeaKlTB4AsculgiUVjXqqWumcaPG/++dzb/0lyuVwpdkJG2/uvQmdDeydueoJSlGaq+5hJ2GCHuW+DI7Z2leWnQp/+rawUeCxHvR4Hkw00usBxycHWy4="}
|
||||||
|
encryption_applied: 1
|
||||||
|
encryption_blob_encrypted:
|
||||||
|
size:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 4
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
id: bdbf83546c0a4cf3a38c103595d62ab8
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.677Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c200027c{"iv":"LTc2Jqoc462JCi78j8WXnQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"VicUNDsaZ7l0mbA2Vi8hTiekMhXsBjrkZR7Idq5YsNcqGhmfmD3+ELKMI1JtqAfkWoplVt0pbBe3i1U30PlSaDOghHshB4y8AJNQZeOYhzAP8w/ND/aMpogfYBM8MgPsxROWM2803H5RP6roDH67FdWLA6xmbUeY53ZVN9h/2KyaJ4LzWrB5NcdjPtTGQB+ynU91mZCN+Z3KjpsBtGXm6DqCxqBunOylxP+WMzE9L2xTUki0ismcNpPejGlNYmloy4Y5f+0ok2EQMdQBGMiS8wjGdmEqJ1zudquS5ITl5rdWwOYDTUap+VMjCSOGB+EQ4a4r/t7xgJnjGuccS3KPnd4gyjw8a+QwWGagzVIFMb9EZIzH5Q8esUFYvdFPaxyk44B7l4rMr725nRNW6cYQ9Kld7t9g/CZrx0aFBQIgRcXeCjcf5NgZwN4hdIJ8HCQBVxDnJszpp4DBLQ1im9lFGQkseQgcbRqyk1j4Bt+HX9JyOcG0nKZQ"}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
parent_id:
|
||||||
|
type_: 5
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
id: c330509f12de47cca64caed7cb7e069c
|
||||||
|
mime:
|
||||||
|
filename:
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.671Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
file_extension:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000320{"iv":"DMeGySLRqW7ZDvJVJ1XLIg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"qGlwrTJM5ec8rHhMi4iMZKCrM1Bz6VnOSHljhLH0aPbrdiygDPbXVlplRpvret8U+i3NbMqy3VmfiL+mQl2Bfy/r4PChTRrNSgPqqdC+D8butSDUEZkHzld2KYgR4hwD+Fy3e+zU40S83rF/XnH1qUrqzPSuS37em+U7AI27bWgXNSX+mNJZ4xUNYLicfwLInqkIGcmnsSbgUDoEkdoq04U9fTy1nHXBL09M6wmLySBNNLAY/YwqG1nFoflSJ6eMH0SXONpyGwvpSlwo1vx7MQKLLcI7+KgEh/2ujCYfBpgyarsqNJN74YqC7lVEa/R9XECi5scJ+2TbTGOPvh0p2R2EFlq5pHp2Y0MMIwrMyzI/iDpzvMMUjPOQ89nsRhSA9ahzS6TtHIcfs6V0i5jwvcC+O4+sAdxnWN3RV3gvyLUb9CoYXHhBvLfxZ27XurtP39UbUZcUP0QHKEAUtcD8LN87eBujK2YIDAA0T41IZUQoxvZC1J9RvyIGFKqy9ELdD1dQ7W0Fh322IWeruH2oy2NnSNF/Hcikj5ggV85G6vJ4bbyvv7pDqXPa5yNs703Di1tiHxtbO8Yb89jWlNvIpIFH5knN7kRMJABWdyzeIFegs8KMuOa/v6ji8iTCZoh1yuAZNX7Fre+voY4aQRYdgSM="}
|
||||||
|
encryption_applied: 1
|
||||||
|
encryption_blob_encrypted:
|
||||||
|
size:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
type_: 4
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: dba25a5bf3534de39854dba8a4ec5351
|
||||||
|
note_id: fbad304e76454894a44ac57fb46c8007
|
||||||
|
tag_id: bdbf83546c0a4cf3a38c103595d62ab8
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.682Z
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20002d8{"iv":"LzlH0FnzEw7HqzZ5ltEkZw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"UazjD4iyPODeDPPOQdsdFEuofk7ZAR7s06kJvkopyTNLTf2vJUqZjdyzXgcr3ju+Nz8pTFnpgIWv287jzOunkqgobwqlHNKTw0XpBJu8Q0k2vGg59PTFOYanz4JlqIfglrXboqnjkMgGYdeIU049/g0iqXmA9xytmRa8JSWpUelThVl2UiRCQwynCrxV7rNtOnxO0v74fX22W5CT703enmScevMmEJpO2fLwIw98Z0X8VPXRyUBMBmBQygxLdKis7Khvk+hwuAL5nPUzqgLaPpWAvn+EZmI5nqqpk9sX0dYHEEnOOWkG4pyNxIItBLLY3W0sybFBwJt96yPuz/Et0WHGdiEjWL6tyDEcq6l5tfbrYI+rqwPxxUOcwWk0aVA+LZ9yvqsp4HBwr7fr92KyelxriPHdBIXBAJae4KI0FHh92YadUcH3Dn6V8WTYzUN1Xbn8NFeoQPPoHHAfs9gRcieyDTRHPlzYprTdyp9b5iBEtRjyZGgO/9b5i9ks8V/61tJhPWIimejgm2w2MdNGnq5APxejIMTRfjXP9hDuArzpdUdMEUKBPcpekR47OEoj7/d565yo/fbw6Q=="}
|
||||||
|
encryption_applied: 1
|
||||||
|
is_shared:
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: fbad304e76454894a44ac57fb46c8007
|
||||||
|
parent_id: 3903d5125217468bb781c409fe6a693d
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.681Z
|
||||||
|
is_conflict:
|
||||||
|
latitude:
|
||||||
|
longitude:
|
||||||
|
altitude:
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo:
|
||||||
|
todo_due:
|
||||||
|
todo_completed:
|
||||||
|
source:
|
||||||
|
source_application:
|
||||||
|
application_data:
|
||||||
|
order:
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c20004ac{"iv":"b9azKpMJIWIEhajdeuWZwA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"UfkxzVjc5tdRe5otTxuM5JK+7lftFfspCMKVh6bxJcDOcVZrbLCsM7diYYkOoGvMDdhSjM6BTn3bA/g69I6j/vG/1CBMhGO00fVgosMxQvuuOQd1kKg3U1Y+MUKDAaSlV26a/PgdZC0nce/0kWxThKLbjR3f//pp2CgY5qZCkLLEelpYU/T2prdA0oCzlczFYJb8kGArCZ74qzK00jpTOPR8E1ktYsmNoRZxWxmYKq3bghRzxdzhOtmwbfYyN3GtC8rz5ZZ5Rpuo4HKe3ZN6ht6J2ztHM9UwrwWG2wHbrbdxZXw2MC1FkQzLg+VnXN55E/GzcVAIqCjZnHzvlPE1EuCojC6XaEJ6wXp8qxYl0WnfUNERaoj93H6yHnOYppOYTNSO/TqGGm/AK2hf6zQinGjWRUsHxnNamfuIdcDzP0kv/STpWu29WkvYkE7kya5jpe3NVjH9Iu8ihe9iuzKtW3Z8t1YSXPk0VS/CHWFI1PAXttQH3MUQdq7kRZIfWnHR7EgVbcUYEYCOznIjO8P+pq1DUpV5ijzLtP3vRGMBt37NVQpBFeMCwKV+Hw1UNpkUO/GN+kZp6hOhmzmLmpDjcYr6/jlX0CjQ9vjQ/RM2VKlTVYb8aTDbH3hFb/fLjP20UtFGylk64lhlTaTa2iOO7WFe5rrdtSorKdNHhNG3upbSDvGPCTknF3LnDNg3I0ILIRoMRLGpvotBxhxHZ6Stxp9MIHi6fSA5RoU7Fg8QVK2X83lFzNmdxHKZ0yv3je5Lk3P+JcFawRNeo7PML1Ep++bwlmtmTKWTTKsgcXmX/6WE4MfoOw50Efc5WJQR/GU1mmWOwHa6PKeeG9uS+Jd2uwNQ0F/TQzrPU/M3lJA6S1Fosa2BsPVvQ2VgTHU8x4gB8LyKgw77ZvTzzVDmjGzqJMycLr82k9mHChgyW/dR3gNJ87u22ffgST1UY/mE1Paymlj5n5VpLPAO5twRQdAv4YWdRaDnBOKxWxmj/fwc9+LmUJF09VygbMbLN92bpdELQFIQQQEnCBjJEYr8pXxIEX6E1Qrj+FuUrF+b"}
|
||||||
|
encryption_applied: 1
|
||||||
|
markup_language:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: ff770ee627694f9e9ec08f4a218029be
|
||||||
|
parent_id: 53fc76b1c3a94e1dafb98bfac07af69b
|
||||||
|
created_time:
|
||||||
|
updated_time: 2021-06-26T20:08:14.800Z
|
||||||
|
is_conflict:
|
||||||
|
latitude:
|
||||||
|
longitude:
|
||||||
|
altitude:
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo:
|
||||||
|
todo_due:
|
||||||
|
todo_completed:
|
||||||
|
source:
|
||||||
|
source_application:
|
||||||
|
application_data:
|
||||||
|
order:
|
||||||
|
user_created_time:
|
||||||
|
user_updated_time:
|
||||||
|
encryption_cipher_text: JED01000022050e6c545903544ac1b08fe406d22155c2000504{"iv":"aQVeN4wYOd1FCLgM36F2uQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4fhj6/6Erwo=","ct":"E/8zUIYScRYmac9VZhSk6elv+s774i92YGLSVyIha56RFs/2by2+35OinJrTtvtshxGbVC4SnZLYkwoJ2m93mZkEk7EWYAcK/i3CoaVeegNl1kH9+zSkOGtlPm5wmqmr0qOry59Jkr/BzfmV2z7ZxqAeUiTO3L1cIzDE7D7zLRriM4s9nKyuUE/8wKmg0/R/drUjU4TRR5u9JebZQzmG0ufSiGkiXHLEu/oDf6ZCZFHihoJzvhP3oCkm13vmWRgIBww7nw46Pl90lit52wL6YEW+9wt8gMRNB5GyCJq3kXRtyRx3jcErx43aPE2o5fRphuElkZoHbZ8PNVpJg9s9QwoPgyqL5XpHU1LQin2qlWQGQ4yFiMOCrVIlljpb35TyBgf9ysCIiClMxoh6ZzltS6o8bT0C860t6UCWkRMZVifddj0oq6XnmiHWi1dIgvb5zMZFahUQ0RcqrfTO0rJEMeH+brjRysN9cguoqASNLXdXqM+J1FEWHC7ESAaC9AV+SfXBz26cx7lptq1zK7ashdwkJ00wn0aem71YO5j5BjVkDf/Qog4R0O9PfpPZPZ3NeAABoOTc/7KceotHFElHmgcSk88tIHxGrhECezdRhLtmyKWttQpMStrxXCzCSYdyeY/K8Qyj9FVeZ3kmA4u/MAtLTmo7KdBhRFxPUPrEoMnhdlhP4/cnKaGbsLmeJ2vrKfMZuo38TlITaikhhDw245inKDyY1fPR/sGZZkRMUE34pIFW9hP8QbLE60EkDmnytkzF2Ejjtff6/LR8oH/0ekOk4xHebGg1l/OdixbYuQMvOq3jELtoKYx0yfsuwXDDNRH08wl+8bPJSAsIe8PJ9StdRAq7FVNQx4xSXCevYWFvFKwkIPSmbx6Yi6Ga9M3FQzhY9EG8ozOKtuunAlIHQhID9svIL92J30cd1RQh5898TAd0G1HLWO329R7W0YLphb3vrfeHT06WTd/XlpCLrroXM33vX+kK8GWdqQbQlYF/8g24HgHORYDS9vu2KeSlCSfpX6fc36lC7kR+COTQ13lX91PzmtqwC5FXM8ZWKJkX67nlSv1HRx5z4+67zbes4XQ5/QO8Xj5j0cCylchWlCT3FgedsCHbuC+XUQca4L3nuNGbccyx/Lrj/rIi"}
|
||||||
|
encryption_applied: 1
|
||||||
|
markup_language:
|
||||||
|
is_shared:
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"e2ee":true,"masterKeys":{"0e6c545903544ac1b08fe406d22155c2":{"id":"0e6c545903544ac1b08fe406d22155c2","checksum":"","encryption_method":4,"content":"{\"iv\":\"hkul4JTSj7dPhbNhmJVUgQ==\",\"v\":1,\"iter\":10000,\"ks\":256,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"salt\":\"bRAWx6CUYlU=\",\"ct\":\"owa8n48rN/J3wdQYnHa8VxdwHcNPNQELIVfm5uZMTQwVbFxiFfrN8b20Bc5V8esz1lF3VroQO6jNOChlEI3E5wI7B93q/L8j7OjOfcb1OFydNf3y4/wx8/3hgvDfG2ncpZMfWgOkQ5VFtfrlFPAsvWTBOAMSL6EY2asXxkSSZAONB0vP339cWaOkKgu7dRs8yyX9N/2wVw3KlwzlBGfjmZjemR6gBhmx4XK8edGlPXtg4mjnPhWfYZUOt5PYRwkp9OQOGSoAsnWBbt3WqOF4J/BGAn8TnWAGbuxJ+BV+8Bjn6+TJnu3SQjgo8rnHW78soyvcI8agEBV0jzGMXHK/P4IfTNdLq8Q1ylvRD8s5Iy2A0LzIjlismMAJddar+1yaGQapWnqsAeQaq7yiL5XN9SXJrjTQZihBGIMZ8TsuLsu9EXcSjtaQP1dCc8GxbQLUUrtyZB2LVzy4dQ3KBuRbf+4/jYYtrBDoP/LL4cQt7lX0Cprb9Ts8JXYf2bI0zHlaOmfagsGw4eIyiGYUDbNvcwl5JmfV0T4ICMeRt8J6jwvMfb9udsxkk/Cu6RCgJt27B38w1mD4PqsFSJkub/TLXOAV+ClR+OUCvOe+n33WfZONglrZwRMosjv1o5Y70K6TQGwFNADQHRiIwjLxa7O18RcR3a6uvRweyEeq0f7zLBWEhrrXJdvwnw==\"}","created_time":1624738094861,"updated_time":1624738094861,"source_application":"net.cozic.joplintest-cli"}},"activeMasterKeyId":"0e6c545903544ac1b08fe406d22155c2","updatedTime":1624738095090}
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/3/e2ee/locks/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/3/e2ee/locks/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/3/e2ee/temp/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/3/e2ee/temp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
2020-07-16: In the new sync format, the version number is stored in /info.json. However, for backward compatibility, we need to keep the old version.txt file here, otherwise old clients will automatically recreate it, and assume a sync target version 1. So we keep it here but set its value to "2", so that old clients know that they need to be upgraded. This directory can be removed after a year or so, once we are confident that all clients have been upgraded to recent versions.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 03a58517a48c408c93bc26c5b92a2b63
|
||||||
|
note_id: ceda4e635262419695e59a7c90aaffa6
|
||||||
|
tag_id: 5d37049a85ca469f97930acac11a9de5
|
||||||
|
created_time: 2021-06-26T20:08:08.364Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.364Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.364Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.364Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
tag2
|
||||||
|
|
||||||
|
id: 08341e97f8d14853a885d25f9ff020cb
|
||||||
|
created_time: 2021-06-26T20:08:08.370Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.370Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.370Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.370Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
parent_id:
|
||||||
|
type_: 5
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 08e260b09d4d42fbb98b545a561d8f72
|
||||||
|
note_id: 264f9c3132f5473e9772c4872a083d2a
|
||||||
|
tag_id: 5d37049a85ca469f97930acac11a9de5
|
||||||
|
created_time: 2021-06-26T20:08:08.367Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.367Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.367Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.367Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 12f2bd71f35e4ae6af168f3061bd8098
|
||||||
|
note_id: 264f9c3132f5473e9772c4872a083d2a
|
||||||
|
tag_id: 08341e97f8d14853a885d25f9ff020cb
|
||||||
|
created_time: 2021-06-26T20:08:08.371Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.371Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.371Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.371Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
folder1
|
||||||
|
|
||||||
|
id: 1754d54f708146119af2d1d97ba39191
|
||||||
|
created_time: 2021-06-26T20:08:08.240Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.240Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.240Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.240Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
parent_id:
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
note3
|
||||||
|
|
||||||
|
id: 264f9c3132f5473e9772c4872a083d2a
|
||||||
|
parent_id: 1754d54f708146119af2d1d97ba39191
|
||||||
|
created_time: 2021-06-26T20:08:08.366Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.366Z
|
||||||
|
is_conflict: 0
|
||||||
|
latitude: 0.00000000
|
||||||
|
longitude: 0.00000000
|
||||||
|
altitude: 0.0000
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo: 0
|
||||||
|
todo_due: 0
|
||||||
|
todo_completed: 0
|
||||||
|
source: joplin
|
||||||
|
source_application: net.cozic.joplintest-cli
|
||||||
|
application_data:
|
||||||
|
order: 1624738088366
|
||||||
|
user_created_time: 2021-06-26T20:08:08.366Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.366Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
markup_language: 1
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
note4
|
||||||
|
|
||||||
|
id: 330f88a3d7bb4d5ebf488c92aa9115e0
|
||||||
|
parent_id: 1754d54f708146119af2d1d97ba39191
|
||||||
|
created_time: 2021-06-26T20:08:08.372Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.372Z
|
||||||
|
is_conflict: 0
|
||||||
|
latitude: 0.00000000
|
||||||
|
longitude: 0.00000000
|
||||||
|
altitude: 0.0000
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo: 0
|
||||||
|
todo_due: 0
|
||||||
|
todo_completed: 0
|
||||||
|
source: joplin
|
||||||
|
source_application: net.cozic.joplintest-cli
|
||||||
|
application_data:
|
||||||
|
order: 1624738088372
|
||||||
|
user_created_time: 2021-06-26T20:08:08.372Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.372Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
markup_language: 1
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
tag1
|
||||||
|
|
||||||
|
id: 5d37049a85ca469f97930acac11a9de5
|
||||||
|
created_time: 2021-06-26T20:08:08.362Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.362Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.362Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.362Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
parent_id:
|
||||||
|
type_: 5
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: 6cd49f1a6fac4861baa0e3240e134048
|
||||||
|
note_id: 330f88a3d7bb4d5ebf488c92aa9115e0
|
||||||
|
tag_id: 08341e97f8d14853a885d25f9ff020cb
|
||||||
|
created_time: 2021-06-26T20:08:08.374Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.374Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.374Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.374Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
photo.jpg
|
||||||
|
|
||||||
|
id: 91530b8c77b4451db32e706f4f365209
|
||||||
|
mime: image/jpeg
|
||||||
|
filename:
|
||||||
|
created_time: 2021-06-26T20:08:08.356Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.356Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.356Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.356Z
|
||||||
|
file_extension: jpg
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
encryption_blob_encrypted: 0
|
||||||
|
size: 2720
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 4
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
photo.jpg
|
||||||
|
|
||||||
|
id: 9333a8289e1248cdaef3db6047de2fbd
|
||||||
|
mime: image/jpeg
|
||||||
|
filename:
|
||||||
|
created_time: 2021-06-26T20:08:08.479Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.479Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.479Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.479Z
|
||||||
|
file_extension: jpg
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
encryption_blob_encrypted: 0
|
||||||
|
size: 2720
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 4
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
subFolder2
|
||||||
|
|
||||||
|
id: 9396bda49c0b447794639cb1069f9ca9
|
||||||
|
created_time: 2021-06-26T20:08:08.243Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.243Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.243Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.243Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
parent_id: 1754d54f708146119af2d1d97ba39191
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
folder3
|
||||||
|
|
||||||
|
id: a23d3b2251854643aab04c0a592db4ba
|
||||||
|
created_time: 2021-06-26T20:08:08.376Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.376Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.376Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.376Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
parent_id:
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
id: b9e8003b12c84826b733f688849443b5
|
||||||
|
note_id: dbdcb925536446be8474489092de8ce0
|
||||||
|
tag_id: 08341e97f8d14853a885d25f9ff020cb
|
||||||
|
created_time: 2021-06-26T20:08:08.483Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.483Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.483Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.483Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
is_shared: 0
|
||||||
|
type_: 6
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
folder2
|
||||||
|
|
||||||
|
id: bf054ce041ac4194ab2800f8495ae81f
|
||||||
|
created_time: 2021-06-26T20:08:08.375Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.375Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.375Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.375Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
parent_id:
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
note1
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
id: ceda4e635262419695e59a7c90aaffa6
|
||||||
|
parent_id: 9396bda49c0b447794639cb1069f9ca9
|
||||||
|
created_time: 2021-06-26T20:08:08.244Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.359Z
|
||||||
|
is_conflict: 0
|
||||||
|
latitude: 0.00000000
|
||||||
|
longitude: 0.00000000
|
||||||
|
altitude: 0.0000
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo: 0
|
||||||
|
todo_due: 0
|
||||||
|
todo_completed: 0
|
||||||
|
source: joplin
|
||||||
|
source_application: net.cozic.joplintest-cli
|
||||||
|
application_data:
|
||||||
|
order: 1624738088244
|
||||||
|
user_created_time: 2021-06-26T20:08:08.244Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.359Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
markup_language: 1
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
note2
|
||||||
|
|
||||||
|
id: d00286f8d0354b28a02fea8a39accf96
|
||||||
|
parent_id: 9396bda49c0b447794639cb1069f9ca9
|
||||||
|
created_time: 2021-06-26T20:08:08.365Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.365Z
|
||||||
|
is_conflict: 0
|
||||||
|
latitude: 0.00000000
|
||||||
|
longitude: 0.00000000
|
||||||
|
altitude: 0.0000
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo: 0
|
||||||
|
todo_due: 0
|
||||||
|
todo_completed: 0
|
||||||
|
source: joplin
|
||||||
|
source_application: net.cozic.joplintest-cli
|
||||||
|
application_data:
|
||||||
|
order: 1624738088365
|
||||||
|
user_created_time: 2021-06-26T20:08:08.365Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.365Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
markup_language: 1
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
note5
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
id: dbdcb925536446be8474489092de8ce0
|
||||||
|
parent_id: a23d3b2251854643aab04c0a592db4ba
|
||||||
|
created_time: 2021-06-26T20:08:08.377Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.481Z
|
||||||
|
is_conflict: 0
|
||||||
|
latitude: 0.00000000
|
||||||
|
longitude: 0.00000000
|
||||||
|
altitude: 0.0000
|
||||||
|
author:
|
||||||
|
source_url:
|
||||||
|
is_todo: 0
|
||||||
|
todo_due: 0
|
||||||
|
todo_completed: 0
|
||||||
|
source: joplin
|
||||||
|
source_application: net.cozic.joplintest-cli
|
||||||
|
application_data:
|
||||||
|
order: 1624738088376
|
||||||
|
user_created_time: 2021-06-26T20:08:08.377Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.481Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
markup_language: 1
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
conflict_original_id:
|
||||||
|
type_: 1
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
subFolder1
|
||||||
|
|
||||||
|
id: e27847adda964cf696aedfcc049ee30f
|
||||||
|
created_time: 2021-06-26T20:08:08.242Z
|
||||||
|
updated_time: 2021-06-26T20:08:08.242Z
|
||||||
|
user_created_time: 2021-06-26T20:08:08.242Z
|
||||||
|
user_updated_time: 2021-06-26T20:08:08.242Z
|
||||||
|
encryption_cipher_text:
|
||||||
|
encryption_applied: 0
|
||||||
|
parent_id: 1754d54f708146119af2d1d97ba39191
|
||||||
|
is_shared: 0
|
||||||
|
share_id:
|
||||||
|
type_: 2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"e2ee":false,"masterKeys":{},"activeMasterKeyId":"","updatedTime":1624738088713}
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/3/normal/locks/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/3/normal/locks/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
packages/app-cli/tests/support/syncTargetSnapshots/3/normal/temp/.gitignore
vendored
Normal file
4
packages/app-cli/tests/support/syncTargetSnapshots/3/normal/temp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
@@ -28,7 +28,6 @@ import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
|||||||
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
||||||
|
|
||||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||||
import MasterKey from '@joplin/lib/models/MasterKey';
|
|
||||||
import Folder from '@joplin/lib/models/Folder';
|
import Folder from '@joplin/lib/models/Folder';
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
import Tag from '@joplin/lib/models/Tag';
|
import Tag from '@joplin/lib/models/Tag';
|
||||||
@@ -707,12 +706,12 @@ class Application extends BaseApplication {
|
|||||||
items: tags,
|
items: tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
const masterKeys = await MasterKey.all();
|
// const masterKeys = await MasterKey.all();
|
||||||
|
|
||||||
this.dispatch({
|
// this.dispatch({
|
||||||
type: 'MASTERKEY_UPDATE_ALL',
|
// type: 'MASTERKEY_UPDATE_ALL',
|
||||||
items: masterKeys,
|
// items: masterKeys,
|
||||||
});
|
// });
|
||||||
|
|
||||||
this.store().dispatch({
|
this.store().dispatch({
|
||||||
type: 'FOLDER_SELECT',
|
type: 'FOLDER_SELECT',
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import { _ } from '@joplin/lib/locale';
|
|||||||
import bridge from '../../services/bridge';
|
import bridge from '../../services/bridge';
|
||||||
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
|
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
|
||||||
import control_PluginsStates from './controls/plugins/PluginsStates';
|
import control_PluginsStates from './controls/plugins/PluginsStates';
|
||||||
|
import EncryptionConfigScreen from '../EncryptionConfigScreen';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
const pathUtils = require('@joplin/lib/path-utils');
|
const pathUtils = require('@joplin/lib/path-utils');
|
||||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
||||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||||
const { EncryptionConfigScreen } = require('../EncryptionConfigScreen.min');
|
|
||||||
const { ClipperConfigScreen } = require('../ClipperConfigScreen.min');
|
const { ClipperConfigScreen } = require('../ClipperConfigScreen.min');
|
||||||
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const Setting = require('@joplin/lib/models/Setting').default;
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
const EncryptionService = require('@joplin/lib/services/EncryptionService').default;
|
import EncryptionService from '@joplin/lib/services/EncryptionService';
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
import { setupAndDisableEncryption, generateMasterKeyAndEnableEncryption } from '@joplin/lib/services/e2ee/utils';
|
||||||
const { _ } = require('@joplin/lib/locale');
|
import time from '@joplin/lib/time';
|
||||||
const time = require('@joplin/lib/time').default;
|
import shim from '@joplin/lib/shim';
|
||||||
const shim = require('@joplin/lib/shim').default;
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
const dialogs = require('./dialogs').default;
|
import dialogs from './dialogs';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import bridge from '../services/bridge';
|
||||||
|
import { MasterKeyEntity } from '@joplin/lib/services/database/types';
|
||||||
|
import { activeMasterKeyId, encryptionEnabled, localSyncTargetInfo, masterKeyAll } from '@joplin/lib/services/synchronizer/syncTargetInfoUtils';
|
||||||
|
import { AppState } from '../app';
|
||||||
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
|
||||||
|
|
||||||
class EncryptionConfigScreenComponent extends React.Component {
|
interface Props {
|
||||||
constructor(props) {
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EncryptionConfigScreenComponent extends React.Component<Props, any> {
|
||||||
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
shared.constructor(this, props);
|
shared.constructor(this, props);
|
||||||
@@ -27,7 +35,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
shared.componentDidMount(this);
|
shared.componentDidMount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
shared.componentDidUpdate(this, prevProps);
|
shared.componentDidUpdate(this, prevProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +43,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
return shared.checkPasswords(this);
|
return shared.checkPasswords(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMasterKey(mk) {
|
renderMasterKey(mk: MasterKeyEntity) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const passwordStyle = {
|
const passwordStyle = {
|
||||||
@@ -49,7 +57,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
return shared.onSavePasswordClick(this, mk);
|
return shared.onSavePasswordClick(this, mk);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPasswordChange = event => {
|
const onPasswordChange = (event: any) => {
|
||||||
return shared.onPasswordChange(this, mk, event.target.value);
|
return shared.onPasswordChange(this, mk, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,7 +169,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onToggleButtonClick = async () => {
|
const onToggleButtonClick = async () => {
|
||||||
const isEnabled = Setting.value('encryption.enabled');
|
const isEnabled = encryptionEnabled();
|
||||||
|
|
||||||
let answer = null;
|
let answer = null;
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
@@ -174,9 +182,9 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
await EncryptionService.instance().disableEncryption();
|
await setupAndDisableEncryption();
|
||||||
} else {
|
} else {
|
||||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(answer);
|
await generateMasterKeyAndEnableEncryption(EncryptionService.instance(), answer);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await dialogs.alert(error.message);
|
await dialogs.alert(error.message);
|
||||||
@@ -188,7 +196,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
<button
|
<button
|
||||||
style={theme.buttonStyle}
|
style={theme.buttonStyle}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onToggleButtonClick();
|
void onToggleButtonClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}
|
{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}
|
||||||
@@ -288,13 +296,15 @@ class EncryptionConfigScreenComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
const syncTargetInfo = localSyncTargetInfo(state.settings['sync.info']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
masterKeys: state.masterKeys,
|
masterKeys: masterKeyAll(syncTargetInfo),
|
||||||
passwords: state.settings['encryption.passwordCache'],
|
passwords: state.settings['encryption.passwordCache'],
|
||||||
encryptionEnabled: state.settings['encryption.enabled'],
|
encryptionEnabled: encryptionEnabled(syncTargetInfo),
|
||||||
activeMasterKeyId: state.settings['encryption.activeMasterKeyId'],
|
activeMasterKeyId: activeMasterKeyId(syncTargetInfo),
|
||||||
shouldReencrypt: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
shouldReencrypt: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
||||||
notLoadedMasterKeys: state.notLoadedMasterKeys,
|
notLoadedMasterKeys: state.notLoadedMasterKeys,
|
||||||
};
|
};
|
||||||
@@ -302,4 +312,4 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const EncryptionConfigScreen = connect(mapStateToProps)(EncryptionConfigScreenComponent);
|
const EncryptionConfigScreen = connect(mapStateToProps)(EncryptionConfigScreenComponent);
|
||||||
|
|
||||||
module.exports = { EncryptionConfigScreen };
|
export default EncryptionConfigScreen;
|
||||||
@@ -35,6 +35,7 @@ import { ShareInvitation } from '@joplin/lib/services/share/reducer';
|
|||||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
||||||
|
import { localSyncTargetInfo, masterKeyAll } from '@joplin/lib/services/synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||||
@@ -857,6 +858,9 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
const syncTargetInfo = localSyncTargetInfo(state.settings['sync.info']);
|
||||||
|
const masterKeys = masterKeyAll(syncTargetInfo);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
settingEditorCodeView: state.settings['editor.codeView'],
|
settingEditorCodeView: state.settings['editor.codeView'],
|
||||||
@@ -864,8 +868,8 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
notes: state.notes,
|
notes: state.notes,
|
||||||
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
||||||
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
|
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
|
||||||
showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && state.masterKeys.length,
|
showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && masterKeys.length,
|
||||||
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(state.masterKeys).length,
|
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(masterKeys).length,
|
||||||
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
||||||
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
||||||
selectedFolderId: state.selectedFolderId,
|
selectedFolderId: state.selectedFolderId,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useState, useEffect } from 'react';
|
|||||||
import JoplinServerApi from '@joplin/lib/JoplinServerApi';
|
import JoplinServerApi from '@joplin/lib/JoplinServerApi';
|
||||||
import { _, _n } from '@joplin/lib/locale';
|
import { _, _n } from '@joplin/lib/locale';
|
||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
|
||||||
import DialogButtonRow from './DialogButtonRow';
|
import DialogButtonRow from './DialogButtonRow';
|
||||||
import { themeStyle, buildStyle } from '@joplin/lib/theme';
|
import { themeStyle, buildStyle } from '@joplin/lib/theme';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
@@ -15,6 +14,7 @@ import { NoteEntity } from '@joplin/lib/services/database/types';
|
|||||||
import Button from './Button/Button';
|
import Button from './Button/Button';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { AppState } from '../app';
|
import { AppState } from '../app';
|
||||||
|
import { encryptionEnabled } from '@joplin/lib/services/synchronizer/syncTargetInfoUtils';
|
||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -210,7 +210,7 @@ export function ShareNoteDialog(props: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function renderEncryptionWarningMessage() {
|
function renderEncryptionWarningMessage() {
|
||||||
if (!Setting.value('encryption.enabled')) return null;
|
if (!encryptionEnabled()) return null;
|
||||||
return <div style={theme.textStyle}>{_('Note: When a note is shared, it will no longer be encrypted on the server.')}<hr/></div>;
|
return <div style={theme.textStyle}>{_('Note: When a note is shared, it will no longer be encrypted on the server.')}<hr/></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ const app = require('./app').default;
|
|||||||
const Folder = require('@joplin/lib/models/Folder').default;
|
const Folder = require('@joplin/lib/models/Folder').default;
|
||||||
const Resource = require('@joplin/lib/models/Resource').default;
|
const Resource = require('@joplin/lib/models/Resource').default;
|
||||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||||
|
const MasterKey = require('@joplin/lib/models/MasterKey').default;
|
||||||
const Note = require('@joplin/lib/models/Note').default;
|
const Note = require('@joplin/lib/models/Note').default;
|
||||||
const Tag = require('@joplin/lib/models/Tag').default;
|
const Tag = require('@joplin/lib/models/Tag').default;
|
||||||
const NoteTag = require('@joplin/lib/models/NoteTag').default;
|
const NoteTag = require('@joplin/lib/models/NoteTag').default;
|
||||||
const MasterKey = require('@joplin/lib/models/MasterKey').default;
|
|
||||||
const Setting = require('@joplin/lib/models/Setting').default;
|
const Setting = require('@joplin/lib/models/Setting').default;
|
||||||
const Revision = require('@joplin/lib/models/Revision').default;
|
const Revision = require('@joplin/lib/models/Revision').default;
|
||||||
const Logger = require('@joplin/lib/Logger').default;
|
const Logger = require('@joplin/lib/Logger').default;
|
||||||
|
|||||||
@@ -39,12 +39,16 @@ do
|
|||||||
|
|
||||||
USER_EMAIL="user$USER_NUM@example.com"
|
USER_EMAIL="user$USER_NUM@example.com"
|
||||||
rm -rf "$PROFILE_DIR"
|
rm -rf "$PROFILE_DIR"
|
||||||
echo "config keychain.supported 0" >> "$CMD_FILE"
|
echo "config keychain.supported 0" >> "$CMD_FILE"
|
||||||
echo "config sync.target 10" >> "$CMD_FILE"
|
echo "config sync.target 2" >> "$CMD_FILE"
|
||||||
# echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
|
echo "config sync.2.path /Users/laurent/Temp/SyncTestE2EE" >> "$CMD_FILE"
|
||||||
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
|
|
||||||
echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
|
# echo "config keychain.supported 0" >> "$CMD_FILE"
|
||||||
|
# echo "config sync.target 10" >> "$CMD_FILE"
|
||||||
|
# # echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
|
||||||
|
# echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
|
||||||
|
# echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
|
||||||
|
|
||||||
elif [[ $CMD == "e2ee" ]]; then
|
elif [[ $CMD == "e2ee" ]]; then
|
||||||
|
|
||||||
echo "e2ee enable --password 111111" >> "$CMD_FILE"
|
echo "e2ee enable --password 111111" >> "$CMD_FILE"
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native');
|
const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native');
|
||||||
const EncryptionService = require('@joplin/lib/services/EncryptionService').default;
|
const DialogBox = require('react-native-dialogbox').default;
|
||||||
|
import EncryptionService from '@joplin/lib/services/EncryptionService';
|
||||||
|
import time from '@joplin/lib/time';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { MasterKeyEntity } from '@joplin/lib/services/database/types';
|
||||||
|
import { setupAndDisableEncryption, generateMasterKeyAndEnableEncryption } from '@joplin/lib/services/e2ee/utils';
|
||||||
|
import { activeMasterKeyId, encryptionEnabled } from '@joplin/lib/services/synchronizer/syncTargetInfoUtils';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { ScreenHeader } = require('../screen-header.js');
|
const { ScreenHeader } = require('../screen-header.js');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
|
||||||
const { BaseScreenComponent } = require('../base-screen.js');
|
const { BaseScreenComponent } = require('../base-screen.js');
|
||||||
const { themeStyle } = require('../global-style.js');
|
const { themeStyle } = require('../global-style.js');
|
||||||
const time = require('@joplin/lib/time').default;
|
|
||||||
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
||||||
const { dialogs } = require('../../utils/dialogs.js');
|
const { dialogs } = require('../../utils/dialogs.js');
|
||||||
const DialogBox = require('react-native-dialogbox').default;
|
|
||||||
|
|
||||||
class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
||||||
static navigationOptions() {
|
static navigationOptions() {
|
||||||
return { header: null };
|
return { header: null } as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -35,7 +38,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
this.isMounted_ = false;
|
this.isMounted_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initState(props) {
|
initState(props: any) {
|
||||||
return shared.initState(this, props);
|
return shared.initState(this, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
shared.componentDidMount(this);
|
shared.componentDidMount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: any) {
|
||||||
shared.componentDidUpdate(this, prevProps);
|
shared.componentDidUpdate(this, prevProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,21 +99,21 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
return this.styles_[themeId];
|
return this.styles_[themeId];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMasterKey(num, mk) {
|
renderMasterKey(_num: number, mk: MasterKeyEntity) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const onSaveClick = () => {
|
const onSaveClick = () => {
|
||||||
return shared.onSavePasswordClick(this, mk);
|
return shared.onSavePasswordClick(this, mk);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPasswordChange = text => {
|
const onPasswordChange = (text: string) => {
|
||||||
return shared.onPasswordChange(this, mk, text);
|
return shared.onPasswordChange(this, mk, text);
|
||||||
};
|
};
|
||||||
|
|
||||||
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
||||||
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
|
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
|
||||||
|
|
||||||
const inputStyle = { flex: 1, marginRight: 10, color: theme.color };
|
const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color };
|
||||||
inputStyle.borderBottomWidth = 1;
|
inputStyle.borderBottomWidth = 1;
|
||||||
inputStyle.borderBottomColor = theme.dividerColor;
|
inputStyle.borderBottomColor = theme.dividerColor;
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
<Text style={this.styles().normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
|
<Text style={this.styles().normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
|
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
|
||||||
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={password} onChangeText={text => onPasswordChange(text)} style={inputStyle}></TextInput>
|
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={password} onChangeText={(text: string) => onPasswordChange(text)} style={inputStyle}></TextInput>
|
||||||
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
|
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
|
||||||
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
|
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
|
||||||
</View>
|
</View>
|
||||||
@@ -138,7 +141,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
const password2 = this.state.passwordPromptConfirmAnswer;
|
const password2 = this.state.passwordPromptConfirmAnswer;
|
||||||
if (!password2) throw new Error(_('Confirm password cannot be empty'));
|
if (!password2) throw new Error(_('Confirm password cannot be empty'));
|
||||||
if (password !== password2) throw new Error(_('Passwords do not match!'));
|
if (password !== password2) throw new Error(_('Passwords do not match!'));
|
||||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
await generateMasterKeyAndEnableEncryption(EncryptionService.instance(), password);
|
||||||
this.setState({ passwordPromptShow: false });
|
this.setState({ passwordPromptShow: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await dialogs.error(this, error.message);
|
await dialogs.error(this, error.message);
|
||||||
@@ -155,7 +158,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
style={this.styles().normalTextInput}
|
style={this.styles().normalTextInput}
|
||||||
secureTextEntry={true}
|
secureTextEntry={true}
|
||||||
value={this.state.passwordPromptAnswer}
|
value={this.state.passwordPromptAnswer}
|
||||||
onChangeText={text => {
|
onChangeText={(text: string) => {
|
||||||
this.setState({ passwordPromptAnswer: text });
|
this.setState({ passwordPromptAnswer: text });
|
||||||
}}
|
}}
|
||||||
></TextInput>
|
></TextInput>
|
||||||
@@ -167,7 +170,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
style={this.styles().normalTextInput}
|
style={this.styles().normalTextInput}
|
||||||
secureTextEntry={true}
|
secureTextEntry={true}
|
||||||
value={this.state.passwordPromptConfirmAnswer}
|
value={this.state.passwordPromptConfirmAnswer}
|
||||||
onChangeText={text => {
|
onChangeText={(text: string) => {
|
||||||
this.setState({ passwordPromptConfirmAnswer: text });
|
this.setState({ passwordPromptConfirmAnswer: text });
|
||||||
}}
|
}}
|
||||||
></TextInput>
|
></TextInput>
|
||||||
@@ -176,7 +179,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
<Button
|
<Button
|
||||||
title={_('Enable')}
|
title={_('Enable')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onEnableClick();
|
void onEnableClick();
|
||||||
}}
|
}}
|
||||||
></Button>
|
></Button>
|
||||||
</View>
|
</View>
|
||||||
@@ -216,7 +219,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await EncryptionService.instance().disableEncryption();
|
await setupAndDisableEncryption();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await dialogs.error(this, error.message);
|
await dialogs.error(this, error.message);
|
||||||
}
|
}
|
||||||
@@ -286,7 +289,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
<View style={{ flex: 1, height: 20 }}></View>
|
<View style={{ flex: 1, height: 20 }}></View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<DialogBox
|
<DialogBox
|
||||||
ref={dialogbox => {
|
ref={(dialogbox: any) => {
|
||||||
this.dialogbox = dialogbox;
|
this.dialogbox = dialogbox;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -295,15 +298,15 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EncryptionConfigScreen = connect(state => {
|
const EncryptionConfigScreen = connect((state: any) => {
|
||||||
return {
|
return {
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
masterKeys: state.masterKeys,
|
masterKeys: state.masterKeys,
|
||||||
passwords: state.settings['encryption.passwordCache'],
|
passwords: state.settings['encryption.passwordCache'],
|
||||||
encryptionEnabled: state.settings['encryption.enabled'],
|
encryptionEnabled: encryptionEnabled(),
|
||||||
activeMasterKeyId: state.settings['encryption.activeMasterKeyId'],
|
activeMasterKeyId: activeMasterKeyId(),
|
||||||
notLoadedMasterKeys: state.notLoadedMasterKeys,
|
notLoadedMasterKeys: state.notLoadedMasterKeys,
|
||||||
};
|
};
|
||||||
})(EncryptionConfigScreenComponent);
|
})(EncryptionConfigScreenComponent);
|
||||||
|
|
||||||
module.exports = { EncryptionConfigScreen };
|
export default EncryptionConfigScreen;
|
||||||
@@ -63,7 +63,7 @@ const { LogScreen } = require('./components/screens/log.js');
|
|||||||
const { StatusScreen } = require('./components/screens/status.js');
|
const { StatusScreen } = require('./components/screens/status.js');
|
||||||
const { SearchScreen } = require('./components/screens/search.js');
|
const { SearchScreen } = require('./components/screens/search.js');
|
||||||
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
|
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
|
||||||
const { EncryptionConfigScreen } = require('./components/screens/encryption-config.js');
|
import EncryptionConfigScreen from './components/screens/encryption-config';
|
||||||
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
|
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
|
||||||
const { MenuContext } = require('react-native-popup-menu');
|
const { MenuContext } = require('react-native-popup-menu');
|
||||||
const { SideMenu } = require('./components/side-menu.js');
|
const { SideMenu } = require('./components/side-menu.js');
|
||||||
@@ -102,6 +102,7 @@ import { clearSharedFilesCache } from './utils/ShareUtils';
|
|||||||
import setIgnoreTlsErrors from './utils/TlsUtils';
|
import setIgnoreTlsErrors from './utils/TlsUtils';
|
||||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||||
import setupNotifications from './utils/setupNotifications';
|
import setupNotifications from './utils/setupNotifications';
|
||||||
|
import { loadMasterKeysFromSettings } from '@joplin/lib/services/e2ee/utils';
|
||||||
|
|
||||||
let storeDispatch = function(_action: any) {};
|
let storeDispatch = function(_action: any) {};
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
|
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||||
void DecryptionWorker.instance().scheduleStart();
|
void DecryptionWorker.instance().scheduleStart();
|
||||||
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
||||||
|
|
||||||
@@ -535,7 +536,7 @@ async function initialize(dispatch: Function) {
|
|||||||
DecryptionWorker.instance().setLogger(mainLogger);
|
DecryptionWorker.instance().setLogger(mainLogger);
|
||||||
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
||||||
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||||
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', decryptionWorker_resourceMetadataButNotBlobDecrypted);
|
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', decryptionWorker_resourceMetadataButNotBlobDecrypted);
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -555,12 +556,12 @@ async function initialize(dispatch: Function) {
|
|||||||
items: tags,
|
items: tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
const masterKeys = await MasterKey.all();
|
// const masterKeys = await MasterKey.all();
|
||||||
|
|
||||||
dispatch({
|
// dispatch({
|
||||||
type: 'MASTERKEY_UPDATE_ALL',
|
// type: 'MASTERKEY_UPDATE_ALL',
|
||||||
items: masterKeys,
|
// items: masterKeys,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const folderId = Setting.value('activeFolderId');
|
const folderId = Setting.value('activeFolderId');
|
||||||
let folder = await Folder.load(folderId);
|
let folder = await Folder.load(folderId);
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import MigrationService from './services/MigrationService';
|
|||||||
import ShareService from './services/share/ShareService';
|
import ShareService from './services/share/ShareService';
|
||||||
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
|
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
|
||||||
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
|
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
|
||||||
|
import { loadMasterKeysFromSettings } from './services/e2ee/utils';
|
||||||
|
import { encryptionEnabled } from './services/synchronizer/syncTargetInfoUtils';
|
||||||
const { toSystemSlashes } = require('./path-utils');
|
const { toSystemSlashes } = require('./path-utils');
|
||||||
const { setAutoFreeze } = require('immer');
|
const { setAutoFreeze } = require('immer');
|
||||||
|
|
||||||
@@ -423,9 +425,18 @@ export default class BaseApplication {
|
|||||||
syswidecas.addCAs(f);
|
syswidecas.addCAs(f);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'encryption.enabled': async () => {
|
|
||||||
|
// Note: this used to run when "encryption.enabled" was changed, but
|
||||||
|
// now we run it anytime any property of the sync target info is
|
||||||
|
// changed. This is not optimal but:
|
||||||
|
// - The sync target info rarely changes.
|
||||||
|
// - All the calls below are cheap or do nothing if there's nothing
|
||||||
|
// to do.
|
||||||
|
'sync.info': async () => {
|
||||||
if (this.hasGui()) {
|
if (this.hasGui()) {
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
appLogger.info('"sync.info" was changed - setting up encryption related code');
|
||||||
|
|
||||||
|
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||||
void DecryptionWorker.instance().scheduleStart();
|
void DecryptionWorker.instance().scheduleStart();
|
||||||
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
||||||
|
|
||||||
@@ -439,6 +450,7 @@ export default class BaseApplication {
|
|||||||
void reg.scheduleSync();
|
void reg.scheduleSync();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'sync.interval': async () => {
|
'sync.interval': async () => {
|
||||||
if (this.hasGui()) reg.setupRecurrentSync();
|
if (this.hasGui()) reg.setupRecurrentSync();
|
||||||
},
|
},
|
||||||
@@ -446,8 +458,7 @@ export default class BaseApplication {
|
|||||||
|
|
||||||
sideEffects['timeFormat'] = sideEffects['dateFormat'];
|
sideEffects['timeFormat'] = sideEffects['dateFormat'];
|
||||||
sideEffects['locale'] = sideEffects['dateFormat'];
|
sideEffects['locale'] = sideEffects['dateFormat'];
|
||||||
sideEffects['encryption.activeMasterKeyId'] = sideEffects['encryption.enabled'];
|
sideEffects['encryption.passwordCache'] = sideEffects['sync.info'];
|
||||||
sideEffects['encryption.passwordCache'] = sideEffects['encryption.enabled'];
|
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
const effect = sideEffects[action.key];
|
const effect = sideEffects[action.key];
|
||||||
@@ -781,7 +792,7 @@ export default class BaseApplication {
|
|||||||
// and if encryption is enabled. This code runs only when shouldReencrypt = -1
|
// and if encryption is enabled. This code runs only when shouldReencrypt = -1
|
||||||
// which can be set by a maintenance script for example.
|
// which can be set by a maintenance script for example.
|
||||||
const folderCount = await Folder.count();
|
const folderCount = await Folder.count();
|
||||||
const itShould = Setting.value('encryption.enabled') && !!folderCount ? Setting.SHOULD_REENCRYPT_YES : Setting.SHOULD_REENCRYPT_NO;
|
const itShould = encryptionEnabled() && !!folderCount ? Setting.SHOULD_REENCRYPT_YES : Setting.SHOULD_REENCRYPT_NO;
|
||||||
Setting.setValue('encryption.shouldReencrypt', itShould);
|
Setting.setValue('encryption.shouldReencrypt', itShould);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,7 +819,7 @@ export default class BaseApplication {
|
|||||||
DecryptionWorker.instance().setLogger(globalLogger);
|
DecryptionWorker.instance().setLogger(globalLogger);
|
||||||
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
||||||
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||||
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', this.decryptionWorker_resourceMetadataButNotBlobDecrypted);
|
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', this.decryptionWorker_resourceMetadataButNotBlobDecrypted);
|
||||||
|
|
||||||
ResourceFetcher.instance().setFileApi(() => {
|
ResourceFetcher.instance().setFileApi(() => {
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ export default class JoplinDatabase extends Database {
|
|||||||
// must be set in the synchronizer too.
|
// must be set in the synchronizer too.
|
||||||
|
|
||||||
// Note: v16 and v17 don't do anything. They were used to debug an issue.
|
// Note: v16 and v17 don't do anything. They were used to debug an issue.
|
||||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39];
|
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40];
|
||||||
|
|
||||||
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
||||||
|
|
||||||
@@ -892,6 +892,10 @@ export default class JoplinDatabase extends Database {
|
|||||||
queries.push('ALTER TABLE `notes` ADD COLUMN conflict_original_id TEXT NOT NULL DEFAULT ""');
|
queries.push('ALTER TABLE `notes` ADD COLUMN conflict_original_id TEXT NOT NULL DEFAULT ""');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targetVersion == 40) {
|
||||||
|
queries.push(this.addMigrationFile(40));
|
||||||
|
}
|
||||||
|
|
||||||
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
|
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
|
||||||
|
|
||||||
queries.push(updateVersionQuery);
|
queries.push(updateVersionQuery);
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import Note from './models/Note';
|
|||||||
import Resource from './models/Resource';
|
import Resource from './models/Resource';
|
||||||
import ItemChange from './models/ItemChange';
|
import ItemChange from './models/ItemChange';
|
||||||
import ResourceLocalState from './models/ResourceLocalState';
|
import ResourceLocalState from './models/ResourceLocalState';
|
||||||
import MasterKey from './models/MasterKey';
|
import BaseModel, { ModelType } from './BaseModel';
|
||||||
import BaseModel from './BaseModel';
|
|
||||||
import time from './time';
|
import time from './time';
|
||||||
import ResourceService from './services/ResourceService';
|
import ResourceService from './services/ResourceService';
|
||||||
import EncryptionService from './services/EncryptionService';
|
import EncryptionService from './services/EncryptionService';
|
||||||
@@ -21,9 +20,15 @@ import ShareService from './services/share/ShareService';
|
|||||||
import TaskQueue from './TaskQueue';
|
import TaskQueue from './TaskQueue';
|
||||||
import ItemUploader from './services/synchronizer/ItemUploader';
|
import ItemUploader from './services/synchronizer/ItemUploader';
|
||||||
import { FileApi } from './file-api';
|
import { FileApi } from './file-api';
|
||||||
|
import { localSyncTargetInfo, remoteSyncTargetInfo, setLocalSyncTargetInfo, syncTargetInfoEquals, setRemoteSyncTargetInfo, mergeSyncTargetInfos, activeMasterKey, masterKeyById } from './services/synchronizer/syncTargetInfoUtils';
|
||||||
|
import { setupAndEnableEncryption, setupAndDisableEncryption } from './services/e2ee/utils';
|
||||||
|
import JoplinDatabase from './JoplinDatabase';
|
||||||
|
import MasterKey from './models/MasterKey';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { Dirnames } = require('./services/synchronizer/utils/types');
|
const { Dirnames } = require('./services/synchronizer/utils/types');
|
||||||
|
|
||||||
|
const logger = Logger.create('Synchronizer');
|
||||||
|
|
||||||
interface RemoteItem {
|
interface RemoteItem {
|
||||||
id: string;
|
id: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
@@ -62,7 +67,7 @@ export default class Synchronizer {
|
|||||||
|
|
||||||
public static verboseMode: boolean = true;
|
public static verboseMode: boolean = true;
|
||||||
|
|
||||||
private db_: any;
|
private db_: JoplinDatabase;
|
||||||
private api_: FileApi;
|
private api_: FileApi;
|
||||||
private appType_: string;
|
private appType_: string;
|
||||||
private logger_: Logger = new Logger();
|
private logger_: Logger = new Logger();
|
||||||
@@ -87,7 +92,7 @@ export default class Synchronizer {
|
|||||||
|
|
||||||
public dispatch: Function;
|
public dispatch: Function;
|
||||||
|
|
||||||
public constructor(db: any, api: FileApi, appType: string) {
|
public constructor(db: JoplinDatabase, api: FileApi, appType: string) {
|
||||||
this.db_ = db;
|
this.db_ = db;
|
||||||
this.api_ = api;
|
this.api_ = api;
|
||||||
this.appType_ = appType;
|
this.appType_ = appType;
|
||||||
@@ -125,15 +130,15 @@ export default class Synchronizer {
|
|||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
lockHandler() {
|
public lockHandler() {
|
||||||
if (this.lockHandler_) return this.lockHandler_;
|
if (this.lockHandler_) return this.lockHandler_;
|
||||||
this.lockHandler_ = new LockHandler(this.api());
|
this.lockHandler_ = new LockHandler(this.api());
|
||||||
return this.lockHandler_;
|
return this.lockHandler_;
|
||||||
}
|
}
|
||||||
|
|
||||||
migrationHandler() {
|
private migrationHandler() {
|
||||||
if (this.migrationHandler_) return this.migrationHandler_;
|
if (this.migrationHandler_) return this.migrationHandler_;
|
||||||
this.migrationHandler_ = new MigrationHandler(this.api(), this.lockHandler(), this.appType_, this.clientId_);
|
this.migrationHandler_ = new MigrationHandler(this.api(), this.db(), this.lockHandler(), this.appType_, this.clientId_);
|
||||||
return this.migrationHandler_;
|
return this.migrationHandler_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,9 +219,9 @@ export default class Synchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Synchronizer.verboseMode) {
|
if (Synchronizer.verboseMode) {
|
||||||
this.logger().info(line.join(': '));
|
logger.info(line.join(': '));
|
||||||
} else {
|
} else {
|
||||||
this.logger().debug(line.join(': '));
|
logger.debug(line.join(': '));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.progressReport_[action]) this.progressReport_[action] = 0;
|
if (!this.progressReport_[action]) this.progressReport_[action] = 0;
|
||||||
@@ -234,7 +239,7 @@ export default class Synchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logSyncSummary(report: any) {
|
async logSyncSummary(report: any) {
|
||||||
this.logger().info('Operations completed: ');
|
logger.info('Operations completed: ');
|
||||||
for (const n in report) {
|
for (const n in report) {
|
||||||
if (!report.hasOwnProperty(n)) continue;
|
if (!report.hasOwnProperty(n)) continue;
|
||||||
if (n == 'errors') continue;
|
if (n == 'errors') continue;
|
||||||
@@ -243,20 +248,20 @@ export default class Synchronizer {
|
|||||||
if (n == 'state') continue;
|
if (n == 'state') continue;
|
||||||
if (n == 'startTime') continue;
|
if (n == 'startTime') continue;
|
||||||
if (n == 'completedTime') continue;
|
if (n == 'completedTime') continue;
|
||||||
this.logger().info(`${n}: ${report[n] ? report[n] : '-'}`);
|
logger.info(`${n}: ${report[n] ? report[n] : '-'}`);
|
||||||
}
|
}
|
||||||
const folderCount = await Folder.count();
|
const folderCount = await Folder.count();
|
||||||
const noteCount = await Note.count();
|
const noteCount = await Note.count();
|
||||||
const resourceCount = await Resource.count();
|
const resourceCount = await Resource.count();
|
||||||
this.logger().info(`Total folders: ${folderCount}`);
|
logger.info(`Total folders: ${folderCount}`);
|
||||||
this.logger().info(`Total notes: ${noteCount}`);
|
logger.info(`Total notes: ${noteCount}`);
|
||||||
this.logger().info(`Total resources: ${resourceCount}`);
|
logger.info(`Total resources: ${resourceCount}`);
|
||||||
|
|
||||||
if (Synchronizer.reportHasErrors(report)) {
|
if (Synchronizer.reportHasErrors(report)) {
|
||||||
this.logger().warn('There was some errors:');
|
logger.warn('There was some errors:');
|
||||||
for (let i = 0; i < report.errors.length; i++) {
|
for (let i = 0; i < report.errors.length; i++) {
|
||||||
const e = report.errors[i];
|
const e = report.errors[i];
|
||||||
this.logger().warn(e);
|
logger.warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,8 +296,8 @@ export default class Synchronizer {
|
|||||||
|
|
||||||
for (const r of lastRequests) {
|
for (const r of lastRequests) {
|
||||||
const timestamp = time.unixMsToLocalHms(r.timestamp);
|
const timestamp = time.unixMsToLocalHms(r.timestamp);
|
||||||
this.logger().info(`Req ${timestamp}: ${r.request}`);
|
logger.info(`Req ${timestamp}: ${r.request}`);
|
||||||
this.logger().info(`Res ${timestamp}: ${r.response}`);
|
logger.info(`Res ${timestamp}: ${r.response}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,9 +371,6 @@ export default class Synchronizer {
|
|||||||
this.syncTargetIsLocked_ = false;
|
this.syncTargetIsLocked_ = false;
|
||||||
this.cancelling_ = false;
|
this.cancelling_ = false;
|
||||||
|
|
||||||
const masterKeysBefore = await MasterKey.count();
|
|
||||||
let hasAutoEnabledEncryption = false;
|
|
||||||
|
|
||||||
const synchronizationId = time.unixMs().toString();
|
const synchronizationId = time.unixMs().toString();
|
||||||
|
|
||||||
const outputContext = Object.assign({}, lastContext);
|
const outputContext = Object.assign({}, lastContext);
|
||||||
@@ -394,11 +396,11 @@ export default class Synchronizer {
|
|||||||
// plain text.
|
// plain text.
|
||||||
try {
|
try {
|
||||||
if (this.resourceService()) {
|
if (this.resourceService()) {
|
||||||
this.logger().info('Indexing resources...');
|
logger.info('Indexing resources...');
|
||||||
await this.resourceService().indexNoteResources();
|
await this.resourceService().indexNoteResources();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger().error('Error indexing resources:', error);
|
logger.error('Error indexing resources:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before synchronising make sure all share_id properties are set
|
// Before synchronising make sure all share_id properties are set
|
||||||
@@ -415,13 +417,38 @@ export default class Synchronizer {
|
|||||||
this.api().setTempDirName(Dirnames.Temp);
|
this.api().setTempDirName(Dirnames.Temp);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const syncTargetInfo = await this.migrationHandler().checkCanSync();
|
const remoteInfo = await remoteSyncTargetInfo(this.api());
|
||||||
|
logger.info('Sync target remote info:', remoteInfo);
|
||||||
|
|
||||||
this.logger().info('Sync target info:', syncTargetInfo);
|
if (!remoteInfo.version) {
|
||||||
|
logger.info('Sync target is new - setting it up...');
|
||||||
if (!syncTargetInfo.version) {
|
|
||||||
this.logger().info('Sync target is new - setting it up...');
|
|
||||||
await this.migrationHandler().upgrade(Setting.value('syncVersion'));
|
await this.migrationHandler().upgrade(Setting.value('syncVersion'));
|
||||||
|
} else {
|
||||||
|
logger.info('Sync target is already setup - checking it...');
|
||||||
|
await this.migrationHandler().checkCanSync(remoteInfo);
|
||||||
|
|
||||||
|
const localInfo = localSyncTargetInfo();
|
||||||
|
|
||||||
|
logger.info('Sync target local info:', localInfo);
|
||||||
|
|
||||||
|
if (!syncTargetInfoEquals(localInfo, remoteInfo)) {
|
||||||
|
const newInfo = mergeSyncTargetInfos(localInfo, remoteInfo);
|
||||||
|
logger.info('Sync target info differs between local and remote - merging infos: ', newInfo);
|
||||||
|
await setRemoteSyncTargetInfo(this.api(), newInfo);
|
||||||
|
setLocalSyncTargetInfo(newInfo);
|
||||||
|
|
||||||
|
if (localInfo.e2ee !== remoteInfo.e2ee) {
|
||||||
|
if (newInfo.e2ee) {
|
||||||
|
const mk = activeMasterKey(newInfo);
|
||||||
|
await setupAndEnableEncryption(mk);
|
||||||
|
} else {
|
||||||
|
await setupAndDisableEncryption();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set it to remote anyway so that timestamps are the same
|
||||||
|
setLocalSyncTargetInfo(remoteInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'outdatedSyncTarget') {
|
if (error.code === 'outdatedSyncTarget') {
|
||||||
@@ -433,7 +460,7 @@ export default class Synchronizer {
|
|||||||
syncLock = await this.lockHandler().acquireLock(LockType.Sync, this.appType_, this.clientId_);
|
syncLock = await this.lockHandler().acquireLock(LockType.Sync, this.appType_, this.clientId_);
|
||||||
|
|
||||||
this.lockHandler().startAutoLockRefresh(syncLock, (error: any) => {
|
this.lockHandler().startAutoLockRefresh(syncLock, (error: any) => {
|
||||||
this.logger().warn('Could not refresh lock - cancelling sync. Error was:', error);
|
logger.warn('Could not refresh lock - cancelling sync. Error was:', error);
|
||||||
this.syncTargetIsLocked_ = true;
|
this.syncTargetIsLocked_ = true;
|
||||||
void this.cancel();
|
void this.cancel();
|
||||||
});
|
});
|
||||||
@@ -448,6 +475,7 @@ export default class Synchronizer {
|
|||||||
if (syncSteps.indexOf('update_remote') >= 0) {
|
if (syncSteps.indexOf('update_remote') >= 0) {
|
||||||
const donePaths: string[] = [];
|
const donePaths: string[] = [];
|
||||||
|
|
||||||
|
|
||||||
const completeItemProcessing = (path: string) => {
|
const completeItemProcessing = (path: string) => {
|
||||||
donePaths.push(path);
|
donePaths.push(path);
|
||||||
};
|
};
|
||||||
@@ -518,7 +546,7 @@ export default class Synchronizer {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'rejectedByTarget') {
|
if (error.code === 'rejectedByTarget') {
|
||||||
this.progressReport_.errors.push(error);
|
this.progressReport_.errors.push(error);
|
||||||
this.logger().warn(`Rejected by target: ${path}: ${error.message}`);
|
logger.warn(`Rejected by target: ${path}: ${error.message}`);
|
||||||
completeItemProcessing(path);
|
completeItemProcessing(path);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
@@ -540,6 +568,12 @@ export default class Synchronizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We no longer upload Master Keys however we keep them
|
||||||
|
// in the database for extra safety. In a future
|
||||||
|
// version, once it's confirmed that the new E2EE system
|
||||||
|
// works well, we can delete them.
|
||||||
|
if (local.type_ === ModelType.MasterKey) action = null;
|
||||||
|
|
||||||
this.logSyncOperation(action, local, remote, reason);
|
this.logSyncOperation(action, local, remote, reason);
|
||||||
|
|
||||||
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || action === 'updateRemote')) {
|
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || action === 'updateRemote')) {
|
||||||
@@ -577,7 +611,7 @@ export default class Synchronizer {
|
|||||||
// already been done" on the next loop, and sync
|
// already been done" on the next loop, and sync
|
||||||
// will never finish because we'll always end up
|
// will never finish because we'll always end up
|
||||||
// here.
|
// here.
|
||||||
this.logger().info(`Need to upload a resource, but blob is not present: ${path}`);
|
logger.info(`Need to upload a resource, but blob is not present: ${path}`);
|
||||||
await handleCannotSyncItem(ItemClass, syncTargetId, local, 'Trying to upload resource, but only metadata is present.');
|
await handleCannotSyncItem(ItemClass, syncTargetId, local, 'Trying to upload resource, but only metadata is present.');
|
||||||
action = null;
|
action = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -589,7 +623,7 @@ export default class Synchronizer {
|
|||||||
const localResourceContentPath = result.path;
|
const localResourceContentPath = result.path;
|
||||||
|
|
||||||
if (resource.size >= 10 * 1000 * 1000) {
|
if (resource.size >= 10 * 1000 * 1000) {
|
||||||
this.logger().warn(`Uploading a large resource (resourceId: ${local.id}, size:${resource.size} bytes) which may tie up the sync process.`);
|
logger.warn(`Uploading a large resource (resourceId: ${local.id}, size:${resource.size} bytes) which may tie up the sync process.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.apiCall('put', remoteContentPath, null, { path: localResourceContentPath, source: 'file', shareId: resource.share_id });
|
await this.apiCall('put', remoteContentPath, null, { path: localResourceContentPath, source: 'file', shareId: resource.share_id });
|
||||||
@@ -860,7 +894,7 @@ export default class Synchronizer {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'rejectedByTarget') {
|
if (error.code === 'rejectedByTarget') {
|
||||||
this.progressReport_.errors.push(error);
|
this.progressReport_.errors.push(error);
|
||||||
this.logger().warn(`Rejected by target: ${path}: ${error.message}`);
|
logger.warn(`Rejected by target: ${path}: ${error.message}`);
|
||||||
action = null;
|
action = null;
|
||||||
} else {
|
} else {
|
||||||
error.message = `On file ${path}: ${error.message}`;
|
error.message = `On file ${path}: ${error.message}`;
|
||||||
@@ -876,7 +910,7 @@ export default class Synchronizer {
|
|||||||
|
|
||||||
if (action == 'createLocal' || action == 'updateLocal') {
|
if (action == 'createLocal' || action == 'updateLocal') {
|
||||||
if (content === null) {
|
if (content === null) {
|
||||||
this.logger().warn(`Remote has been deleted between now and the delta() call? In that case it will be handled during the next sync: ${path}`);
|
logger.warn(`Remote has been deleted between now and the delta() call? In that case it will be handled during the next sync: ${path}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
content = ItemClass.filter(content);
|
content = ItemClass.filter(content);
|
||||||
@@ -908,17 +942,27 @@ export default class Synchronizer {
|
|||||||
await ResourceLocalState.save({ resource_id: content.id, fetch_status: Resource.FETCH_STATUS_IDLE });
|
await ResourceLocalState.save({ resource_id: content.id, fetch_status: Resource.FETCH_STATUS_IDLE });
|
||||||
}
|
}
|
||||||
|
|
||||||
await ItemClass.save(content, options);
|
if (content.type_ === ModelType.MasterKey) {
|
||||||
|
// Special case for master keys - if we download
|
||||||
|
// one, we only add it to the store if it's not
|
||||||
|
// already there. That can happen for example if
|
||||||
|
// the new E2EE migration was processed at a
|
||||||
|
// time a master key was still on the sync
|
||||||
|
// target. In that case, info.json would not
|
||||||
|
// have it.
|
||||||
|
//
|
||||||
|
// If info.json already has the key we shouldn't
|
||||||
|
// update because the most up to date keys
|
||||||
|
// should always be in info.json now.
|
||||||
|
const existingMasterKey = masterKeyById(content.id);
|
||||||
|
if (!existingMasterKey) {
|
||||||
|
logger.info(`Downloaded a master key that was not in info.json - adding it to the store. ID: ${content.id}`);
|
||||||
|
await MasterKey.save(content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await ItemClass.save(content, options);
|
||||||
|
|
||||||
if (creatingOrUpdatingResource) this.dispatch({ type: 'SYNC_CREATED_OR_UPDATED_RESOURCE', id: content.id });
|
if (creatingOrUpdatingResource) this.dispatch({ type: 'SYNC_CREATED_OR_UPDATED_RESOURCE', id: content.id });
|
||||||
|
|
||||||
if (!hasAutoEnabledEncryption && content.type_ === BaseModel.TYPE_MASTER_KEY && !masterKeysBefore) {
|
|
||||||
hasAutoEnabledEncryption = true;
|
|
||||||
this.logger().info('One master key was downloaded and none was previously available: automatically enabling encryption');
|
|
||||||
this.logger().info('Using master key: ', content.id);
|
|
||||||
await this.encryptionService().enableEncryption(content);
|
|
||||||
await this.encryptionService().loadMasterKeysFromSettings();
|
|
||||||
this.logger().info('Encryption has been enabled with downloaded master key as active key. However, note that no password was initially supplied. It will need to be provided by user.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.encryption_applied) this.dispatch({ type: 'SYNC_GOT_ENCRYPTED_ITEM' });
|
if (content.encryption_applied) this.dispatch({ type: 'SYNC_GOT_ENCRYPTED_ITEM' });
|
||||||
@@ -989,7 +1033,7 @@ export default class Synchronizer {
|
|||||||
// Only log an info statement for this since this is a common condition that is reported
|
// Only log an info statement for this since this is a common condition that is reported
|
||||||
// in the application, and needs to be resolved by the user.
|
// in the application, and needs to be resolved by the user.
|
||||||
// Or it's a temporary issue that will be resolved on next sync.
|
// Or it's a temporary issue that will be resolved on next sync.
|
||||||
this.logger().info(error.message);
|
logger.info(error.message);
|
||||||
|
|
||||||
if (error.code === 'failSafe' || error.code === 'lockError') {
|
if (error.code === 'failSafe' || error.code === 'lockError') {
|
||||||
// Get the message to display on UI, but not in testing to avoid poluting stdout
|
// Get the message to display on UI, but not in testing to avoid poluting stdout
|
||||||
@@ -998,10 +1042,10 @@ export default class Synchronizer {
|
|||||||
}
|
}
|
||||||
} else if (error.code === 'unknownItemType') {
|
} else if (error.code === 'unknownItemType') {
|
||||||
this.progressReport_.errors.push(_('Unknown item type downloaded - please upgrade Joplin to the latest version'));
|
this.progressReport_.errors.push(_('Unknown item type downloaded - please upgrade Joplin to the latest version'));
|
||||||
this.logger().error(error);
|
logger.error(error);
|
||||||
} else {
|
} else {
|
||||||
this.logger().error(error);
|
logger.error(error);
|
||||||
if (error.details) this.logger().error('Details:', error.details);
|
if (error.details) logger.error('Details:', error.details);
|
||||||
|
|
||||||
// Don't save to the report errors that are due to things like temporary network errors or timeout.
|
// Don't save to the report errors that are due to things like temporary network errors or timeout.
|
||||||
if (!shim.fetchRequestCanBeRetried(error)) {
|
if (!shim.fetchRequestCanBeRetried(error)) {
|
||||||
@@ -1019,7 +1063,7 @@ export default class Synchronizer {
|
|||||||
this.syncTargetIsLocked_ = false;
|
this.syncTargetIsLocked_ = false;
|
||||||
|
|
||||||
if (this.cancelling()) {
|
if (this.cancelling()) {
|
||||||
this.logger().info('Synchronisation was cancelled.');
|
logger.info('Synchronisation was cancelled.');
|
||||||
this.cancelling_ = false;
|
this.cancelling_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,7 +1073,7 @@ export default class Synchronizer {
|
|||||||
try {
|
try {
|
||||||
await this.shareService_.maintenance();
|
await this.shareService_.maintenance();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger().error('Could not run share service maintenance:', error);
|
logger.error('Could not run share service maintenance:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const { fileExtension } = require('../path-utils');
|
|||||||
const script = {};
|
const script = {};
|
||||||
|
|
||||||
script.exec = async function() {
|
script.exec = async function() {
|
||||||
|
if (!Setting.value('resourceDir')) return; // Probably running tests
|
||||||
|
|
||||||
const stats = await shim.fsDriver().readDirStats(Setting.value('resourceDir'));
|
const stats = await shim.fsDriver().readDirStats(Setting.value('resourceDir'));
|
||||||
|
|
||||||
let queries = [];
|
let queries = [];
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ const SearchEngine = require('../services/searchengine/SearchEngine').default;
|
|||||||
const script = {};
|
const script = {};
|
||||||
|
|
||||||
script.exec = async function() {
|
script.exec = async function() {
|
||||||
await SearchEngine.instance().rebuildIndex();
|
try {
|
||||||
|
await SearchEngine.instance().rebuildIndex();
|
||||||
|
} catch {
|
||||||
|
// Probably running tests.
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = script;
|
module.exports = script;
|
||||||
|
|||||||
30
packages/lib/migrations/40.ts
Normal file
30
packages/lib/migrations/40.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import JoplinDatabase from '../JoplinDatabase';
|
||||||
|
import { MigrationScript } from '../models/Migration';
|
||||||
|
import Setting from '../models/Setting';
|
||||||
|
import { SyncTargetInfo } from '../services/synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
|
const script: MigrationScript = {
|
||||||
|
|
||||||
|
exec: async function(db: JoplinDatabase): Promise<void> {
|
||||||
|
const masterKeys = await db.selectAll('SELECT * FROM master_keys');
|
||||||
|
|
||||||
|
const masterKeyMap: Record<string, any> = {};
|
||||||
|
for (const mk of masterKeys) masterKeyMap[mk.id] = mk;
|
||||||
|
|
||||||
|
const syncInfo: SyncTargetInfo = {
|
||||||
|
// When we first setup the local sync target info, we don't know
|
||||||
|
// what's on the remote one, so we set it to 0. During sync, the
|
||||||
|
// version from remote will be fetched.
|
||||||
|
version: 0,
|
||||||
|
e2ee: Setting.valueNoThrow('encryption.enabled', false),
|
||||||
|
masterKeys: masterKeyMap,
|
||||||
|
activeMasterKeyId: Setting.valueNoThrow('encryption.activeMasterKeyId', ''),
|
||||||
|
updatedTime: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Setting.setValue('sync.info', JSON.stringify(syncInfo));
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default script;
|
||||||
@@ -9,7 +9,8 @@ import Database from '../database';
|
|||||||
import ItemChange from './ItemChange';
|
import ItemChange from './ItemChange';
|
||||||
import ShareService from '../services/share/ShareService';
|
import ShareService from '../services/share/ShareService';
|
||||||
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
|
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
|
||||||
const JoplinError = require('../JoplinError.js');
|
import { encryptionEnabled } from '../services/synchronizer/syncTargetInfoUtils';
|
||||||
|
import JoplinError from '../JoplinError';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
@@ -410,7 +411,7 @@ export default class BaseItem extends BaseModel {
|
|||||||
|
|
||||||
const serialized = await ItemClass.serialize(item, shownKeys);
|
const serialized = await ItemClass.serialize(item, shownKeys);
|
||||||
|
|
||||||
if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported() || !itemCanBeEncrypted(item)) {
|
if (!encryptionEnabled() || !ItemClass.encryptionSupported() || !itemCanBeEncrypted(item)) {
|
||||||
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
||||||
if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted');
|
if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted');
|
||||||
return serialized;
|
return serialized;
|
||||||
@@ -598,7 +599,8 @@ export default class BaseItem extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async itemsThatNeedSync(syncTarget: number, limit = 100): Promise<ItemsThatNeedSyncResult> {
|
public static async itemsThatNeedSync(syncTarget: number, limit = 100): Promise<ItemsThatNeedSyncResult> {
|
||||||
const classNames = this.syncItemClassNames();
|
// Although we keep the master keys in the database, we no longer sync them
|
||||||
|
const classNames = this.syncItemClassNames().filter(n => n !== 'MasterKey');
|
||||||
|
|
||||||
for (let i = 0; i < classNames.length; i++) {
|
for (let i = 0; i < classNames.length; i++) {
|
||||||
const className = classNames[i];
|
const className = classNames[i];
|
||||||
@@ -687,7 +689,7 @@ export default class BaseItem extends BaseModel {
|
|||||||
throw new Error('Unreachable');
|
throw new Error('Unreachable');
|
||||||
}
|
}
|
||||||
|
|
||||||
static syncItemClassNames() {
|
static syncItemClassNames(): string[] {
|
||||||
return BaseItem.syncItemDefinitions_.map((def: any) => {
|
return BaseItem.syncItemDefinitions_.map((def: any) => {
|
||||||
return def.className;
|
return def.className;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import BaseModel from '../BaseModel';
|
import BaseModel from '../BaseModel';
|
||||||
import { MasterKeyEntity } from '../services/database/types';
|
import { MasterKeyEntity } from '../services/database/types';
|
||||||
|
import { masterKeyAll, masterKeyById, saveMasterKey } from '../services/synchronizer/syncTargetInfoUtils';
|
||||||
import BaseItem from './BaseItem';
|
import BaseItem from './BaseItem';
|
||||||
|
|
||||||
export default class MasterKey extends BaseItem {
|
export default class MasterKey extends BaseItem {
|
||||||
@@ -16,20 +17,34 @@ export default class MasterKey extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static latest() {
|
static latest() {
|
||||||
return this.modelSelectOne('SELECT * FROM master_keys WHERE created_time >= (SELECT max(created_time) FROM master_keys)');
|
throw new Error('Not implemented');
|
||||||
|
// return this.modelSelectOne('SELECT * FROM master_keys WHERE created_time >= (SELECT max(created_time) FROM master_keys)');
|
||||||
}
|
}
|
||||||
|
|
||||||
static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], method: number) {
|
static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], method: number) {
|
||||||
return masterKeys.filter(m => m.encryption_method !== method);
|
return masterKeys.filter(m => m.encryption_method !== method);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async save(o: MasterKeyEntity, options: any = null) {
|
public static async load(id: string): Promise<MasterKeyEntity> {
|
||||||
return super.save(o, options).then(item => {
|
return masterKeyById(id);
|
||||||
this.dispatch({
|
}
|
||||||
type: 'MASTERKEY_UPDATE_ONE',
|
|
||||||
item: item,
|
public static async allIds(): Promise<string[]> {
|
||||||
});
|
return masterKeyAll().map(k => k.id);
|
||||||
return item;
|
}
|
||||||
});
|
|
||||||
|
public static async all(): Promise<MasterKeyEntity[]> {
|
||||||
|
return masterKeyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async save(o: MasterKeyEntity): Promise<MasterKeyEntity> {
|
||||||
|
const newMasterKey = saveMasterKey(o);
|
||||||
|
|
||||||
|
// this.dispatch({
|
||||||
|
// type: 'MASTERKEY_UPDATE_ONE',
|
||||||
|
// item: newMasterKey,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return newMasterKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
packages/lib/models/Migration.test.js
Normal file
22
packages/lib/models/Migration.test.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
const __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function(resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
const test_utils_1 = require('../testing/test-utils');
|
||||||
|
describe('models_Note', function() {
|
||||||
|
beforeEach((done) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield test_utils_1.setupDatabaseAndSynchronizer(1);
|
||||||
|
yield test_utils_1.switchClient(1);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
it('should migrate to v40', () => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
// # sourceMappingURL=Migration.test.js.map
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
import BaseModel from '../BaseModel';
|
import BaseModel from '../BaseModel';
|
||||||
|
import JoplinDatabase from '../JoplinDatabase';
|
||||||
|
import migration40 from '../migrations/40';
|
||||||
|
import { MigrationEntity } from '../services/database/types';
|
||||||
|
|
||||||
const migrationScripts: Record<number, any> = {
|
export interface MigrationScript {
|
||||||
|
exec(db: JoplinDatabase): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrationScripts: Record<number, MigrationScript> = {
|
||||||
20: require('../migrations/20.js'),
|
20: require('../migrations/20.js'),
|
||||||
27: require('../migrations/27.js'),
|
27: require('../migrations/27.js'),
|
||||||
33: require('../migrations/33.js'),
|
33: require('../migrations/33.js'),
|
||||||
35: require('../migrations/35.js'),
|
35: require('../migrations/35.js'),
|
||||||
|
40: migration40,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Migration extends BaseModel {
|
export default class Migration extends BaseModel {
|
||||||
@@ -16,7 +24,7 @@ export default class Migration extends BaseModel {
|
|||||||
return BaseModel.TYPE_MIGRATION;
|
return BaseModel.TYPE_MIGRATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
static migrationsToDo() {
|
public static migrationsToDo(): Promise<MigrationEntity[]> {
|
||||||
return this.modelSelectAll('SELECT * FROM migrations ORDER BY number ASC');
|
return this.modelSelectAll('SELECT * FROM migrations ORDER BY number ASC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const { filename, safeFilename } = require('../path-utils');
|
|||||||
const { FsDriverDummy } = require('../fs-driver-dummy.js');
|
const { FsDriverDummy } = require('../fs-driver-dummy.js');
|
||||||
import JoplinError from '../JoplinError';
|
import JoplinError from '../JoplinError';
|
||||||
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
|
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
|
||||||
|
import { encryptionEnabled } from '../services/synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
export default class Resource extends BaseItem {
|
export default class Resource extends BaseItem {
|
||||||
|
|
||||||
@@ -196,7 +197,7 @@ export default class Resource extends BaseItem {
|
|||||||
public static async fullPathForSyncUpload(resource: ResourceEntity) {
|
public static async fullPathForSyncUpload(resource: ResourceEntity) {
|
||||||
const plainTextPath = this.fullPath(resource);
|
const plainTextPath = this.fullPath(resource);
|
||||||
|
|
||||||
if (!Setting.value('encryption.enabled') || !itemCanBeEncrypted(resource as any)) {
|
if (!encryptionEnabled() || !itemCanBeEncrypted(resource as any)) {
|
||||||
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
||||||
if (resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled');
|
if (resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled');
|
||||||
return { path: plainTextPath, resource: resource };
|
return { path: plainTextPath, resource: resource };
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ class Setting extends BaseModel {
|
|||||||
cacheDir: '',
|
cacheDir: '',
|
||||||
pluginDir: '',
|
pluginDir: '',
|
||||||
flagOpenDevTools: false,
|
flagOpenDevTools: false,
|
||||||
syncVersion: 2,
|
syncVersion: 3,
|
||||||
startupDevPlugins: [],
|
startupDevPlugins: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -210,6 +210,7 @@ class Setting extends BaseModel {
|
|||||||
private static customSections_: SettingSections = {};
|
private static customSections_: SettingSections = {};
|
||||||
private static changedKeys_: string[] = [];
|
private static changedKeys_: string[] = [];
|
||||||
private static fileHandler_: FileHandler = null;
|
private static fileHandler_: FileHandler = null;
|
||||||
|
private static settingFilename_: string = 'settings.json';
|
||||||
|
|
||||||
static tableName() {
|
static tableName() {
|
||||||
return 'settings';
|
return 'settings';
|
||||||
@@ -233,7 +234,15 @@ class Setting extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static get settingFilePath(): string {
|
public static get settingFilePath(): string {
|
||||||
return `${this.value('profileDir')}/settings.json`;
|
return `${this.value('profileDir')}/${this.settingFilename_}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get settingFilename(): string {
|
||||||
|
return this.settingFilename_;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static set settingFilename(v: string) {
|
||||||
|
this.settingFilename_ = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get fileHandler(): FileHandler {
|
public static get fileHandler(): FileHandler {
|
||||||
@@ -313,6 +322,14 @@ class Setting extends BaseModel {
|
|||||||
storage: SettingStorage.File,
|
storage: SettingStorage.File,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// This is a local cache of the sync target info.json file. See
|
||||||
|
// SyncTargetInfoUtils.ts for more info.
|
||||||
|
'sync.info': {
|
||||||
|
value: '',
|
||||||
|
type: SettingItemType.String,
|
||||||
|
public: false,
|
||||||
|
},
|
||||||
|
|
||||||
'sync.upgradeState': {
|
'sync.upgradeState': {
|
||||||
value: Setting.SYNC_UPGRADE_STATE_IDLE,
|
value: Setting.SYNC_UPGRADE_STATE_IDLE,
|
||||||
type: SettingItemType.Int,
|
type: SettingItemType.Int,
|
||||||
@@ -1640,7 +1657,7 @@ class Setting extends BaseModel {
|
|||||||
throw new Error(`Unhandled value type: ${md.type}`);
|
throw new Error(`Unhandled value type: ${md.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static value(key: string) {
|
public static value(key: string) {
|
||||||
// Need to copy arrays and objects since in setValue(), the old value and new one is compared
|
// Need to copy arrays and objects since in setValue(), the old value and new one is compared
|
||||||
// with strict equality and the value is updated only if changed. However if the caller acquire
|
// with strict equality and the value is updated only if changed. However if the caller acquire
|
||||||
// and object and change a key, the objects will be detected as equal. By returning a copy
|
// and object and change a key, the objects will be detected as equal. By returning a copy
|
||||||
@@ -1671,6 +1688,12 @@ class Setting extends BaseModel {
|
|||||||
return copyIfNeeded(md.value);
|
return copyIfNeeded(md.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function returns the default value if the setting key does not exist.
|
||||||
|
public static valueNoThrow(key: string, defaultValue: any) {
|
||||||
|
if (!this.keyExists(key)) return defaultValue;
|
||||||
|
return this.value(key);
|
||||||
|
}
|
||||||
|
|
||||||
static isEnum(key: string) {
|
static isEnum(key: string) {
|
||||||
const md = this.settingMetadata(key);
|
const md = this.settingMetadata(key);
|
||||||
return md.isEnum === true;
|
return md.isEnum === true;
|
||||||
|
|||||||
@@ -51,4 +51,8 @@ export default class FileHandler {
|
|||||||
this.valueJsonCache_ = json;
|
this.valueJsonCache_ = json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async clearCache() {
|
||||||
|
this.valueJsonCache_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
||||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
||||||
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json",
|
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json",
|
||||||
"test": "jest",
|
"test": "jest --verbose=false",
|
||||||
"test-ci": "npm run test"
|
"test-ci": "npm run test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface State {
|
|||||||
notesParentType: string;
|
notesParentType: string;
|
||||||
folders: any[];
|
folders: any[];
|
||||||
tags: any[];
|
tags: any[];
|
||||||
masterKeys: any[];
|
// masterKeys: any[];
|
||||||
notLoadedMasterKeys: any[];
|
notLoadedMasterKeys: any[];
|
||||||
searches: any[];
|
searches: any[];
|
||||||
highlightedWords: string[];
|
highlightedWords: string[];
|
||||||
@@ -104,7 +104,6 @@ export const defaultState: State = {
|
|||||||
notesParentType: null,
|
notesParentType: null,
|
||||||
folders: [],
|
folders: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
masterKeys: [],
|
|
||||||
notLoadedMasterKeys: [],
|
notLoadedMasterKeys: [],
|
||||||
searches: [],
|
searches: [],
|
||||||
highlightedWords: [],
|
highlightedWords: [],
|
||||||
@@ -975,7 +974,7 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'FOLDER_UPDATE_ONE':
|
case 'FOLDER_UPDATE_ONE':
|
||||||
case 'MASTERKEY_UPDATE_ONE':
|
// case 'MASTERKEY_UPDATE_ONE':
|
||||||
updateOneItem(draft, action);
|
updateOneItem(draft, action);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -983,9 +982,9 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
|||||||
handleItemDelete(draft, action);
|
handleItemDelete(draft, action);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'MASTERKEY_UPDATE_ALL':
|
// case 'MASTERKEY_UPDATE_ALL':
|
||||||
draft.masterKeys = action.items;
|
// draft.masterKeys = action.items;
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
case 'MASTERKEY_SET_NOT_LOADED':
|
case 'MASTERKEY_SET_NOT_LOADED':
|
||||||
draft.notLoadedMasterKeys = action.ids;
|
draft.notLoadedMasterKeys = action.ids;
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ export default class DecryptionWorker {
|
|||||||
return finalReport;
|
return finalReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(options: any) {
|
public async start(options: any = null): Promise<any> {
|
||||||
this.startCalls_.push(true);
|
this.startCalls_.push(true);
|
||||||
let output = null;
|
let output = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars, prefer-const */
|
import { fileContentEqual, setupDatabaseAndSynchronizer, supportDir, switchClient, objectsEqual, checkThrowAsync } from '../testing/test-utils';
|
||||||
|
import Folder from '../models/Folder';
|
||||||
|
import Note from '../models/Note';
|
||||||
|
import Setting from '../models/Setting';
|
||||||
|
import BaseItem from '../models/BaseItem';
|
||||||
|
import MasterKey from '../models/MasterKey';
|
||||||
|
import EncryptionService from '../services/EncryptionService';
|
||||||
|
import { setEncryptionEnabled } from './synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
const { fileContentEqual, setupDatabaseAndSynchronizer, supportDir, switchClient, objectsEqual, checkThrowAsync } = require('../testing/test-utils.js');
|
let service: EncryptionService = null;
|
||||||
const Folder = require('../models/Folder').default;
|
|
||||||
const Note = require('../models/Note').default;
|
|
||||||
const Setting = require('../models/Setting').default;
|
|
||||||
const BaseItem = require('../models/BaseItem').default;
|
|
||||||
const MasterKey = require('../models/MasterKey').default;
|
|
||||||
const EncryptionService = require('../services/EncryptionService').default;
|
|
||||||
|
|
||||||
let service = null;
|
|
||||||
|
|
||||||
describe('services_EncryptionService', function() {
|
describe('services_EncryptionService', function() {
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@ describe('services_EncryptionService', function() {
|
|||||||
await switchClient(1);
|
await switchClient(1);
|
||||||
service = new EncryptionService();
|
service = new EncryptionService();
|
||||||
BaseItem.encryptionService_ = service;
|
BaseItem.encryptionService_ = service;
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,11 +69,11 @@ describe('services_EncryptionService', function() {
|
|||||||
expect(plainTextOld.content).toBe(plainTextNew.content);
|
expect(plainTextOld.content).toBe(plainTextNew.content);
|
||||||
|
|
||||||
// Check that old content can be decrypted with new master key
|
// Check that old content can be decrypted with new master key
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
const cipherText = await service.encryptString('some secret');
|
const cipherText = await service.encryptString('some secret');
|
||||||
const plainTextFromOld = await service.decryptString(cipherText);
|
const plainTextFromOld = await service.decryptString(cipherText);
|
||||||
|
|
||||||
await service.loadMasterKey_(upgradedMasterKey, '123456', true);
|
await service.loadMasterKey(upgradedMasterKey, '123456', true);
|
||||||
const plainTextFromNew = await service.decryptString(cipherText);
|
const plainTextFromNew = await service.decryptString(cipherText);
|
||||||
|
|
||||||
expect(plainTextFromOld).toBe(plainTextFromNew);
|
expect(plainTextFromOld).toBe(plainTextFromNew);
|
||||||
@@ -86,6 +85,7 @@ describe('services_EncryptionService', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hasThrown = await checkThrowAsync(async () => await service.upgradeMasterKey(masterKey, '777'));
|
const hasThrown = await checkThrowAsync(async () => await service.upgradeMasterKey(masterKey, '777'));
|
||||||
|
expect(hasThrown).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should require a checksum only for old master keys', (async () => {
|
it('should require a checksum only for old master keys', (async () => {
|
||||||
@@ -128,7 +128,7 @@ describe('services_EncryptionService', function() {
|
|||||||
encryptionMethod: EncryptionService.METHOD_SJCL,
|
encryptionMethod: EncryptionService.METHOD_SJCL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const masterKey3 = await MasterKey.save(await service.generateMasterKey('123456'));
|
await MasterKey.save(await service.generateMasterKey('123456'));
|
||||||
|
|
||||||
const needUpgrade = service.masterKeysThatNeedUpgrading(await MasterKey.all());
|
const needUpgrade = service.masterKeysThatNeedUpgrading(await MasterKey.all());
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ describe('services_EncryptionService', function() {
|
|||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
|
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
const cipherText = await service.encryptString('some secret');
|
const cipherText = await service.encryptString('some secret');
|
||||||
const plainText = await service.decryptString(cipherText);
|
const plainText = await service.decryptString(cipherText);
|
||||||
@@ -161,7 +161,7 @@ describe('services_EncryptionService', function() {
|
|||||||
it('should decrypt various encryption methods', (async () => {
|
it('should decrypt various encryption methods', (async () => {
|
||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
{
|
{
|
||||||
const cipherText = await service.encryptString('some secret', {
|
const cipherText = await service.encryptString('some secret', {
|
||||||
@@ -188,7 +188,7 @@ describe('services_EncryptionService', function() {
|
|||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
|
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
const cipherText = await service.encryptString('some secret');
|
const cipherText = await service.encryptString('some secret');
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ describe('services_EncryptionService', function() {
|
|||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
|
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
let cipherText = await service.encryptString('some secret');
|
let cipherText = await service.encryptString('some secret');
|
||||||
cipherText += 'ABCDEFGHIJ';
|
cipherText += 'ABCDEFGHIJ';
|
||||||
@@ -217,7 +217,7 @@ describe('services_EncryptionService', function() {
|
|||||||
it('should encrypt and decrypt notes and folders', (async () => {
|
it('should encrypt and decrypt notes and folders', (async () => {
|
||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
const folder = await Folder.save({ title: 'folder' });
|
const folder = await Folder.save({ title: 'folder' });
|
||||||
const note = await Note.save({ title: 'encrypted note', body: 'something', parent_id: folder.id });
|
const note = await Note.save({ title: 'encrypted note', body: 'something', parent_id: folder.id });
|
||||||
@@ -248,7 +248,7 @@ describe('services_EncryptionService', function() {
|
|||||||
it('should encrypt and decrypt files', (async () => {
|
it('should encrypt and decrypt files', (async () => {
|
||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
const sourcePath = `${supportDir}/photo.jpg`;
|
const sourcePath = `${supportDir}/photo.jpg`;
|
||||||
const encryptedPath = `${Setting.value('tempDir')}/photo.crypted`;
|
const encryptedPath = `${Setting.value('tempDir')}/photo.crypted`;
|
||||||
@@ -265,7 +265,7 @@ describe('services_EncryptionService', function() {
|
|||||||
let masterKey = await service.generateMasterKey('123456');
|
let masterKey = await service.generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
|
|
||||||
await service.loadMasterKey_(masterKey, '123456', true);
|
await service.loadMasterKey(masterKey, '123456', true);
|
||||||
|
|
||||||
// First check that we can replicate the error with the old encryption method
|
// First check that we can replicate the error with the old encryption method
|
||||||
service.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL;
|
service.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL;
|
||||||
@@ -7,6 +7,7 @@ import BaseItem from '../models/BaseItem';
|
|||||||
|
|
||||||
const { padLeft } = require('../string-utils.js');
|
const { padLeft } = require('../string-utils.js');
|
||||||
import JoplinError from '../JoplinError';
|
import JoplinError from '../JoplinError';
|
||||||
|
import { activeMasterKeyId, setActiveMasterKeyId } from './synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
function hexPad(s: string, length: number) {
|
function hexPad(s: string, length: number) {
|
||||||
return padLeft(s, length, '0');
|
return padLeft(s, length, '0');
|
||||||
@@ -45,8 +46,8 @@ export default class EncryptionService {
|
|||||||
// changed easily since the chunk size is incorporated into the encrypted data.
|
// changed easily since the chunk size is incorporated into the encrypted data.
|
||||||
private chunkSize_ = 5000;
|
private chunkSize_ = 5000;
|
||||||
private loadedMasterKeys_: Record<string, string> = {};
|
private loadedMasterKeys_: Record<string, string> = {};
|
||||||
private activeMasterKeyId_: string = null;
|
// private activeMasterKeyId_: string = null;
|
||||||
private defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL_1A;
|
public defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL_1A;
|
||||||
private defaultMasterKeyEncryptionMethod_ = EncryptionService.METHOD_SJCL_4;
|
private defaultMasterKeyEncryptionMethod_ = EncryptionService.METHOD_SJCL_4;
|
||||||
private logger_ = new Logger();
|
private logger_ = new Logger();
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ export default class EncryptionService {
|
|||||||
// changed easily since the chunk size is incorporated into the encrypted data.
|
// changed easily since the chunk size is incorporated into the encrypted data.
|
||||||
this.chunkSize_ = 5000;
|
this.chunkSize_ = 5000;
|
||||||
this.loadedMasterKeys_ = {};
|
this.loadedMasterKeys_ = {};
|
||||||
this.activeMasterKeyId_ = null;
|
// this.activeMasterKeyId_ = null;
|
||||||
this.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL_1A;
|
this.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL_1A;
|
||||||
this.defaultMasterKeyEncryptionMethod_ = EncryptionService.METHOD_SJCL_4;
|
this.defaultMasterKeyEncryptionMethod_ = EncryptionService.METHOD_SJCL_4;
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
@@ -102,67 +103,6 @@ export default class EncryptionService {
|
|||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateMasterKeyAndEnableEncryption(password: string) {
|
|
||||||
let masterKey = await this.generateMasterKey(password);
|
|
||||||
masterKey = await MasterKey.save(masterKey);
|
|
||||||
await this.enableEncryption(masterKey, password);
|
|
||||||
await this.loadMasterKeysFromSettings();
|
|
||||||
return masterKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
async enableEncryption(masterKey: MasterKeyEntity, password: string = null) {
|
|
||||||
Setting.setValue('encryption.enabled', true);
|
|
||||||
Setting.setValue('encryption.activeMasterKeyId', masterKey.id);
|
|
||||||
|
|
||||||
if (password) {
|
|
||||||
const passwordCache = Setting.value('encryption.passwordCache');
|
|
||||||
passwordCache[masterKey.id] = password;
|
|
||||||
Setting.setValue('encryption.passwordCache', passwordCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark only the non-encrypted ones for sync since, if there are encrypted ones,
|
|
||||||
// it means they come from the sync target and are already encrypted over there.
|
|
||||||
await BaseItem.markAllNonEncryptedForSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async disableEncryption() {
|
|
||||||
// Allow disabling encryption even if some items are still encrypted, because whether E2EE is enabled or disabled
|
|
||||||
// should not affect whether items will enventually be decrypted or not (DecryptionWorker will still work as
|
|
||||||
// long as there are encrypted items). Also even if decryption is disabled, it's possible that encrypted items
|
|
||||||
// will still be received via synchronisation.
|
|
||||||
|
|
||||||
// const hasEncryptedItems = await BaseItem.hasEncryptedItems();
|
|
||||||
// if (hasEncryptedItems) throw new Error(_('Encryption cannot currently be disabled because some items are still encrypted. Please wait for all the items to be decrypted and try again.'));
|
|
||||||
|
|
||||||
Setting.setValue('encryption.enabled', false);
|
|
||||||
// The only way to make sure everything gets decrypted on the sync target is
|
|
||||||
// to re-sync everything.
|
|
||||||
await BaseItem.forceSyncAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadMasterKeysFromSettings() {
|
|
||||||
const masterKeys = await MasterKey.all();
|
|
||||||
const passwords = Setting.value('encryption.passwordCache');
|
|
||||||
const activeMasterKeyId = Setting.value('encryption.activeMasterKeyId');
|
|
||||||
|
|
||||||
this.logger().info(`Trying to load ${masterKeys.length} master keys...`);
|
|
||||||
|
|
||||||
for (let i = 0; i < masterKeys.length; i++) {
|
|
||||||
const mk = masterKeys[i];
|
|
||||||
const password = passwords[mk.id];
|
|
||||||
if (this.isMasterKeyLoaded(mk.id)) continue;
|
|
||||||
if (!password) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.loadMasterKey_(mk, password, activeMasterKeyId === mk.id);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger().warn(`Cannot load master key ${mk.id}. Invalid password?`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger().info(`Loaded master keys: ${this.loadedMasterKeysCount()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedMasterKeysCount() {
|
loadedMasterKeysCount() {
|
||||||
let output = 0;
|
let output = 0;
|
||||||
for (const n in this.loadedMasterKeys_) {
|
for (const n in this.loadedMasterKeys_) {
|
||||||
@@ -181,23 +121,27 @@ export default class EncryptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setActiveMasterKeyId(id: string) {
|
setActiveMasterKeyId(id: string) {
|
||||||
this.activeMasterKeyId_ = id;
|
setActiveMasterKeyId(id);
|
||||||
|
// this.activeMasterKeyId_ = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeMasterKeyId() {
|
activeMasterKeyId() {
|
||||||
if (!this.activeMasterKeyId_) {
|
const id = activeMasterKeyId();
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
const error: any = new Error('No master key is defined as active. Check this: Either one or more master keys exist but no password was provided for any of them. Or no master key exist. Or master keys and password exist, but none was set as active.');
|
const error: any = new Error('No master key is defined as active. Check this: Either one or more master keys exist but no password was provided for any of them. Or no master key exist. Or master keys and password exist, but none was set as active.');
|
||||||
error.code = 'noActiveMasterKey';
|
error.code = 'noActiveMasterKey';
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return this.activeMasterKeyId_;
|
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
isMasterKeyLoaded(id: string) {
|
isMasterKeyLoaded(id: string) {
|
||||||
return !!this.loadedMasterKeys_[id];
|
return !!this.loadedMasterKeys_[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMasterKey_(model: MasterKeyEntity, password: string, makeActive = false) {
|
public async loadMasterKey(model: MasterKeyEntity, password: string, makeActive = false) {
|
||||||
if (!model.id) throw new Error('Master key does not have an ID - save it first');
|
if (!model.id) throw new Error('Master key does not have an ID - save it first');
|
||||||
this.loadedMasterKeys_[model.id] = await this.decryptMasterKey_(model, password);
|
this.loadedMasterKeys_[model.id] = await this.decryptMasterKey_(model, password);
|
||||||
if (makeActive) this.setActiveMasterKeyId(model.id);
|
if (makeActive) this.setActiveMasterKeyId(model.id);
|
||||||
@@ -244,22 +188,6 @@ export default class EncryptionService {
|
|||||||
return sjcl.codec.hex.fromBits(bitArray);
|
return sjcl.codec.hex.fromBits(bitArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
// async seedSjcl() {
|
|
||||||
// throw new Error('NOT TESTED');
|
|
||||||
|
|
||||||
// // Just putting this here in case it becomes needed
|
|
||||||
// // Normally seeding random bytes is not needed for our use since
|
|
||||||
// // we use shim.randomBytes directly to generate master keys.
|
|
||||||
|
|
||||||
// const sjcl = shim.sjclModule;
|
|
||||||
// const randomBytes = await shim.randomBytes(1024 / 8);
|
|
||||||
// const hexBytes = randomBytes.map(a => {
|
|
||||||
// return a.toString(16);
|
|
||||||
// });
|
|
||||||
// const hexSeed = sjcl.codec.hex.toBits(hexBytes.join(''));
|
|
||||||
// sjcl.random.addEntropy(hexSeed, 1024, 'shim.randomBytes');
|
|
||||||
// }
|
|
||||||
|
|
||||||
async generateApiToken() {
|
async generateApiToken() {
|
||||||
return await this.randomHexString(64);
|
return await this.randomHexString(64);
|
||||||
}
|
}
|
||||||
|
|||||||
51
packages/lib/services/MigrationService.test.ts
Normal file
51
packages/lib/services/MigrationService.test.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import Setting from '../models/Setting';
|
||||||
|
import { setupDatabaseAndSynchronizer, switchClient } from '../testing/test-utils';
|
||||||
|
import MigrationService from './MigrationService';
|
||||||
|
import { SyncTargetInfo } from './synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
|
function migrationService() {
|
||||||
|
return new MigrationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MigrationService', function() {
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hard to test how it would migrate master keys since the code to save them
|
||||||
|
// to the database no longer exists.
|
||||||
|
|
||||||
|
// it('should migrate to v40 - with keys', async () => {
|
||||||
|
// const startTime = Date.now();
|
||||||
|
|
||||||
|
// const mk1 = await MasterKey.save(await encryptionService().generateMasterKey('1'));
|
||||||
|
// const mk2 = await MasterKey.save(await encryptionService().generateMasterKey('2'));
|
||||||
|
// Setting.setValue('encryption.enabled', true);
|
||||||
|
// Setting.setValue('encryption.activeMasterKeyId', mk1.id);
|
||||||
|
|
||||||
|
// await migrationService().runScript(40, MasterKey.db());
|
||||||
|
|
||||||
|
// const info: SyncTargetInfo = JSON.parse(Setting.value('sync.info'));
|
||||||
|
// expect(info.e2ee).toBe(true);
|
||||||
|
// expect(info.activeMasterKeyId).toBe(mk1.id);
|
||||||
|
// expect(info.version).toBe(0);
|
||||||
|
// expect(Object.keys(info.masterKeys).sort()).toEqual([mk1.id, mk2.id].sort());
|
||||||
|
// expect(info.updatedTime).toBeGreaterThanOrEqual(startTime);
|
||||||
|
// });
|
||||||
|
|
||||||
|
it('should migrate to v40 - empty', async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
await migrationService().runScript(40);
|
||||||
|
|
||||||
|
const info: SyncTargetInfo = JSON.parse(Setting.value('sync.info'));
|
||||||
|
expect(info.e2ee).toBe(false);
|
||||||
|
expect(info.activeMasterKeyId).toBe('');
|
||||||
|
expect(info.version).toBe(0);
|
||||||
|
expect(Object.keys(info.masterKeys)).toEqual([]);
|
||||||
|
expect(info.updatedTime).toBeGreaterThanOrEqual(startTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -5,26 +5,28 @@ export default class MigrationService extends BaseService {
|
|||||||
|
|
||||||
private static instance_: MigrationService;
|
private static instance_: MigrationService;
|
||||||
|
|
||||||
static instance() {
|
public static instance() {
|
||||||
if (this.instance_) return this.instance_;
|
if (this.instance_) return this.instance_;
|
||||||
this.instance_ = new MigrationService();
|
this.instance_ = new MigrationService();
|
||||||
return this.instance_;
|
return this.instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async runScript(num: number) {
|
public async runScript(num: number) {
|
||||||
const script = Migration.script(num);
|
const script = Migration.script(num);
|
||||||
await script.exec();
|
await script.exec(Migration.db());
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
public async run(migrationsToSkip: number[] = []) {
|
||||||
const migrations = await Migration.migrationsToDo();
|
const migrations = await Migration.migrationsToDo();
|
||||||
|
|
||||||
for (const migration of migrations) {
|
for (const migration of migrations) {
|
||||||
this.logger().info(`Running migration: ${migration.number}`);
|
this.logger().info(`Running migration: ${migration.number}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (migrationsToSkip.includes(migration.number)) continue;
|
||||||
|
|
||||||
await this.runScript(migration.number);
|
await this.runScript(migration.number);
|
||||||
await Migration.delete(migration.id);
|
await Migration.delete(migration.id as any);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger().error(`Cannot run migration: ${migration.number}`, error);
|
this.logger().error(`Cannot run migration: ${migration.number}`, error);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
import { resourceService, decryptionWorker, supportDir, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, setupDatabaseAndSynchronizer, db, synchronizer, switchClient } from '../testing/test-utils';
|
||||||
import time from '../time';
|
import time from '../time';
|
||||||
import NoteResource from '../models/NoteResource';
|
import NoteResource from '../models/NoteResource';
|
||||||
import ResourceService from '../services/ResourceService';
|
import ResourceService from '../services/ResourceService';
|
||||||
import shim from '../shim';
|
import shim from '../shim';
|
||||||
|
|
||||||
const { resourceService, decryptionWorker, supportDir, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, setupDatabaseAndSynchronizer, db, synchronizer, switchClient } = require('../testing/test-utils.js');
|
|
||||||
import Folder from '../models/Folder';
|
import Folder from '../models/Folder';
|
||||||
import Note from '../models/Note';
|
import Note from '../models/Note';
|
||||||
import Resource from '../models/Resource';
|
import Resource from '../models/Resource';
|
||||||
import SearchEngine from '../services/searchengine/SearchEngine';
|
import SearchEngine from '../services/searchengine/SearchEngine';
|
||||||
|
import { setupAndEnableEncryption, loadMasterKeysFromSettings } from './e2ee/utils';
|
||||||
|
|
||||||
describe('services_ResourceService', function() {
|
describe('services_ResourceService', function() {
|
||||||
|
|
||||||
@@ -139,8 +139,8 @@ describe('services_ResourceService', function() {
|
|||||||
// Eventually R1 is deleted because service thinks that it was at some point associated with a note, but no longer.
|
// Eventually R1 is deleted because service thinks that it was at some point associated with a note, but no longer.
|
||||||
|
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
const folder1 = await Folder.save({ title: 'folder1' });
|
const folder1 = await Folder.save({ title: 'folder1' });
|
||||||
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||||
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`); // R1
|
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`); // R1
|
||||||
@@ -151,8 +151,8 @@ describe('services_ResourceService', function() {
|
|||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
|
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await decryptionWorker().start();
|
await decryptionWorker().start();
|
||||||
{
|
{
|
||||||
const n1 = await Note.load(note1.id);
|
const n1 = await Note.load(note1.id);
|
||||||
|
|||||||
76
packages/lib/services/e2ee/utils.ts
Normal file
76
packages/lib/services/e2ee/utils.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import Logger from '../../Logger';
|
||||||
|
import BaseItem from '../../models/BaseItem';
|
||||||
|
import MasterKey from '../../models/MasterKey';
|
||||||
|
import Setting from '../../models/Setting';
|
||||||
|
import { MasterKeyEntity } from '../database/types';
|
||||||
|
import EncryptionService from '../EncryptionService';
|
||||||
|
import { localSyncTargetInfo, setEncryptionEnabled } from '../synchronizer/syncTargetInfoUtils';
|
||||||
|
|
||||||
|
const logger = Logger.create('e2ee/utils');
|
||||||
|
|
||||||
|
export async function setupAndEnableEncryption(masterKey: MasterKeyEntity = null, password: string = null) {
|
||||||
|
if (!masterKey) {
|
||||||
|
// May happen for example if there are master keys in info.json but none
|
||||||
|
// of them is set as active. But in fact, unless there is a bug in the
|
||||||
|
// application, this shouldn't happen.
|
||||||
|
logger.warn('Setting up E2EE without a master key - user will need to either generate one or select one of the existing ones as active');
|
||||||
|
}
|
||||||
|
|
||||||
|
setEncryptionEnabled(true, masterKey ? masterKey.id : null);
|
||||||
|
|
||||||
|
if (masterKey && password) {
|
||||||
|
const passwordCache = Setting.value('encryption.passwordCache');
|
||||||
|
passwordCache[masterKey.id] = password;
|
||||||
|
Setting.setValue('encryption.passwordCache', passwordCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark only the non-encrypted ones for sync since, if there are encrypted ones,
|
||||||
|
// it means they come from the sync target and are already encrypted over there.
|
||||||
|
await BaseItem.markAllNonEncryptedForSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupAndDisableEncryption() {
|
||||||
|
// Allow disabling encryption even if some items are still encrypted, because whether E2EE is enabled or disabled
|
||||||
|
// should not affect whether items will enventually be decrypted or not (DecryptionWorker will still work as
|
||||||
|
// long as there are encrypted items). Also even if decryption is disabled, it's possible that encrypted items
|
||||||
|
// will still be received via synchronisation.
|
||||||
|
|
||||||
|
setEncryptionEnabled(false);
|
||||||
|
|
||||||
|
// The only way to make sure everything gets decrypted on the sync target is
|
||||||
|
// to re-sync everything.
|
||||||
|
await BaseItem.forceSyncAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMasterKeyAndEnableEncryption(service: EncryptionService, password: string) {
|
||||||
|
let masterKey = await service.generateMasterKey(password);
|
||||||
|
masterKey = await MasterKey.save(masterKey);
|
||||||
|
await setupAndEnableEncryption(masterKey, password);
|
||||||
|
await loadMasterKeysFromSettings(service);
|
||||||
|
return masterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadMasterKeysFromSettings(service: EncryptionService) {
|
||||||
|
const syncTargetInfo = localSyncTargetInfo();
|
||||||
|
|
||||||
|
const masterKeys = await MasterKey.all();
|
||||||
|
const passwords = Setting.value('encryption.passwordCache');
|
||||||
|
const activeMasterKeyId = syncTargetInfo.activeMasterKeyId || '';
|
||||||
|
|
||||||
|
logger.info(`Trying to load ${masterKeys.length} master keys...`);
|
||||||
|
|
||||||
|
for (let i = 0; i < masterKeys.length; i++) {
|
||||||
|
const mk = masterKeys[i];
|
||||||
|
const password = passwords[mk.id];
|
||||||
|
if (service.isMasterKeyLoaded(mk.id)) continue;
|
||||||
|
if (!password) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.loadMasterKey(mk, password, activeMasterKeyId === mk.id);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Cannot load master key ${mk.id}. Invalid password?`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`);
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import shim from '../../shim';
|
|||||||
import filterParser from './filterParser';
|
import filterParser from './filterParser';
|
||||||
import queryBuilder from './queryBuilder';
|
import queryBuilder from './queryBuilder';
|
||||||
import { ItemChangeEntity, NoteEntity } from '../database/types';
|
import { ItemChangeEntity, NoteEntity } from '../database/types';
|
||||||
|
import JoplinDatabase from '../../JoplinDatabase';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
|
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ export default class SearchEngine {
|
|||||||
|
|
||||||
public dispatch: Function = (_o: any) => {};
|
public dispatch: Function = (_o: any) => {};
|
||||||
private logger_ = new Logger();
|
private logger_ = new Logger();
|
||||||
private db_: any = null;
|
private db_: JoplinDatabase = null;
|
||||||
private isIndexing_ = false;
|
private isIndexing_ = false;
|
||||||
private syncCalls_: any[] = [];
|
private syncCalls_: any[] = [];
|
||||||
private scheduleSyncTablesIID_: any;
|
private scheduleSyncTablesIID_: any;
|
||||||
@@ -62,7 +63,8 @@ export default class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rebuildIndex_() {
|
async rebuildIndex_() {
|
||||||
let noteIds: string[] = await this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND encryption_applied = 0');
|
let noteIds: string[] = await this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND encryption_applied = 0') as any;
|
||||||
|
|
||||||
noteIds = noteIds.map((n: any) => n.id);
|
noteIds = noteIds.map((n: any) => n.id);
|
||||||
|
|
||||||
const lastChangeId = await ItemChange.lastChangeId();
|
const lastChangeId = await ItemChange.lastChangeId();
|
||||||
|
|||||||
210
packages/lib/services/synchronizer/MigrationHandler.test.ts
Normal file
210
packages/lib/services/synchronizer/MigrationHandler.test.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// To create a sync target snapshot for the current syncVersion:
|
||||||
|
// - In test-utils, set syncTargetName_ to "filesystem"
|
||||||
|
// - Then run:
|
||||||
|
// node tests/support/createSyncTargetSnapshot.js normal && node tests/support/createSyncTargetSnapshot.js e2ee
|
||||||
|
//
|
||||||
|
// These tests work by a taking a sync target snapshot at a version n and upgrading it to n+1.
|
||||||
|
|
||||||
|
import LockHandler from './LockHandler';
|
||||||
|
import MigrationHandler from './MigrationHandler';
|
||||||
|
import { Dirnames } from './utils/types';
|
||||||
|
import { setSyncTargetName, fileApi, synchronizer, decryptionWorker, encryptionService, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow, db } from '../../testing/test-utils';
|
||||||
|
import { deploySyncTargetSnapshot, testData, checkTestData } from '../../testing/syncTargetUtils';
|
||||||
|
import Setting from '../../models/Setting';
|
||||||
|
import MasterKey from '../../models/MasterKey';
|
||||||
|
import { loadMasterKeysFromSettings } from '../e2ee/utils';
|
||||||
|
import { remoteSyncTargetInfo } from './syncTargetInfoUtils';
|
||||||
|
|
||||||
|
const specTimeout = 60000 * 10; // Nextcloud tests can be slow
|
||||||
|
|
||||||
|
let lockHandler_: LockHandler = null;
|
||||||
|
let migrationHandler_: MigrationHandler = null;
|
||||||
|
|
||||||
|
function lockHandler(): LockHandler {
|
||||||
|
if (lockHandler_) return lockHandler_;
|
||||||
|
lockHandler_ = new LockHandler(fileApi());
|
||||||
|
return lockHandler_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrationHandler(clientId: string = 'abcd'): MigrationHandler {
|
||||||
|
if (migrationHandler_) return migrationHandler_;
|
||||||
|
migrationHandler_ = new MigrationHandler(fileApi(), db(), lockHandler(), 'desktop', clientId);
|
||||||
|
return migrationHandler_;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MigrationTests {
|
||||||
|
[key: string]: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrationTests: MigrationTests = {
|
||||||
|
2: async function() {
|
||||||
|
const items = (await fileApi().list('', { includeHidden: true })).items;
|
||||||
|
expect(items.filter((i: any) => i.path === '.resource' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'locks' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'temp' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'info.json' && !i.isDir).length).toBe(1);
|
||||||
|
|
||||||
|
const versionForOldClients = await fileApi().get('.sync/version.txt');
|
||||||
|
expect(versionForOldClients).toBe('2');
|
||||||
|
},
|
||||||
|
|
||||||
|
3: async function() {
|
||||||
|
const items = (await fileApi().list('', { includeHidden: true })).items;
|
||||||
|
expect(items.filter((i: any) => i.path === '.resource' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'locks' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'temp' && i.isDir).length).toBe(1);
|
||||||
|
expect(items.filter((i: any) => i.path === 'info.json' && !i.isDir).length).toBe(1);
|
||||||
|
|
||||||
|
const versionForOldClients = await fileApi().get('.sync/version.txt');
|
||||||
|
expect(versionForOldClients).toBe('2');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const maxSyncVersion = Number(Object.keys(migrationTests).sort().pop());
|
||||||
|
|
||||||
|
async function testMigration(migrationVersion: number, maxSyncVersion: number) {
|
||||||
|
await deploySyncTargetSnapshot('normal', migrationVersion - 1);
|
||||||
|
|
||||||
|
const info = await remoteSyncTargetInfo(fileApi());
|
||||||
|
expect(info.version).toBe(migrationVersion - 1);
|
||||||
|
|
||||||
|
// Now, migrate to the new version
|
||||||
|
Setting.setConstant('syncVersion', migrationVersion);
|
||||||
|
await migrationHandler().upgrade(migrationVersion);
|
||||||
|
|
||||||
|
// Verify that it has been upgraded
|
||||||
|
const newInfo = await remoteSyncTargetInfo(fileApi());
|
||||||
|
expect(newInfo.version).toBe(migrationVersion);
|
||||||
|
await migrationTests[migrationVersion]();
|
||||||
|
|
||||||
|
// If we're not on the latest version, we exit here, because although the
|
||||||
|
// synchronizer can run the migration from one version to another, it cannot
|
||||||
|
// sync the data on an older version (since the code has been changed to
|
||||||
|
// work with the latest version).
|
||||||
|
if (migrationVersion !== maxSyncVersion) return;
|
||||||
|
|
||||||
|
// Now sync with that upgraded target
|
||||||
|
await synchronizer().start();
|
||||||
|
|
||||||
|
// Check that the data has not been altered
|
||||||
|
await expectNotThrow(async () => await checkTestData(testData));
|
||||||
|
|
||||||
|
// Check what happens if we switch to a different client and sync
|
||||||
|
await switchClient(2);
|
||||||
|
Setting.setConstant('syncVersion', migrationVersion);
|
||||||
|
await synchronizer().start();
|
||||||
|
await expectNotThrow(async () => await checkTestData(testData));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testMigrationE2EE(migrationVersion: number, maxSyncVersion: number) {
|
||||||
|
// First create some test data that will be used to validate
|
||||||
|
// that the migration didn't alter any data.
|
||||||
|
await deploySyncTargetSnapshot('e2ee', migrationVersion - 1);
|
||||||
|
|
||||||
|
// Now, migrate to the new version
|
||||||
|
Setting.setConstant('syncVersion', migrationVersion);
|
||||||
|
await migrationHandler().upgrade(migrationVersion);
|
||||||
|
|
||||||
|
// Verify that it has been upgraded
|
||||||
|
const newInfo = await remoteSyncTargetInfo(fileApi());
|
||||||
|
expect(newInfo.version).toBe(migrationVersion);
|
||||||
|
await migrationTests[migrationVersion]();
|
||||||
|
|
||||||
|
if (migrationVersion !== maxSyncVersion) return;
|
||||||
|
|
||||||
|
// Now sync with that upgraded target
|
||||||
|
await synchronizer().start();
|
||||||
|
|
||||||
|
// Decrypt the data
|
||||||
|
const masterKey = (await MasterKey.all())[0];
|
||||||
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
await decryptionWorker().start();
|
||||||
|
|
||||||
|
// Check that the data has not been altered
|
||||||
|
await expectNotThrow(async () => await checkTestData(testData));
|
||||||
|
|
||||||
|
// Check what happens if we switch to a different client and sync
|
||||||
|
await switchClient(2);
|
||||||
|
Setting.setConstant('syncVersion', migrationVersion);
|
||||||
|
await synchronizer().start();
|
||||||
|
|
||||||
|
// Should throw because data hasn't been decrypted yet
|
||||||
|
await expectThrow(async () => await checkTestData(testData));
|
||||||
|
|
||||||
|
// Enable E2EE and decrypt
|
||||||
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
await decryptionWorker().start();
|
||||||
|
|
||||||
|
// Should not throw because data is decrypted
|
||||||
|
await expectNotThrow(async () => await checkTestData(testData));
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousSyncTargetName: string = '';
|
||||||
|
|
||||||
|
describe('MigrationHandler', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done: Function) => {
|
||||||
|
// Note that, for undocumented reasons, the timeout argument passed
|
||||||
|
// to `test()` (or `it()`) is ignored if it is higher than the
|
||||||
|
// global Jest timeout. So we need to set it globally.
|
||||||
|
//
|
||||||
|
// https://github.com/facebook/jest/issues/5055#issuecomment-513585906
|
||||||
|
jest.setTimeout(specTimeout);
|
||||||
|
|
||||||
|
// To test the migrations, we have to use the filesystem sync target
|
||||||
|
// because the sync target snapshots are plain files. Eventually
|
||||||
|
// it should be possible to copy a filesystem target to memory
|
||||||
|
// but for now that will do.
|
||||||
|
previousSyncTargetName = setSyncTargetName('filesystem');
|
||||||
|
lockHandler_ = null;
|
||||||
|
migrationHandler_ = null;
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await setupDatabaseAndSynchronizer(2);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async (done: Function) => {
|
||||||
|
setSyncTargetName(previousSyncTargetName);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init a new sync target', (async () => {
|
||||||
|
// Check that basic folders "locks" and "temp" are created for new sync targets.
|
||||||
|
await migrationHandler().upgrade(1);
|
||||||
|
const result = await fileApi().list();
|
||||||
|
expect(result.items.filter((i: any) => i.path === Dirnames.Locks).length).toBe(1);
|
||||||
|
expect(result.items.filter((i: any) => i.path === Dirnames.Temp).length).toBe(1);
|
||||||
|
}), specTimeout);
|
||||||
|
|
||||||
|
it('should not allow syncing if the sync target is out-dated', (async () => {
|
||||||
|
await synchronizer().start();
|
||||||
|
await fileApi().put('info.json', `{"version":${Setting.value('syncVersion') - 1}}`);
|
||||||
|
await expectThrow(async () => await migrationHandler().checkCanSync(), 'outdatedSyncTarget');
|
||||||
|
}), specTimeout);
|
||||||
|
|
||||||
|
it('should not allow syncing if the client is out-dated', (async () => {
|
||||||
|
await synchronizer().start();
|
||||||
|
await fileApi().put('info.json', `{"version":${Setting.value('syncVersion') + 1}}`);
|
||||||
|
await expectThrow(async () => await migrationHandler().checkCanSync(), 'outdatedClient');
|
||||||
|
}), specTimeout);
|
||||||
|
|
||||||
|
it('should apply migration 2 normal', async () => {
|
||||||
|
await testMigration(2, maxSyncVersion);
|
||||||
|
}, specTimeout);
|
||||||
|
|
||||||
|
it('should apply migration 2 E2EE', async () => {
|
||||||
|
await testMigrationE2EE(2, maxSyncVersion);
|
||||||
|
}, specTimeout);
|
||||||
|
|
||||||
|
it('should apply migration 3 normal', async () => {
|
||||||
|
await testMigration(3, maxSyncVersion);
|
||||||
|
}, specTimeout);
|
||||||
|
|
||||||
|
it('should apply migration 3 E2EE', async () => {
|
||||||
|
await testMigrationE2EE(3, maxSyncVersion);
|
||||||
|
}, specTimeout);
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,65 +1,52 @@
|
|||||||
import LockHandler, { LockType } from './LockHandler';
|
import LockHandler, { LockType } from './LockHandler';
|
||||||
import { Dirnames } from './utils/types';
|
import { Dirnames } from './utils/types';
|
||||||
import BaseService from '../BaseService';
|
import BaseService from '../BaseService';
|
||||||
|
import migration1 from './migrations/1';
|
||||||
|
import migration2 from './migrations/2';
|
||||||
|
import migration3 from './migrations/3';
|
||||||
|
|
||||||
|
export type MigrationFunction = (api: FileApi, db: JoplinDatabase)=> Promise<void>;
|
||||||
|
|
||||||
// To add a new migration:
|
// To add a new migration:
|
||||||
// - Add the migration logic in ./migrations/VERSION_NUM.js
|
// - Add the migration logic in ./migrations/VERSION_NUM.js
|
||||||
// - Add the file to the array below.
|
// - Add the file to the array below.
|
||||||
// - Set Setting.syncVersion to VERSION_NUM in models/Setting.js
|
// - Set Setting.syncVersion to VERSION_NUM in models/Setting.js
|
||||||
// - Add tests in synchronizer_migrationHandler
|
// - Add tests in synchronizer_migrationHandler
|
||||||
const migrations = [
|
const migrations: MigrationFunction[] = [
|
||||||
null,
|
null,
|
||||||
require('./migrations/1.js').default,
|
migration1,
|
||||||
require('./migrations/2.js').default,
|
migration2,
|
||||||
|
migration3,
|
||||||
];
|
];
|
||||||
|
|
||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
import JoplinError from '../../JoplinError';
|
import JoplinError from '../../JoplinError';
|
||||||
|
import { FileApi } from '../../file-api';
|
||||||
interface SyncTargetInfo {
|
import { remoteSyncTargetInfo, setRemoteSyncTargetInfo, setLocalSyncTargetInfo, SyncTargetInfo, mergeSyncTargetInfos, localSyncTargetInfo } from './syncTargetInfoUtils';
|
||||||
version: number;
|
import JoplinDatabase from '../../JoplinDatabase';
|
||||||
}
|
|
||||||
|
|
||||||
export default class MigrationHandler extends BaseService {
|
export default class MigrationHandler extends BaseService {
|
||||||
|
|
||||||
private api_: any = null;
|
private api_: FileApi = null;
|
||||||
private lockHandler_: LockHandler = null;
|
private lockHandler_: LockHandler = null;
|
||||||
private clientType_: string;
|
private clientType_: string;
|
||||||
private clientId_: string;
|
private clientId_: string;
|
||||||
|
private db_: JoplinDatabase;
|
||||||
|
|
||||||
constructor(api: any, lockHandler: LockHandler, clientType: string, clientId: string) {
|
constructor(api: FileApi, db: JoplinDatabase, lockHandler: LockHandler, clientType: string, clientId: string) {
|
||||||
super();
|
super();
|
||||||
this.api_ = api;
|
this.api_ = api;
|
||||||
|
this.db_ = db;
|
||||||
this.lockHandler_ = lockHandler;
|
this.lockHandler_ = lockHandler;
|
||||||
this.clientType_ = clientType;
|
this.clientType_ = clientType;
|
||||||
this.clientId_ = clientId;
|
this.clientId_ = clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchSyncTargetInfo(): Promise<SyncTargetInfo> {
|
public async checkCanSync(syncTargetInfo: SyncTargetInfo = null): Promise<void> {
|
||||||
const syncTargetInfoText = await this.api_.get('info.json');
|
syncTargetInfo = syncTargetInfo || await remoteSyncTargetInfo(this.api_);
|
||||||
|
|
||||||
// Returns version 0 if the sync target is empty
|
|
||||||
let output: SyncTargetInfo = { version: 0 };
|
|
||||||
|
|
||||||
if (syncTargetInfoText) {
|
|
||||||
output = JSON.parse(syncTargetInfoText);
|
|
||||||
if (!output.version) throw new Error('Missing "version" field in info.json');
|
|
||||||
} else {
|
|
||||||
const oldVersion = await this.api_.get('.sync/version.txt');
|
|
||||||
if (oldVersion) output = { version: 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private serializeSyncTargetInfo(info: SyncTargetInfo) {
|
|
||||||
return JSON.stringify(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkCanSync(): Promise<SyncTargetInfo> {
|
|
||||||
const supportedSyncTargetVersion = Setting.value('syncVersion');
|
const supportedSyncTargetVersion = Setting.value('syncVersion');
|
||||||
const syncTargetInfo = await this.fetchSyncTargetInfo();
|
|
||||||
|
|
||||||
if (syncTargetInfo.version) {
|
if (syncTargetInfo.version) {
|
||||||
if (syncTargetInfo.version > supportedSyncTargetVersion) {
|
if (syncTargetInfo.version > supportedSyncTargetVersion) {
|
||||||
@@ -68,16 +55,15 @@ export default class MigrationHandler extends BaseService {
|
|||||||
throw new JoplinError(sprintf('Sync version of the target (%d) is lower than the version supported by the client (%d). Please upgrade the sync target.', syncTargetInfo.version, supportedSyncTargetVersion), 'outdatedSyncTarget');
|
throw new JoplinError(sprintf('Sync version of the target (%d) is lower than the version supported by the client (%d). Please upgrade the sync target.', syncTargetInfo.version, supportedSyncTargetVersion), 'outdatedSyncTarget');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncTargetInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgrade(targetVersion: number = 0) {
|
public async upgrade(targetVersion: number = 0) {
|
||||||
const supportedSyncTargetVersion = Setting.value('syncVersion');
|
const supportedSyncTargetVersion = Setting.value('syncVersion');
|
||||||
const syncTargetInfo = await this.fetchSyncTargetInfo();
|
|
||||||
|
|
||||||
if (syncTargetInfo.version > supportedSyncTargetVersion) {
|
const info = mergeSyncTargetInfos(localSyncTargetInfo(), await remoteSyncTargetInfo(this.api_));
|
||||||
throw new JoplinError(sprintf('Sync version of the target (%d) is greater than the version supported by the client (%d). Please upgrade your client.', syncTargetInfo.version, supportedSyncTargetVersion), 'outdatedClient');
|
|
||||||
|
if (info.version > supportedSyncTargetVersion) {
|
||||||
|
throw new JoplinError(sprintf('Sync version of the target (%d) is greater than the version supported by the client (%d). Please upgrade your client.', info.version, supportedSyncTargetVersion), 'outdatedClient');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (supportedSyncTargetVersion !== migrations.length - 1) {
|
// if (supportedSyncTargetVersion !== migrations.length - 1) {
|
||||||
@@ -91,8 +77,8 @@ export default class MigrationHandler extends BaseService {
|
|||||||
// Also if the sync target version is 0, it means it's a new one so we need the
|
// Also if the sync target version is 0, it means it's a new one so we need the
|
||||||
// lock folder first before doing anything else.
|
// lock folder first before doing anything else.
|
||||||
// Temp folder is needed too to get remoteDate() call to work.
|
// Temp folder is needed too to get remoteDate() call to work.
|
||||||
if (syncTargetInfo.version === 0 || syncTargetInfo.version === 1) {
|
if (info.version === 0 || info.version === 1) {
|
||||||
this.logger().info('MigrationHandler: Sync target version is 0 or 1 - creating "locks" and "temp" directory:', syncTargetInfo);
|
this.logger().info('MigrationHandler: Sync target version is 0 or 1 - creating "locks" and "temp" directory:', info);
|
||||||
await this.api_.mkdir(Dirnames.Locks);
|
await this.api_.mkdir(Dirnames.Locks);
|
||||||
await this.api_.mkdir(Dirnames.Temp);
|
await this.api_.mkdir(Dirnames.Temp);
|
||||||
}
|
}
|
||||||
@@ -107,7 +93,7 @@ export default class MigrationHandler extends BaseService {
|
|||||||
this.logger().info('MigrationHandler: Acquired exclusive lock:', exclusiveLock);
|
this.logger().info('MigrationHandler: Acquired exclusive lock:', exclusiveLock);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let newVersion = syncTargetInfo.version + 1; newVersion < migrations.length; newVersion++) {
|
for (let newVersion = info.version + 1; newVersion < migrations.length; newVersion++) {
|
||||||
if (targetVersion && newVersion > targetVersion) break;
|
if (targetVersion && newVersion > targetVersion) break;
|
||||||
|
|
||||||
const fromVersion = newVersion - 1;
|
const fromVersion = newVersion - 1;
|
||||||
@@ -119,13 +105,17 @@ export default class MigrationHandler extends BaseService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (autoLockError) throw autoLockError;
|
if (autoLockError) throw autoLockError;
|
||||||
await migration(this.api_);
|
await migration(this.api_, this.db_);
|
||||||
if (autoLockError) throw autoLockError;
|
if (autoLockError) throw autoLockError;
|
||||||
|
|
||||||
await this.api_.put('info.json', this.serializeSyncTargetInfo({
|
const newInfo: SyncTargetInfo = {
|
||||||
...syncTargetInfo,
|
...info,
|
||||||
version: newVersion,
|
version: newVersion,
|
||||||
}));
|
updatedTime: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await setRemoteSyncTargetInfo(this.api_, newInfo);
|
||||||
|
setLocalSyncTargetInfo(newInfo);
|
||||||
|
|
||||||
this.logger().info(`MigrationHandler: Done migrating from version ${fromVersion} to version ${newVersion}`);
|
this.logger().info(`MigrationHandler: Done migrating from version ${fromVersion} to version ${newVersion}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import time from '../../time';
|
import time from '../../time';
|
||||||
import Setting from '../../models/Setting';
|
|
||||||
import { allNotesFolders, localNotesFoldersSameAsRemote } from '../../testing/test-utils-synchronizer';
|
import { allNotesFolders, localNotesFoldersSameAsRemote } from '../../testing/test-utils-synchronizer';
|
||||||
|
import { synchronizerStart, setupDatabaseAndSynchronizer, sleep, switchClient, syncTargetId, loadEncryptionMasterKey, decryptionWorker } from '../../testing/test-utils';
|
||||||
const { synchronizerStart, setupDatabaseAndSynchronizer, sleep, switchClient, syncTargetId, loadEncryptionMasterKey, decryptionWorker } = require('../../testing/test-utils.js');
|
|
||||||
import Folder from '../../models/Folder';
|
import Folder from '../../models/Folder';
|
||||||
import Note from '../../models/Note';
|
import Note from '../../models/Note';
|
||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
|
import { setEncryptionEnabled } from './syncTargetInfoUtils';
|
||||||
|
|
||||||
describe('Synchronizer.conflicts', function() {
|
describe('Synchronizer.conflicts', function() {
|
||||||
|
|
||||||
@@ -227,7 +226,7 @@ describe('Synchronizer.conflicts', function() {
|
|||||||
|
|
||||||
async function ignorableNoteConflictTest(withEncryption: boolean) {
|
async function ignorableNoteConflictTest(withEncryption: boolean) {
|
||||||
if (withEncryption) {
|
if (withEncryption) {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
await loadEncryptionMasterKey();
|
await loadEncryptionMasterKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import MasterKey from '../../models/MasterKey';
|
|||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
import { ResourceEntity } from '../database/types';
|
import { ResourceEntity } from '../database/types';
|
||||||
import Synchronizer from '../../Synchronizer';
|
import Synchronizer from '../../Synchronizer';
|
||||||
|
import { setupAndDisableEncryption, setupAndEnableEncryption, loadMasterKeysFromSettings } from '../e2ee/utils';
|
||||||
|
import { encryptionEnabled, localSyncTargetInfo, setEncryptionEnabled } from './syncTargetInfoUtils';
|
||||||
|
|
||||||
let insideBeforeEach = false;
|
let insideBeforeEach = false;
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('notes and folders should get encrypted when encryption is enabled', (async () => {
|
it('notes and folders should get encrypted when encryption is enabled', (async () => {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
const folder1 = await Folder.save({ title: 'folder1' });
|
const folder1 = await Folder.save({ title: 'folder1' });
|
||||||
let note1 = await Note.save({ title: 'un', body: 'to be encrypted', parent_id: folder1.id });
|
let note1 = await Note.save({ title: 'un', body: 'to be encrypted', parent_id: folder1.id });
|
||||||
@@ -55,7 +57,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
expect(masterKey_2.content).toBe(masterKey.content);
|
expect(masterKey_2.content).toBe(masterKey.content);
|
||||||
expect(masterKey_2.checksum).toBe(masterKey.checksum);
|
expect(masterKey_2.checksum).toBe(masterKey.checksum);
|
||||||
// Now load the master key we got from client 1 and try to decrypt
|
// Now load the master key we got from client 1 and try to decrypt
|
||||||
await encryptionService().loadMasterKey_(masterKey_2, '123456', true);
|
await encryptionService().loadMasterKey(masterKey_2, '123456', true);
|
||||||
// Get the decrypted items back
|
// Get the decrypted items back
|
||||||
await Folder.decrypt(folder1_2);
|
await Folder.decrypt(folder1_2);
|
||||||
await Note.decrypt(note1_2);
|
await Note.decrypt(note1_2);
|
||||||
@@ -74,7 +76,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
|
|
||||||
it('should enable encryption automatically when downloading new master key (and none was previously available)',(async () => {
|
it('should enable encryption automatically when downloading new master key (and none was previously available)',(async () => {
|
||||||
// Enable encryption on client 1 and sync an item
|
// Enable encryption on client 1 and sync an item
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
await loadEncryptionMasterKey();
|
await loadEncryptionMasterKey();
|
||||||
let folder1 = await Folder.save({ title: 'folder1' });
|
let folder1 = await Folder.save({ title: 'folder1' });
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
@@ -82,9 +84,9 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
|
|
||||||
// Synchronising should enable encryption since we're going to get a master key
|
// Synchronising should enable encryption since we're going to get a master key
|
||||||
expect(Setting.value('encryption.enabled')).toBe(false);
|
expect(encryptionEnabled()).toBe(false);
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
expect(Setting.value('encryption.enabled')).toBe(true);
|
expect(encryptionEnabled()).toBe(true);
|
||||||
|
|
||||||
// Check that we got the master key from client 1
|
// Check that we got the master key from client 1
|
||||||
const masterKey = (await MasterKey.all())[0];
|
const masterKey = (await MasterKey.all())[0];
|
||||||
@@ -109,7 +111,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
|
|
||||||
// Now client 2 set the master key password
|
// Now client 2 set the master key password
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
|
||||||
// Now that master key should be loaded
|
// Now that master key should be loaded
|
||||||
expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id);
|
expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id);
|
||||||
@@ -134,6 +136,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
// First create a folder, without encryption enabled, and sync it
|
// First create a folder, without encryption enabled, and sync it
|
||||||
await Folder.save({ title: 'folder1' });
|
await Folder.save({ title: 'folder1' });
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
let files = await fileApi().list('', { includeDirs: false, syncItemsOnly: true });
|
let files = await fileApi().list('', { includeDirs: false, syncItemsOnly: true });
|
||||||
let content = await fileApi().get(files.items[0].path);
|
let content = await fileApi().get(files.items[0].path);
|
||||||
expect(content.indexOf('folder1') >= 0).toBe(true);
|
expect(content.indexOf('folder1') >= 0).toBe(true);
|
||||||
@@ -141,24 +144,27 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
// Then enable encryption and sync again
|
// Then enable encryption and sync again
|
||||||
let masterKey = await encryptionService().generateMasterKey('123456');
|
let masterKey = await encryptionService().generateMasterKey('123456');
|
||||||
masterKey = await MasterKey.save(masterKey);
|
masterKey = await MasterKey.save(masterKey);
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
|
|
||||||
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
// Even though the folder has not been changed it should have been synced again so that
|
// Even though the folder has not been changed it should have been synced again so that
|
||||||
// an encrypted version of it replaces the decrypted version.
|
// an encrypted version of it replaces the decrypted version.
|
||||||
files = await fileApi().list('', { includeDirs: false, syncItemsOnly: true });
|
files = await fileApi().list('', { includeDirs: false, syncItemsOnly: true });
|
||||||
expect(files.items.length).toBe(2);
|
expect(files.items.length).toBe(1);
|
||||||
// By checking that the folder title is not present, we can confirm that the item has indeed been encrypted
|
// By checking that the folder title is not present, we can confirm that the item has indeed been encrypted
|
||||||
// One of the two items is the master key
|
|
||||||
content = await fileApi().get(files.items[0].path);
|
content = await fileApi().get(files.items[0].path);
|
||||||
expect(content.indexOf('folder1') < 0).toBe(true);
|
expect(content.indexOf('folder1') < 0).toBe(true);
|
||||||
content = await fileApi().get(files.items[1].path);
|
|
||||||
expect(content.indexOf('folder1') < 0).toBe(true);
|
// Also verify that we got the master key back
|
||||||
|
const syncTargetInfo = localSyncTargetInfo();
|
||||||
|
expect(syncTargetInfo.masterKeys[masterKey.id]).toBeTruthy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should upload decrypted items to sync target after encryption disabled', (async () => {
|
it('should upload decrypted items to sync target after encryption disabled', (async () => {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
await loadEncryptionMasterKey();
|
await loadEncryptionMasterKey();
|
||||||
|
|
||||||
await Folder.save({ title: 'folder1' });
|
await Folder.save({ title: 'folder1' });
|
||||||
@@ -167,7 +173,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
let allEncrypted = await allSyncTargetItemsEncrypted();
|
let allEncrypted = await allSyncTargetItemsEncrypted();
|
||||||
expect(allEncrypted).toBe(true);
|
expect(allEncrypted).toBe(true);
|
||||||
|
|
||||||
await encryptionService().disableEncryption();
|
await setupAndDisableEncryption();
|
||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
allEncrypted = await allSyncTargetItemsEncrypted();
|
allEncrypted = await allSyncTargetItemsEncrypted();
|
||||||
@@ -179,7 +185,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
// which means it's going to fail in unexpected way. So the loop below wait for beforeEach to be done.
|
// which means it's going to fail in unexpected way. So the loop below wait for beforeEach to be done.
|
||||||
while (insideBeforeEach) await time.msleep(100);
|
while (insideBeforeEach) await time.msleep(100);
|
||||||
|
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
|
|
||||||
await Folder.save({ title: 'folder1' });
|
await Folder.save({ title: 'folder1' });
|
||||||
@@ -188,21 +194,21 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
expect(Setting.value('encryption.enabled')).toBe(true);
|
expect(encryptionEnabled()).toBe(true);
|
||||||
|
|
||||||
// If we try to disable encryption now, it should throw an error because some items are
|
// If we try to disable encryption now, it should throw an error because some items are
|
||||||
// currently encrypted. They must be decrypted first so that they can be sent as
|
// currently encrypted. They must be decrypted first so that they can be sent as
|
||||||
// plain text to the sync target.
|
// plain text to the sync target.
|
||||||
// let hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
|
// let hasThrown = await checkThrowAsync(async () => await setupAndDisableEncryption());
|
||||||
// expect(hasThrown).toBe(true);
|
// expect(hasThrown).toBe(true);
|
||||||
|
|
||||||
// Now supply the password, and decrypt the items
|
// Now supply the password, and decrypt the items
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await decryptionWorker().start();
|
await decryptionWorker().start();
|
||||||
|
|
||||||
// Try to disable encryption again
|
// Try to disable encryption again
|
||||||
const hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
|
const hasThrown = await checkThrowAsync(async () => await setupAndDisableEncryption());
|
||||||
expect(hasThrown).toBe(false);
|
expect(hasThrown).toBe(false);
|
||||||
|
|
||||||
// If we sync now the target should receive the decrypted items
|
// If we sync now the target should receive the decrypted items
|
||||||
@@ -212,7 +218,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should set the resource file size after decryption', (async () => {
|
it('should set the resource file size after decryption', (async () => {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
|
|
||||||
const folder1 = await Folder.save({ title: 'folder1' });
|
const folder1 = await Folder.save({ title: 'folder1' });
|
||||||
@@ -227,7 +233,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
|
||||||
const fetcher = newResourceFetcher(synchronizer());
|
const fetcher = newResourceFetcher(synchronizer());
|
||||||
fetcher.queueDownload_(resource1.id);
|
fetcher.queueDownload_(resource1.id);
|
||||||
@@ -249,8 +255,8 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
expect(await allSyncTargetItemsEncrypted()).toBe(false);
|
expect(await allSyncTargetItemsEncrypted()).toBe(false);
|
||||||
|
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
@@ -264,8 +270,8 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||||
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`);
|
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
const resource1 = (await Resource.all())[0];
|
const resource1 = (await Resource.all())[0];
|
||||||
@@ -276,8 +282,8 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
const note1 = await Note.save({ title: 'note' });
|
const note1 = await Note.save({ title: 'note' });
|
||||||
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`);
|
await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
expect(await allSyncTargetItemsEncrypted()).toBe(true);
|
expect(await allSyncTargetItemsEncrypted()).toBe(true);
|
||||||
|
|
||||||
@@ -285,7 +291,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await decryptionWorker().start();
|
await decryptionWorker().start();
|
||||||
|
|
||||||
let resource = (await Resource.all())[0];
|
let resource = (await Resource.all())[0];
|
||||||
@@ -311,8 +317,8 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
|
|
||||||
const note = await Note.save({ title: 'ma note' });
|
const note = await Note.save({ title: 'ma note' });
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
@@ -329,7 +335,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
await Note.save({ id: note.id, encryption_cipher_text: 'doesntlookright' });
|
await Note.save({ id: note.id, encryption_cipher_text: 'doesntlookright' });
|
||||||
|
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
|
||||||
hasThrown = await checkThrowAsync(async () => await decryptionWorker().start({ errorHandler: 'throw' }));
|
hasThrown = await checkThrowAsync(async () => await decryptionWorker().start({ errorHandler: 'throw' }));
|
||||||
expect(hasThrown).toBe(true);
|
expect(hasThrown).toBe(true);
|
||||||
@@ -367,7 +373,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not encrypt notes that are shared by link', (async () => {
|
it('should not encrypt notes that are shared by link', (async () => {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
await loadEncryptionMasterKey();
|
await loadEncryptionMasterKey();
|
||||||
|
|
||||||
await createFolderTree('', [
|
await createFolderTree('', [
|
||||||
@@ -459,7 +465,7 @@ describe('Synchronizer.e2ee', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
await loadEncryptionMasterKey();
|
await loadEncryptionMasterKey();
|
||||||
|
|
||||||
const folder1 = await createFolderTree('', [
|
const folder1 = await createFolderTree('', [
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import Resource from '../../models/Resource';
|
|||||||
import ResourceFetcher from '../../services/ResourceFetcher';
|
import ResourceFetcher from '../../services/ResourceFetcher';
|
||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
import { ModelType } from '../../BaseModel';
|
import { ModelType } from '../../BaseModel';
|
||||||
|
import { loadMasterKeysFromSettings } from '../e2ee/utils';
|
||||||
|
import { setEncryptionEnabled } from './syncTargetInfoUtils';
|
||||||
|
|
||||||
let insideBeforeEach = false;
|
let insideBeforeEach = false;
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ describe('Synchronizer.resources', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should encrypt resources', (async () => {
|
it('should encrypt resources', (async () => {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
|
|
||||||
const folder1 = await Folder.save({ title: 'folder1' });
|
const folder1 = await Folder.save({ title: 'folder1' });
|
||||||
@@ -158,7 +160,7 @@ describe('Synchronizer.resources', function() {
|
|||||||
|
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
|
|
||||||
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
|
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
|
||||||
fetcher.queueDownload_(resource1.id);
|
fetcher.queueDownload_(resource1.id);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
import BaseModel from '../../BaseModel';
|
import BaseModel from '../../BaseModel';
|
||||||
|
import { synchronizerStart, revisionService, setupDatabaseAndSynchronizer, synchronizer, switchClient, encryptionService, loadEncryptionMasterKey, decryptionWorker } from '../../testing/test-utils';
|
||||||
const { synchronizerStart, revisionService, setupDatabaseAndSynchronizer, synchronizer, switchClient, encryptionService, loadEncryptionMasterKey, decryptionWorker } = require('../../testing/test-utils.js');
|
|
||||||
import Note from '../../models/Note';
|
import Note from '../../models/Note';
|
||||||
import Revision from '../../models/Revision';
|
import Revision from '../../models/Revision';
|
||||||
|
import { setupAndEnableEncryption, loadMasterKeysFromSettings } from '../e2ee/utils';
|
||||||
|
|
||||||
describe('Synchronizer.revisions', function() {
|
describe('Synchronizer.revisions', function() {
|
||||||
|
|
||||||
@@ -165,8 +165,8 @@ describe('Synchronizer.revisions', function() {
|
|||||||
|
|
||||||
await Note.save({ title: 'ma note', updated_time: dateInPast, created_time: dateInPast }, { autoTimestamp: false });
|
await Note.save({ title: 'ma note', updated_time: dateInPast, created_time: dateInPast }, { autoTimestamp: false });
|
||||||
const masterKey = await loadEncryptionMasterKey();
|
const masterKey = await loadEncryptionMasterKey();
|
||||||
await encryptionService().enableEncryption(masterKey, '123456');
|
await setupAndEnableEncryption(masterKey, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
@@ -174,7 +174,7 @@ describe('Synchronizer.revisions', function() {
|
|||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
|
|
||||||
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456');
|
||||||
await encryptionService().loadMasterKeysFromSettings();
|
await loadMasterKeysFromSettings(encryptionService());
|
||||||
await decryptionWorker().start();
|
await decryptionWorker().start();
|
||||||
|
|
||||||
await revisionService().collectRevisions();
|
await revisionService().collectRevisions();
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import Setting from '../../models/Setting';
|
import { synchronizerStart, setupDatabaseAndSynchronizer, switchClient, encryptionService, loadEncryptionMasterKey } from '../../testing/test-utils';
|
||||||
|
|
||||||
const { synchronizerStart, setupDatabaseAndSynchronizer, switchClient, encryptionService, loadEncryptionMasterKey } = require('../../testing/test-utils.js');
|
|
||||||
import Folder from '../../models/Folder';
|
import Folder from '../../models/Folder';
|
||||||
import Note from '../../models/Note';
|
import Note from '../../models/Note';
|
||||||
import Tag from '../../models/Tag';
|
import Tag from '../../models/Tag';
|
||||||
import MasterKey from '../../models/MasterKey';
|
import MasterKey from '../../models/MasterKey';
|
||||||
|
import { setEncryptionEnabled } from './syncTargetInfoUtils';
|
||||||
|
|
||||||
describe('Synchronizer.tags', function() {
|
describe('Synchronizer.tags', function() {
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ describe('Synchronizer.tags', function() {
|
|||||||
async function shoudSyncTagTest(withEncryption: boolean) {
|
async function shoudSyncTagTest(withEncryption: boolean) {
|
||||||
let masterKey = null;
|
let masterKey = null;
|
||||||
if (withEncryption) {
|
if (withEncryption) {
|
||||||
Setting.setValue('encryption.enabled', true);
|
setEncryptionEnabled(true);
|
||||||
masterKey = await loadEncryptionMasterKey();
|
masterKey = await loadEncryptionMasterKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ describe('Synchronizer.tags', function() {
|
|||||||
await synchronizerStart();
|
await synchronizerStart();
|
||||||
if (withEncryption) {
|
if (withEncryption) {
|
||||||
const masterKey_2 = await MasterKey.load(masterKey.id);
|
const masterKey_2 = await MasterKey.load(masterKey.id);
|
||||||
await encryptionService().loadMasterKey_(masterKey_2, '123456', true);
|
await encryptionService().loadMasterKey(masterKey_2, '123456', true);
|
||||||
const t = await Tag.load(tag.id);
|
const t = await Tag.load(tag.id);
|
||||||
await Tag.decrypt(t);
|
await Tag.decrypt(t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import MigrationHandler from '../MigrationHandler';
|
|||||||
const { useEffect, useState } = shim.react();
|
const { useEffect, useState } = shim.react();
|
||||||
import Setting from '../../../models/Setting';
|
import Setting from '../../../models/Setting';
|
||||||
import { reg } from '../../../registry';
|
import { reg } from '../../../registry';
|
||||||
|
import Synchronizer from '../../../Synchronizer';
|
||||||
|
|
||||||
export interface SyncTargetUpgradeResult {
|
export interface SyncTargetUpgradeResult {
|
||||||
done: boolean;
|
done: boolean;
|
||||||
@@ -21,11 +22,12 @@ export default function useSyncTargetUpgrade(): SyncTargetUpgradeResult {
|
|||||||
let error = null;
|
let error = null;
|
||||||
try {
|
try {
|
||||||
reg.logger().info('useSyncTargetUpgrade: Acquire synchronizer...');
|
reg.logger().info('useSyncTargetUpgrade: Acquire synchronizer...');
|
||||||
const synchronizer = await reg.syncTarget().synchronizer();
|
const synchronizer: Synchronizer = await reg.syncTarget().synchronizer();
|
||||||
|
|
||||||
reg.logger().info('useSyncTargetUpgrade: Create migration handler...');
|
reg.logger().info('useSyncTargetUpgrade: Create migration handler...');
|
||||||
const migrationHandler = new MigrationHandler(
|
const migrationHandler = new MigrationHandler(
|
||||||
synchronizer.api(),
|
synchronizer.api(),
|
||||||
|
synchronizer.db(),
|
||||||
synchronizer.lockHandler(),
|
synchronizer.lockHandler(),
|
||||||
Setting.value('appType'),
|
Setting.value('appType'),
|
||||||
Setting.value('clientId')
|
Setting.value('clientId')
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user