@abraca/dabra 1.8.2 → 2.0.0

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 (37) hide show
  1. package/dist/abracadabra-provider.cjs +12722 -9050
  2. package/dist/abracadabra-provider.cjs.map +1 -1
  3. package/dist/abracadabra-provider.esm.js +12683 -9061
  4. package/dist/abracadabra-provider.esm.js.map +1 -1
  5. package/dist/index.d.ts +1485 -118
  6. package/package.json +1 -1
  7. package/src/AbracadabraBaseProvider.ts +51 -2
  8. package/src/AbracadabraClient.ts +516 -66
  9. package/src/AbracadabraProvider.ts +22 -7
  10. package/src/AbracadabraWS.ts +1 -1
  11. package/src/ChatClient.ts +193 -113
  12. package/src/ContentManager.ts +228 -0
  13. package/src/CryptoIdentityKeystore.ts +3 -3
  14. package/src/DocConverters.ts +1862 -0
  15. package/src/DocKeyManager.ts +60 -12
  16. package/src/DocTypes.ts +628 -0
  17. package/src/DocUtils.ts +89 -0
  18. package/src/DocumentManager.ts +319 -0
  19. package/src/E2EAbracadabraProvider.ts +189 -0
  20. package/src/EncryptedChatClient.ts +173 -0
  21. package/src/EncryptedY.ts +2 -2
  22. package/src/FileBlobStore.ts +10 -0
  23. package/src/IdentityDoc.ts +25 -0
  24. package/src/MetaManager.ts +100 -0
  25. package/src/MnemonicKeyDerivation.ts +4 -4
  26. package/src/NotificationsClient.ts +120 -98
  27. package/src/OutgoingMessages/SubdocMessage.ts +2 -2
  28. package/src/RpcClient.ts +659 -0
  29. package/src/TreeManager.ts +473 -0
  30. package/src/TreeTimestamps.ts +28 -25
  31. package/src/index.ts +71 -1
  32. package/src/messageRecord.ts +121 -0
  33. package/src/types.ts +174 -16
  34. package/src/webrtc/AbracadabraWebRTC.ts +2 -2
  35. package/src/webrtc/DataChannelRouter.ts +2 -2
  36. package/src/webrtc/E2EEChannel.ts +3 -3
  37. package/src/webrtc/FileTransferChannel.ts +9 -2
@@ -45,16 +45,64 @@ export class DocKeyManager {
45
45
  }
46
46
 
47
47
  const envelope = await client.getMyKeyEnvelope(docId);
48
- if (!envelope) return null;
48
+ if (envelope) {
49
+ const x25519PrivKey = await keystore.getX25519PrivateKey();
50
+ try {
51
+ const wrapped = fromBase64(envelope.encrypted_key);
52
+ const docKey = await this._unwrapKey(wrapped, x25519PrivKey, docId);
53
+ this.cache.set(docId, { key: docKey, epoch: envelope.key_epoch, fetchedAt: Date.now() });
54
+ return docKey;
55
+ } finally {
56
+ x25519PrivKey.fill(0);
57
+ }
58
+ }
49
59
 
50
- const x25519PrivKey = await keystore.getX25519PrivateKey();
60
+ // Inheritance fallback. The doc has no envelope of our own — but it
61
+ // might inherit its encryption mode from an ancestor (e.g. a child
62
+ // page under an E2E space). The ancestor IS provisioned for us
63
+ // (otherwise we couldn't open the parent either). Resolve the
64
+ // encryption-source via /docs/:id/encryption.inherited_from, fetch
65
+ // OUR envelope on that source, unwrap with the source's salt,
66
+ // re-wrap for the current docId with its own salt, upload the new
67
+ // envelope so subsequent calls hit the direct-envelope path.
51
68
  try {
52
- const wrapped = fromBase64(envelope.encrypted_key);
53
- const docKey = await this._unwrapKey(wrapped, x25519PrivKey, docId);
54
- this.cache.set(docId, { key: docKey, epoch: envelope.key_epoch, fetchedAt: Date.now() });
55
- return docKey;
56
- } finally {
57
- x25519PrivKey.fill(0);
69
+ const enc = await client.getDocEncryption(docId);
70
+ if (enc.effective_mode !== "e2e" || !enc.inherited_from || enc.inherited_from === docId) {
71
+ return null;
72
+ }
73
+ const sourceEnv = await client.getMyKeyEnvelope(enc.inherited_from);
74
+ if (!sourceEnv) return null;
75
+ const x25519PrivKey = await keystore.getX25519PrivateKey();
76
+ try {
77
+ const wrappedSrc = fromBase64(sourceEnv.encrypted_key);
78
+ const docKey = await this._unwrapKey(wrappedSrc, x25519PrivKey, enc.inherited_from);
79
+ this.cache.set(docId, { key: docKey, epoch: sourceEnv.key_epoch, fetchedAt: Date.now() });
80
+ // Re-wrap for the current doc + upload so future opens take
81
+ // the direct path. Best-effort — caller already has the key.
82
+ const me = await client.getMe();
83
+ if (me.publicKey) {
84
+ const myKeys = await client.listUserKeys(me.id);
85
+ const primaryKey = myKeys[0];
86
+ if (primaryKey?.x25519Key) {
87
+ const x25519Pub = fromBase64(primaryKey.x25519Key.replace(/-/g, "+").replace(/_/g, "/"));
88
+ const rewrapped = await this.wrapKeyForRecipient(docKey, x25519Pub, docId);
89
+ const b64 = (() => {
90
+ let s = "";
91
+ for (let i = 0; i < rewrapped.length; i++) s += String.fromCharCode(rewrapped[i]!);
92
+ return btoa(s);
93
+ })();
94
+ await client.uploadKeyEnvelopes(docId, {
95
+ key_epoch: sourceEnv.key_epoch,
96
+ envelopes: [{ recipient_key_id: primaryKey.id, encrypted_key: b64 }],
97
+ }).catch(() => null);
98
+ }
99
+ }
100
+ return docKey;
101
+ } finally {
102
+ x25519PrivKey.fill(0);
103
+ }
104
+ } catch {
105
+ return null;
58
106
  }
59
107
  }
60
108
 
@@ -73,11 +121,11 @@ export class DocKeyManager {
73
121
 
74
122
  const salt = new TextEncoder().encode(docId);
75
123
  const keyBytes = hkdf(sha256, sharedSecret, salt, HKDF_INFO, 32);
76
- const wrapKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["encrypt"]);
124
+ const wrapKey = await crypto.subtle.importKey("raw", keyBytes as BufferSource, { name: "AES-GCM" }, false, ["encrypt"]);
77
125
 
78
126
  const rawDocKey = await crypto.subtle.exportKey("raw", docKey);
79
127
  const nonce = crypto.getRandomValues(new Uint8Array(12));
80
- const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, wrapKey, rawDocKey));
128
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce as BufferSource }, wrapKey, rawDocKey));
81
129
 
82
130
  const result = new Uint8Array(32 + 12 + ciphertext.length);
83
131
  result.set(ephemeralPub, 0);
@@ -94,8 +142,8 @@ export class DocKeyManager {
94
142
  const sharedSecret = x25519.getSharedSecret(recipientX25519PrivKey, ephemeralPub);
95
143
  const salt = new TextEncoder().encode(docId);
96
144
  const keyBytes = hkdf(sha256, sharedSecret, salt, HKDF_INFO, 32);
97
- const wrapKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["decrypt"]);
98
- const rawDocKey = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, wrapKey, ciphertext);
145
+ const wrapKey = await crypto.subtle.importKey("raw", keyBytes as BufferSource, { name: "AES-GCM" }, false, ["decrypt"]);
146
+ const rawDocKey = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce as BufferSource }, wrapKey, ciphertext as BufferSource);
99
147
  return crypto.subtle.importKey("raw", rawDocKey, { name: "AES-GCM" }, true, ["encrypt", "decrypt"]);
100
148
  }
101
149