@vrtmrz/livesync-commonlib 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +5 -0
  2. package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
  3. package/dist/API/DirectFileManipulatorV2.js +17 -5
  4. package/dist/API/processSetting.d.ts +19 -0
  5. package/dist/API/processSetting.js +91 -6
  6. package/dist/common/idDerivation.d.ts +13 -0
  7. package/dist/common/idDerivation.js +75 -0
  8. package/dist/common/models/db.definition.d.ts +1 -0
  9. package/dist/common/models/setting.const.defaults.js +3 -0
  10. package/dist/common/models/setting.const.qr.js +4 -1
  11. package/dist/common/models/setting.type.d.ts +6 -0
  12. package/dist/common/models/tweak.compatibility.js +6 -2
  13. package/dist/common/models/tweak.definition.d.ts +8 -0
  14. package/dist/common/models/tweak.definition.js +4 -3
  15. package/dist/common/settingConstants.d.ts +3 -0
  16. package/dist/common/utils.js +3 -1
  17. package/dist/events/coreEvents.d.ts +3 -0
  18. package/dist/events/coreEvents.js +2 -0
  19. package/dist/managers/EntryManager/EntryManagerImpls.js +10 -1
  20. package/dist/managers/HashManager/HashManager.d.ts +1 -0
  21. package/dist/managers/HashManager/HashManager.js +13 -1
  22. package/dist/pouchdb/LiveSyncDBFunctions.d.ts +1 -1
  23. package/dist/pouchdb/LiveSyncDBFunctions.js +33 -38
  24. package/dist/pouchdb/negotiation.js +7 -3
  25. package/dist/pouchdb/remoteFeatureCompatibility.d.ts +10 -0
  26. package/dist/pouchdb/remoteFeatureCompatibility.js +12 -1
  27. package/dist/pouchdb/remoteIdCompatibility.d.ts +5 -0
  28. package/dist/pouchdb/remoteIdCompatibility.js +28 -0
  29. package/dist/replication/CentralCompatibility.d.ts +1 -0
  30. package/dist/replication/CentralCompatibility.js +2 -1
  31. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +6 -2
  32. package/dist/replication/couchdb/LiveSyncReplicator.js +24 -5
  33. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +1 -0
  34. package/dist/replication/journal/LiveSyncJournalReplicator.js +50 -5
  35. package/dist/replication/trystero/TrysteroReplicator.d.ts +369 -5
  36. package/dist/replication/trystero/TrysteroReplicator.js +31 -4
  37. package/dist/services/base/AppLifecycleService.d.ts +2 -0
  38. package/dist/services/base/AppLifecycleService.js +5 -0
  39. package/dist/services/base/IService.d.ts +1 -0
  40. package/dist/services/base/PathService.d.ts +3 -1
  41. package/dist/services/base/PathService.js +9 -4
  42. package/dist/services/base/SettingService.d.ts +2 -0
  43. package/dist/services/base/SettingService.js +57 -3
  44. package/dist/settings.d.ts +2 -1
  45. package/dist/settings.js +14 -0
  46. package/dist/setupUri.d.ts +6 -0
  47. package/dist/setupUri.js +14 -0
  48. package/dist/string_and_binary/path.d.ts +1 -1
  49. package/dist/string_and_binary/path.js +3 -3
  50. package/dist/worker/bgWorker.js +1 -1
  51. package/docs/proven-in-use.md +8 -0
  52. package/docs/releasing.md +1 -1
  53. package/docs/setup-uri.md +58 -0
  54. package/package.json +9 -1
@@ -13,6 +13,7 @@ import { ServiceBase } from "./ServiceBase.js";
13
13
  import { createInstanceLogFunction } from "../lib/logUtils.js";
14
14
  import { isCloudantURI } from "../../pouchdb/utils_couchdb.js";
15
15
  import { decryptString, encryptString } from "../../encryption/stringEncryption.js";
16
+ import { encryptWithEphemeralSalt } from "octagonal-wheels/encryption/hkdf";
16
17
  import {
17
18
  activateP2PRemoteConfiguration,
18
19
  activateRemoteConfiguration,
@@ -20,6 +21,7 @@ import {
20
21
  migrateP2PActiveRemoteConfigurationIdInPlace
21
22
  } from "../../serviceFeatures/remoteConfig.js";
22
23
  import { ConnectionStringParser } from "../../common/ConnectionString.js";
24
+ import { configuredIdKey } from "../../common/idDerivation.js";
23
25
  class SettingService extends ServiceBase {
24
26
  deviceAndVaultName = "";
25
27
  APIService;
@@ -151,6 +153,32 @@ class SettingService extends ServiceBase {
151
153
  Object.assign(settings, patch);
152
154
  Object.assign(this.settings, patch);
153
155
  }
156
+ const idKey = configuredIdKey(settings);
157
+ if (settings.idDerivationVersion === 1) {
158
+ if (idKey === false) {
159
+ throw new Error("The configured ID derivation key is unavailable.");
160
+ }
161
+ let passphrase = this.usedPassphrase;
162
+ if (passphrase === "") {
163
+ passphrase = await this.getPassphrase(settings);
164
+ }
165
+ if (passphrase === false || passphrase === "") {
166
+ const message = "Failed to retrieve a passphrase for the ID derivation key. Settings were not saved.";
167
+ this._log(message, LOG_LEVEL_URGENT);
168
+ throw new Error(message);
169
+ }
170
+ const encryptedIdKey = await encryptString(idKey, passphrase + SALT_OF_PASSPHRASE);
171
+ if (encryptedIdKey === "") {
172
+ const message = "Failed to encrypt the ID derivation key. Settings were not saved.";
173
+ this._log(message, LOG_LEVEL_URGENT);
174
+ throw new Error(message);
175
+ }
176
+ settings.encryptedIdDerivationKey = encryptedIdKey;
177
+ settings.idDerivationKey = "";
178
+ this.usedPassphrase = passphrase;
179
+ } else {
180
+ settings.encryptedIdDerivationKey = "";
181
+ }
154
182
  settings.deviceAndVaultName = "";
155
183
  if (settings.P2P_DevicePeerName && settings.P2P_DevicePeerName.trim() !== "") {
156
184
  this._log("Saving device peer name to small config");
@@ -207,7 +235,7 @@ class SettingService extends ServiceBase {
207
235
  settings.endpoint = "";
208
236
  }
209
237
  if (settings.encrypt && settings.passphrase != "") {
210
- settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings);
238
+ settings.encryptedPassphrase = await this.encryptPlainConfigurationItem(settings.passphrase, settings);
211
239
  settings.passphrase = "";
212
240
  }
213
241
  await this.encryptRemoteConfigurationUris(settings);
@@ -405,8 +433,14 @@ class SettingService extends ServiceBase {
405
433
  return false;
406
434
  }
407
435
  async encryptConfigurationItem(src, settings) {
436
+ return this.encryptConfigurationItemWith(src, settings, encryptString);
437
+ }
438
+ async encryptPlainConfigurationItem(src, settings) {
439
+ return this.encryptConfigurationItemWith(src, settings, encryptWithEphemeralSalt);
440
+ }
441
+ async encryptConfigurationItemWith(src, settings, encrypt) {
408
442
  if (this.usedPassphrase != "") {
409
- return await encryptString(src, this.usedPassphrase + SALT_OF_PASSPHRASE);
443
+ return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE);
410
444
  }
411
445
  const passphrase = await this.getPassphrase(settings);
412
446
  if (passphrase === false) {
@@ -416,7 +450,7 @@ class SettingService extends ServiceBase {
416
450
  );
417
451
  return "";
418
452
  }
419
- const dec = await encryptString(src, passphrase + SALT_OF_PASSPHRASE);
453
+ const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE);
420
454
  if (dec) {
421
455
  this.usedPassphrase = passphrase;
422
456
  return dec;
@@ -428,7 +462,27 @@ class SettingService extends ServiceBase {
428
462
  * @param settings The settings to decrypt.
429
463
  */
430
464
  async decryptSettings(settings) {
465
+ if (settings.idDerivationVersion !== 1) {
466
+ configuredIdKey(settings);
467
+ } else if (!settings.encryptedIdDerivationKey) {
468
+ throw new Error("The configured ID derivation key is missing or unavailable.");
469
+ }
431
470
  const passphrase = await this.getPassphrase(settings);
471
+ if (settings.idDerivationVersion === 1) {
472
+ if (passphrase === false || passphrase === "") {
473
+ throw new Error("The configured ID derivation key cannot be decrypted without a passphrase.");
474
+ }
475
+ const decryptedIdKey = await this.decryptConfigurationItem(settings.encryptedIdDerivationKey, passphrase);
476
+ if (decryptedIdKey === false) {
477
+ throw new Error("The configured ID derivation key could not be decrypted.");
478
+ }
479
+ try {
480
+ configuredIdKey({ ...settings, idDerivationKey: decryptedIdKey });
481
+ } catch {
482
+ throw new Error("The configured ID derivation key is invalid.");
483
+ }
484
+ settings.idDerivationKey = decryptedIdKey;
485
+ }
432
486
  if (passphrase === false) {
433
487
  this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT);
434
488
  const hasEncryptedRemoteConfigurations = Object.values(settings.remoteConfigurations || {}).some(
@@ -6,8 +6,9 @@
6
6
  export { DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, P2P_DEFAULT_SETTINGS, SETTINGS_SCHEMA_DEFAULTS, createNewVaultSettings, } from "./common/models/setting.const.defaults.js";
7
7
  export { PREFERRED_BASE, PREFERRED_JOURNAL_SYNC, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED, } from "./common/models/setting.const.preferred.js";
8
8
  export { CURRENT_SETTING_VERSION } from "./common/models/setting.const.js";
9
+ export { deriveIdKey, deriveOrImportIdKey, formatIdRecoveryCode, configuredIdKey, ID_DERIVATION_VERSION, ID_RECOVERY_CODE_PREFIX, } from "./common/idDerivation.js";
9
10
  export { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState, } from "./common/models/setting.lifecycle.js";
10
11
  export { assessTweakCompatibility } from "./common/models/tweak.compatibility.js";
11
12
  export type { TweakAssessment, TweakAssessmentEntry, TweakAssessmentRelation, TweakAssessmentValue, TweakTransition, TweakTransitionReason, } from "./common/models/tweak.compatibility.js";
12
13
  export type { TweakReconstruction, TweakValues } from "./common/models/tweak.definition.js";
13
- export type { ObsidianLiveSyncSettings, RemoteDBSettings, RemoteTypeSettings, } from "./common/models/setting.type.js";
14
+ export type { ObsidianLiveSyncSettings, RemoteDBSettings, RemoteTypeSettings } from "./common/models/setting.type.js";
package/dist/settings.js CHANGED
@@ -12,6 +12,14 @@ import {
12
12
  PREFERRED_SETTING_SELF_HOSTED
13
13
  } from "./common/models/setting.const.preferred.js";
14
14
  import { CURRENT_SETTING_VERSION } from "./common/models/setting.const.js";
15
+ import {
16
+ deriveIdKey,
17
+ deriveOrImportIdKey,
18
+ formatIdRecoveryCode,
19
+ configuredIdKey,
20
+ ID_DERIVATION_VERSION,
21
+ ID_RECOVERY_CODE_PREFIX
22
+ } from "./common/idDerivation.js";
15
23
  import {
16
24
  prepareSettingsForLoad,
17
25
  SettingsMigrationReviewCodes
@@ -20,6 +28,8 @@ import { assessTweakCompatibility } from "./common/models/tweak.compatibility.js
20
28
  export {
21
29
  CURRENT_SETTING_VERSION,
22
30
  DEFAULT_SETTINGS,
31
+ ID_DERIVATION_VERSION,
32
+ ID_RECOVERY_CODE_PREFIX,
23
33
  NEW_VAULT_SETTINGS,
24
34
  P2P_DEFAULT_SETTINGS,
25
35
  PREFERRED_BASE,
@@ -29,6 +39,10 @@ export {
29
39
  SETTINGS_SCHEMA_DEFAULTS,
30
40
  SettingsMigrationReviewCodes,
31
41
  assessTweakCompatibility,
42
+ configuredIdKey,
32
43
  createNewVaultSettings,
44
+ deriveIdKey,
45
+ deriveOrImportIdKey,
46
+ formatIdRecoveryCode,
33
47
  prepareSettingsForLoad
34
48
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Setup URI sharing for maintained hosts.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { encodeSettingsToSetupURI, encodeTimeBoundSetupURI, getTimeBoundSetupURIUsableUntil, isTimeBoundSetupURIUsableNow, decodeSettingsFromSetupURI, type TimeBoundSetupURIOptions, type TimeBoundSetupURIResult, type TimeBoundSetupURIMode, } from "./API/processSetting.js";
@@ -0,0 +1,14 @@
1
+ import {
2
+ encodeSettingsToSetupURI,
3
+ encodeTimeBoundSetupURI,
4
+ getTimeBoundSetupURIUsableUntil,
5
+ isTimeBoundSetupURIUsableNow,
6
+ decodeSettingsFromSetupURI
7
+ } from "./API/processSetting.js";
8
+ export {
9
+ decodeSettingsFromSetupURI,
10
+ encodeSettingsToSetupURI,
11
+ encodeTimeBoundSetupURI,
12
+ getTimeBoundSetupURIUsableUntil,
13
+ isTimeBoundSetupURIUsableNow
14
+ };
@@ -8,7 +8,7 @@ export declare function stripAllPrefixes(prefixedPath: FilePathWithPrefix): File
8
8
  export declare function addPrefix(path: FilePath | FilePathWithPrefix, prefix: string): FilePathWithPrefix;
9
9
  export declare function expandFilePathPrefix(path: FilePathWithPrefix | FilePath): [string, FilePathWithPrefix];
10
10
  export declare function expandDocumentIDPrefix(id: DocumentID): [string, FilePathWithPrefix];
11
- export declare function path2id_base(filenameSrc: FilePathWithPrefix | FilePath, obfuscatePassphrase: string | false, caseInsensitive: boolean): Promise<DocumentID>;
11
+ export declare function path2id_base(filenameSrc: FilePathWithPrefix | FilePath, obfuscatePassphrase: string | false, caseInsensitive: boolean, idDerivationKey?: string): Promise<DocumentID>;
12
12
  export declare function id2path_base(id: DocumentID, entry?: EntryHasPath): FilePathWithPrefix;
13
13
  export declare function getPath(entry: AnyEntry): FilePathWithPrefix;
14
14
  export declare function getPathWithoutPrefix(entry: AnyEntry): FilePath;
@@ -14,6 +14,7 @@ import { memorizeFuncWithLRUCache } from "../common/utils.js";
14
14
  import { uint8ArrayToHexString, writeString } from "./convert.js";
15
15
  import { unique } from "octagonal-wheels/collection.js";
16
16
  import { CHeader, ICHeader, ICXHeader, PSCHeader } from "../common/models/fileaccess.const.js";
17
+ import { computeKeyedId } from "../common/idDerivation.js";
17
18
  function isValidFilenameInWidows(filename) {
18
19
  const regex = /[\u0000-\u001f]|[\\":?<>|*#]/g;
19
20
  if (regex.test(filename)) return false;
@@ -68,7 +69,7 @@ const _hashString = memorizeFuncWithLRUCache(async (key) => {
68
69
  function hashString(key) {
69
70
  return _hashString(key);
70
71
  }
71
- async function path2id_base(filenameSrc, obfuscatePassphrase, caseInsensitive) {
72
+ async function path2id_base(filenameSrc, obfuscatePassphrase, caseInsensitive, idDerivationKey) {
72
73
  if (filenameSrc.startsWith(PREFIX_OBFUSCATED)) return `${filenameSrc}`;
73
74
  let filename = `${filenameSrc}`;
74
75
  const newPrefix = obfuscatePassphrase ? PREFIX_OBFUSCATED : "";
@@ -82,8 +83,7 @@ async function path2id_base(filenameSrc, obfuscatePassphrase, caseInsensitive) {
82
83
  }
83
84
  const [prefix, body] = expandFilePathPrefix(x);
84
85
  if (body.startsWith(PREFIX_OBFUSCATED)) return newPrefix + x;
85
- const hashedPassphrase = await hashString(obfuscatePassphrase);
86
- const out = await hashString(`${hashedPassphrase}:${filename}`);
86
+ const out = idDerivationKey ? await computeKeyedId(idDerivationKey, "document", filename) : await hashString(`${await hashString(obfuscatePassphrase)}:${filename}`);
87
87
  return prefix + newPrefix + out;
88
88
  }
89
89
  function id2path_base(id, entry) {