@mdbase-dev/connect 0.1.0-beta.56 → 0.1.0-beta.57
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.
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/connect.d.ts +0 -2
- package/dist/connect.js +7 -31
- package/dist/connection-transport.js +3 -1
- package/dist/crypto.js +22 -9
- package/dist/pending-recovery-transports.d.ts +4 -0
- package/dist/pending-recovery-transports.js +48 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-4Lyda0FLrTi1k549p7spkiOUvz9jT9v7sm+BUgVC4uwPVgyZgY8o6aJlDd+H45xY
|
package/dist/connect.d.ts
CHANGED
|
@@ -53,8 +53,6 @@ export declare class MdbaseConnectInternals<Frontmatter extends JsonObject> {
|
|
|
53
53
|
removeToken(collectionId: string, keyHandle?: string, reason?: MdbaseUnavailableReason, discardPending?: boolean): void;
|
|
54
54
|
tokenKey(collectionId: string): string;
|
|
55
55
|
pendingMutationKey(collectionId: string): string;
|
|
56
|
-
private removePendingMutations;
|
|
57
|
-
private pendingMutationsUseKey;
|
|
58
56
|
notificationKey(collectionId: string, transport?: "web_push" | "fcm"): string;
|
|
59
57
|
directPreferenceKey(): string;
|
|
60
58
|
defaultApplicationOrigin(): string;
|
package/dist/connect.js
CHANGED
|
@@ -8,6 +8,7 @@ import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
|
|
|
8
8
|
import { connectError, serverConnectError } from "./errors.js";
|
|
9
9
|
import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
10
10
|
import { uniqueOperations } from "./operation-helpers.js";
|
|
11
|
+
import { pendingMutationsUseKey, pendingRecoveryTransports, removePendingMutations } from "./pending-recovery-transports.js";
|
|
11
12
|
import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
12
13
|
import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultManifestSource, defaultRedirectUri, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, manifestStorageFingerprint, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
|
|
13
14
|
import { storedTokenFromResponse } from "./token-response.js";
|
|
@@ -211,7 +212,7 @@ export class MdbaseConnectInternals {
|
|
|
211
212
|
redirect_uri: this.redirectUri,
|
|
212
213
|
state,
|
|
213
214
|
code_challenge: challenge,
|
|
214
|
-
contracts: authorizationContractRequirements(operations, application.requirements?.files),
|
|
215
|
+
contracts: authorizationContractRequirements(operations, application.requirements?.files, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, targetCollectionId)),
|
|
215
216
|
requested_operations: operations,
|
|
216
217
|
...(application.requirements?.files
|
|
217
218
|
? { requested_files: application.requirements.files }
|
|
@@ -312,7 +313,7 @@ export class MdbaseConnectInternals {
|
|
|
312
313
|
issued_at: issuedAt.toISOString(),
|
|
313
314
|
expires_at: new Date(issuedAt.getTime() + 10 * 60 * 1_000).toISOString(),
|
|
314
315
|
code_challenge: challenge,
|
|
315
|
-
contracts: authorizationContractRequirements(operations, application.requirements?.files),
|
|
316
|
+
contracts: authorizationContractRequirements(operations, application.requirements?.files, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, options.target?.kind === "collection" ? options.target.collectionId : undefined)),
|
|
316
317
|
requested_operations: operations,
|
|
317
318
|
...(application.requirements?.files
|
|
318
319
|
? { requested_files: application.requirements.files }
|
|
@@ -631,12 +632,14 @@ export class MdbaseConnectInternals {
|
|
|
631
632
|
}
|
|
632
633
|
removeToken(collectionId, keyHandle, reason = "authorization_lost", discardPending = false) {
|
|
633
634
|
this.invalidatedConnections.set(collectionId, reason);
|
|
634
|
-
if (keyHandle && (discardPending || !this.
|
|
635
|
+
if (keyHandle && (discardPending || !pendingMutationsUseKey(this.storage, this.pendingMutationKey(collectionId), keyHandle))) {
|
|
635
636
|
void this.keyStore.delete(keyHandle).catch(() => undefined);
|
|
636
637
|
}
|
|
637
638
|
this.storage.removeItem(this.tokenKey(collectionId));
|
|
638
639
|
if (discardPending)
|
|
639
|
-
this.
|
|
640
|
+
removePendingMutations(this.storage, this.pendingMutationKey(collectionId), (pendingKeyHandle) => {
|
|
641
|
+
void this.keyStore.delete(pendingKeyHandle).catch(() => undefined);
|
|
642
|
+
});
|
|
640
643
|
for (const transport of ["web_push", "fcm"]) {
|
|
641
644
|
this.storage.removeItem(this.notificationKey(collectionId, transport));
|
|
642
645
|
}
|
|
@@ -653,33 +656,6 @@ export class MdbaseConnectInternals {
|
|
|
653
656
|
pendingMutationKey(collectionId) {
|
|
654
657
|
return `${this.storagePrefix()}:pending-mutation:${collectionId}`;
|
|
655
658
|
}
|
|
656
|
-
removePendingMutations(collectionId) {
|
|
657
|
-
const baseKey = this.pendingMutationKey(collectionId);
|
|
658
|
-
const keys = [baseKey];
|
|
659
|
-
for (let index = 0; index < this.storage.length; index += 1) {
|
|
660
|
-
const key = this.storage.key(index);
|
|
661
|
-
if (key?.startsWith(`${baseKey}:`))
|
|
662
|
-
keys.push(key);
|
|
663
|
-
}
|
|
664
|
-
for (const key of keys) {
|
|
665
|
-
const pending = parseStored(this.storage.getItem(key));
|
|
666
|
-
if (pending?.keyHandle)
|
|
667
|
-
void this.keyStore.delete(pending.keyHandle).catch(() => undefined);
|
|
668
|
-
this.storage.removeItem(key);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
pendingMutationsUseKey(collectionId, keyHandle) {
|
|
672
|
-
const baseKey = this.pendingMutationKey(collectionId);
|
|
673
|
-
for (let index = 0; index < this.storage.length; index += 1) {
|
|
674
|
-
const key = this.storage.key(index);
|
|
675
|
-
if (key !== baseKey && !key?.startsWith(`${baseKey}:`))
|
|
676
|
-
continue;
|
|
677
|
-
const pending = parseStored(this.storage.getItem(key));
|
|
678
|
-
if (pending?.keyHandle === keyHandle)
|
|
679
|
-
return true;
|
|
680
|
-
}
|
|
681
|
-
return false;
|
|
682
|
-
}
|
|
683
659
|
notificationKey(collectionId, transport = "web_push") {
|
|
684
660
|
return `${this.storagePrefix()}:notifications:${collectionId}:${transport}`;
|
|
685
661
|
}
|
|
@@ -266,7 +266,9 @@ export class ConnectionTransport {
|
|
|
266
266
|
throw error;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
|
|
269
|
+
const expectedProtocol = attempt.pendingMutationRecord?.request?.protocol_version
|
|
270
|
+
?? OPERATION_TRANSPORT_PROTOCOL_VERSION;
|
|
271
|
+
if (body?.protocol_version !== expectedProtocol
|
|
270
272
|
|| body?.request_id !== attempt.requestId) {
|
|
271
273
|
if (attempt.pendingMutation)
|
|
272
274
|
throw unknownMutationOutcome(attempt.requestId, new Error("Operation response protocol or request ID did not match."));
|
package/dist/crypto.js
CHANGED
|
@@ -161,8 +161,14 @@ export async function encryptRelayRequest(store, handle, binding, operation, inp
|
|
|
161
161
|
validateGrantEncryption(binding.encryption);
|
|
162
162
|
const record = await requireAgreementKey(store, handle, binding.encryption.application_agreement_public_key);
|
|
163
163
|
const counter = await store.nextCounter(handle);
|
|
164
|
-
const metadata = {
|
|
165
|
-
|
|
164
|
+
const metadata = {
|
|
165
|
+
binding,
|
|
166
|
+
protocolVersion: OPERATION_TRANSPORT_PROTOCOL_VERSION,
|
|
167
|
+
requestId,
|
|
168
|
+
operation,
|
|
169
|
+
counter
|
|
170
|
+
};
|
|
171
|
+
const key = await deriveDirectionalKey(record.agreementPrivateKey, binding, metadata.protocolVersion, "request");
|
|
166
172
|
const plaintext = new TextEncoder().encode(JSON.stringify(input ?? {}));
|
|
167
173
|
const ciphertext = await crypto.subtle.encrypt({
|
|
168
174
|
name: "AES-GCM",
|
|
@@ -183,11 +189,12 @@ export async function decryptRelayResponse(store, handle, binding, request, resp
|
|
|
183
189
|
const record = await requireAgreementKey(store, handle, binding.encryption.application_agreement_public_key);
|
|
184
190
|
const metadata = {
|
|
185
191
|
binding,
|
|
192
|
+
protocolVersion: request.protocol_version,
|
|
186
193
|
requestId: request.request_id,
|
|
187
194
|
operation: request.operation,
|
|
188
195
|
counter: request.counter
|
|
189
196
|
};
|
|
190
|
-
const key = await deriveDirectionalKey(record.agreementPrivateKey, binding, "response");
|
|
197
|
+
const key = await deriveDirectionalKey(record.agreementPrivateKey, binding, request.protocol_version, "response");
|
|
191
198
|
try {
|
|
192
199
|
const plaintext = await crypto.subtle.decrypt({
|
|
193
200
|
name: "AES-GCM",
|
|
@@ -246,10 +253,10 @@ async function requireSigningKey(store, handle, expectedPublicKey) {
|
|
|
246
253
|
}
|
|
247
254
|
return record;
|
|
248
255
|
}
|
|
249
|
-
async function deriveDirectionalKey(privateKey, binding, direction) {
|
|
256
|
+
async function deriveDirectionalKey(privateKey, binding, protocolVersion, direction) {
|
|
250
257
|
const shared = await deriveP256SharedSecret(privateKey, binding.encryption.connector_agreement_public_key);
|
|
251
258
|
const material = await crypto.subtle.importKey("raw", shared, "HKDF", false, ["deriveKey"]);
|
|
252
|
-
const contextBytes = new TextEncoder().encode(context(binding));
|
|
259
|
+
const contextBytes = new TextEncoder().encode(context(binding, protocolVersion));
|
|
253
260
|
const salt = await crypto.subtle.digest("SHA-256", contextBytes);
|
|
254
261
|
return crypto.subtle.deriveKey({
|
|
255
262
|
name: "HKDF",
|
|
@@ -268,11 +275,11 @@ export async function deriveP256SharedSecret(privateKey, peerPublicKey) {
|
|
|
268
275
|
throw new RelayCryptoError("invalid_public_key", "The connector relay public key is invalid.");
|
|
269
276
|
}
|
|
270
277
|
}
|
|
271
|
-
function context(binding) {
|
|
278
|
+
function context(binding, protocolVersion) {
|
|
272
279
|
const value = binding.encryption;
|
|
273
280
|
return [
|
|
274
281
|
"mdbase-connect",
|
|
275
|
-
|
|
282
|
+
protocolVersion,
|
|
276
283
|
value.suite,
|
|
277
284
|
binding.grantId,
|
|
278
285
|
binding.applicationId,
|
|
@@ -283,12 +290,18 @@ function context(binding) {
|
|
|
283
290
|
].join("|");
|
|
284
291
|
}
|
|
285
292
|
function aad(metadata, direction) {
|
|
286
|
-
return [
|
|
293
|
+
return [
|
|
294
|
+
context(metadata.binding, metadata.protocolVersion),
|
|
295
|
+
metadata.requestId,
|
|
296
|
+
direction,
|
|
297
|
+
metadata.operation,
|
|
298
|
+
metadata.counter
|
|
299
|
+
].join("|");
|
|
287
300
|
}
|
|
288
301
|
function envelopeMetadata(metadata) {
|
|
289
302
|
const encryption = metadata.binding.encryption;
|
|
290
303
|
return {
|
|
291
|
-
protocol_version:
|
|
304
|
+
protocol_version: metadata.protocolVersion,
|
|
292
305
|
suite: RELAY_ENCRYPTION_SUITE,
|
|
293
306
|
request_id: metadata.requestId,
|
|
294
307
|
grant_id: metadata.binding.grantId,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CollectionOperation, OperationTransportProtocolVersion } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
export declare function pendingRecoveryTransports(storage: Storage, storagePrefix: string, operations: readonly CollectionOperation[], collectionId?: string): OperationTransportProtocolVersion[];
|
|
3
|
+
export declare function pendingMutationsUseKey(storage: Storage, baseKey: string, keyHandle: string): boolean;
|
|
4
|
+
export declare function removePendingMutations(storage: Storage, baseKey: string, removeKey: (keyHandle: string) => void): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isSupportedOperationTransport, OPERATION_TRANSPORT_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { parseStored } from "./runtime-utils.js";
|
|
3
|
+
export function pendingRecoveryTransports(storage, storagePrefix, operations, collectionId) {
|
|
4
|
+
const prefix = `${storagePrefix}:pending-mutation:`;
|
|
5
|
+
const versions = new Set();
|
|
6
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
7
|
+
const key = storage.key(index);
|
|
8
|
+
if (!key?.startsWith(prefix))
|
|
9
|
+
continue;
|
|
10
|
+
const pending = parseStored(storage.getItem(key));
|
|
11
|
+
if (!pending
|
|
12
|
+
|| !operations.includes(pending.operation)
|
|
13
|
+
|| (collectionId && pending.collectionId !== collectionId))
|
|
14
|
+
continue;
|
|
15
|
+
const version = pending.envelope?.protocol_version ?? pending.request?.protocol_version;
|
|
16
|
+
if (version !== undefined
|
|
17
|
+
&& version !== OPERATION_TRANSPORT_PROTOCOL_VERSION
|
|
18
|
+
&& isSupportedOperationTransport(version)) {
|
|
19
|
+
versions.add(version);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...versions].sort((left, right) => right - left);
|
|
23
|
+
}
|
|
24
|
+
export function pendingMutationsUseKey(storage, baseKey, keyHandle) {
|
|
25
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
26
|
+
const key = storage.key(index);
|
|
27
|
+
if (key !== baseKey && !key?.startsWith(`${baseKey}:`))
|
|
28
|
+
continue;
|
|
29
|
+
const pending = parseStored(storage.getItem(key));
|
|
30
|
+
if (pending?.keyHandle === keyHandle)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
export function removePendingMutations(storage, baseKey, removeKey) {
|
|
36
|
+
const keys = [baseKey];
|
|
37
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
38
|
+
const key = storage.key(index);
|
|
39
|
+
if (key?.startsWith(`${baseKey}:`))
|
|
40
|
+
keys.push(key);
|
|
41
|
+
}
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
const pending = parseStored(storage.getItem(key));
|
|
44
|
+
if (pending?.keyHandle)
|
|
45
|
+
removeKey(pending.keyHandle);
|
|
46
|
+
storage.removeItem(key);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.57",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.57"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|