@enbox/agent 0.1.8 → 0.2.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.
- package/dist/browser.mjs +11 -11
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/anonymous-dwn-api.js +1 -1
- package/dist/esm/anonymous-dwn-api.js.map +1 -1
- package/dist/esm/connect.js +4 -10
- package/dist/esm/connect.js.map +1 -1
- package/dist/esm/dwn-api.js +144 -195
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-protocol-cache.js +149 -0
- package/dist/esm/dwn-protocol-cache.js.map +1 -0
- package/dist/esm/dwn-record-upgrade.js +3 -3
- package/dist/esm/dwn-record-upgrade.js.map +1 -1
- package/dist/esm/hd-identity-vault.js +6 -5
- package/dist/esm/hd-identity-vault.js.map +1 -1
- package/dist/esm/identity-api.js +0 -2
- package/dist/esm/identity-api.js.map +1 -1
- package/dist/esm/oidc.js +2 -1
- package/dist/esm/oidc.js.map +1 -1
- package/dist/esm/permissions-api.js +24 -6
- package/dist/esm/permissions-api.js.map +1 -1
- package/dist/esm/prototyping/crypto/jose/jwe-flattened.js +1 -1
- package/dist/esm/prototyping/crypto/jose/jwe-flattened.js.map +1 -1
- package/dist/esm/prototyping/crypto/jose/jwe.js +11 -3
- package/dist/esm/prototyping/crypto/jose/jwe.js.map +1 -1
- package/dist/esm/store-data-protocols.js +2 -2
- package/dist/esm/store-data-protocols.js.map +1 -1
- package/dist/esm/sync-api.js +3 -0
- package/dist/esm/sync-api.js.map +1 -1
- package/dist/esm/sync-engine-level.js +447 -29
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/test-harness.js +3 -5
- package/dist/esm/test-harness.js.map +1 -1
- package/dist/esm/types/dwn.js.map +1 -1
- package/dist/types/anonymous-dwn-api.d.ts +3 -3
- package/dist/types/anonymous-dwn-api.d.ts.map +1 -1
- package/dist/types/connect.d.ts.map +1 -1
- package/dist/types/dwn-api.d.ts +11 -18
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-protocol-cache.d.ts +76 -0
- package/dist/types/dwn-protocol-cache.d.ts.map +1 -0
- package/dist/types/hd-identity-vault.d.ts.map +1 -1
- package/dist/types/identity-api.d.ts.map +1 -1
- package/dist/types/oidc.d.ts.map +1 -1
- package/dist/types/permissions-api.d.ts.map +1 -1
- package/dist/types/prototyping/crypto/jose/jwe-flattened.d.ts.map +1 -1
- package/dist/types/prototyping/crypto/jose/jwe.d.ts +12 -2
- package/dist/types/prototyping/crypto/jose/jwe.d.ts.map +1 -1
- package/dist/types/sync-api.d.ts +3 -4
- package/dist/types/sync-api.d.ts.map +1 -1
- package/dist/types/sync-engine-level.d.ts +63 -5
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/test-harness.d.ts.map +1 -1
- package/dist/types/types/dwn.d.ts +18 -19
- package/dist/types/types/dwn.d.ts.map +1 -1
- package/dist/types/types/sync.d.ts +47 -5
- package/dist/types/types/sync.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/anonymous-dwn-api.ts +4 -4
- package/src/connect.ts +4 -10
- package/src/dwn-api.ts +192 -250
- package/src/dwn-protocol-cache.ts +216 -0
- package/src/dwn-record-upgrade.ts +3 -3
- package/src/hd-identity-vault.ts +6 -5
- package/src/identity-api.ts +0 -2
- package/src/oidc.ts +2 -1
- package/src/permissions-api.ts +28 -6
- package/src/prototyping/crypto/jose/jwe-flattened.ts +4 -1
- package/src/prototyping/crypto/jose/jwe.ts +24 -2
- package/src/store-data-protocols.ts +2 -2
- package/src/sync-api.ts +7 -3
- package/src/sync-engine-level.ts +509 -32
- package/src/test-harness.ts +3 -5
- package/src/types/dwn.ts +19 -21
- package/src/types/sync.ts +56 -5
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol definition fetching and caching utilities for {@link AgentDwnApi}.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `dwn-api.ts` to keep protocol-resolution logic in its own
|
|
5
|
+
* module.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DidUrlDereferencer } from '@enbox/dids';
|
|
11
|
+
import type { PublicKeyJwk } from '@enbox/crypto';
|
|
12
|
+
import type { TtlCache } from '@enbox/common';
|
|
13
|
+
import type {
|
|
14
|
+
ProtocolDefinition,
|
|
15
|
+
ProtocolsQueryReply,
|
|
16
|
+
RecordsQueryReply,
|
|
17
|
+
} from '@enbox/dwn-sdk-js';
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
DwnInterface,
|
|
21
|
+
DwnMessage,
|
|
22
|
+
DwnMessageReply,
|
|
23
|
+
DwnSigner,
|
|
24
|
+
MessageHandler,
|
|
25
|
+
} from './types/dwn.js';
|
|
26
|
+
|
|
27
|
+
import { KeyDerivationScheme } from '@enbox/dwn-sdk-js';
|
|
28
|
+
|
|
29
|
+
import { getDwnServiceEndpointUrls } from './utils.js';
|
|
30
|
+
import { DwnInterface as DwnInterfaceEnum, dwnMessageConstructors } from './types/dwn.js';
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Dependency signatures — keep the extracted code free of `this` references.
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** Callback to obtain a DWN signer for a given DID. */
|
|
37
|
+
type GetSignerFn = (author: string) => Promise<DwnSigner>;
|
|
38
|
+
|
|
39
|
+
/** Callback to send a raw DWN request to a remote endpoint. */
|
|
40
|
+
type SendDwnRpcRequestFn = <T extends DwnInterface>(params: {
|
|
41
|
+
targetDid: string;
|
|
42
|
+
dwnEndpointUrls: string[];
|
|
43
|
+
message: DwnMessage[T];
|
|
44
|
+
data?: Blob;
|
|
45
|
+
subscriptionHandler?: MessageHandler[T];
|
|
46
|
+
}) => Promise<DwnMessageReply[T]>;
|
|
47
|
+
|
|
48
|
+
/** Minimal DWN interface needed for local `processMessage` calls. */
|
|
49
|
+
interface DwnNode {
|
|
50
|
+
processMessage(tenant: string, message: unknown, options?: unknown): Promise<any>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Exported functions
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetches a protocol definition from the **local** DWN, backed by a TTL cache.
|
|
59
|
+
*
|
|
60
|
+
* @param tenantDid - The DID whose DWN to query
|
|
61
|
+
* @param protocolUri - The protocol URI to look up
|
|
62
|
+
* @param dwn - The local DWN instance
|
|
63
|
+
* @param getSigner - Callback to obtain the signer for `tenantDid`
|
|
64
|
+
* @param cache - The shared protocol definition cache
|
|
65
|
+
* @returns The protocol definition, or `undefined` if not installed
|
|
66
|
+
*/
|
|
67
|
+
export async function getProtocolDefinition(
|
|
68
|
+
tenantDid: string,
|
|
69
|
+
protocolUri: string,
|
|
70
|
+
dwn: DwnNode,
|
|
71
|
+
getSigner: GetSignerFn,
|
|
72
|
+
cache: TtlCache<string, ProtocolDefinition>,
|
|
73
|
+
): Promise<ProtocolDefinition | undefined> {
|
|
74
|
+
const cacheKey = `${tenantDid}~${protocolUri}`;
|
|
75
|
+
|
|
76
|
+
const cached = cache.get(cacheKey);
|
|
77
|
+
if (cached) {
|
|
78
|
+
return cached;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const signer = await getSigner(tenantDid);
|
|
82
|
+
const protocolsQuery = await dwnMessageConstructors[
|
|
83
|
+
DwnInterfaceEnum.ProtocolsQuery
|
|
84
|
+
].create({
|
|
85
|
+
filter: { protocol: protocolUri },
|
|
86
|
+
signer,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const reply = await dwn.processMessage(
|
|
90
|
+
tenantDid, protocolsQuery.message,
|
|
91
|
+
);
|
|
92
|
+
if (reply.status.code !== 200 || !reply.entries?.length) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const definition = reply.entries[0].descriptor.definition;
|
|
97
|
+
cache.set(cacheKey, definition);
|
|
98
|
+
return definition;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fetches a protocol definition from a **remote** DWN.
|
|
103
|
+
*
|
|
104
|
+
* Uses an unsigned `ProtocolsQuery` since public protocols can be queried
|
|
105
|
+
* anonymously.
|
|
106
|
+
*
|
|
107
|
+
* @param targetDid - The remote DWN owner
|
|
108
|
+
* @param protocolUri - The protocol URI to look up
|
|
109
|
+
* @param didDereferencer - A DID URL dereferencer for resolving service endpoints
|
|
110
|
+
* @param sendDwnRpcRequest - Callback to send the RPC query
|
|
111
|
+
* @param cache - The shared protocol definition cache
|
|
112
|
+
* @returns The protocol definition
|
|
113
|
+
* @throws If the protocol cannot be fetched
|
|
114
|
+
*/
|
|
115
|
+
export async function fetchRemoteProtocolDefinition(
|
|
116
|
+
targetDid: string,
|
|
117
|
+
protocolUri: string,
|
|
118
|
+
didDereferencer: DidUrlDereferencer,
|
|
119
|
+
sendDwnRpcRequest: SendDwnRpcRequestFn,
|
|
120
|
+
cache: TtlCache<string, ProtocolDefinition>,
|
|
121
|
+
): Promise<ProtocolDefinition> {
|
|
122
|
+
const cacheKey = `remote~${targetDid}~${protocolUri}`;
|
|
123
|
+
const cached = cache.get(cacheKey);
|
|
124
|
+
if (cached) { return cached; }
|
|
125
|
+
|
|
126
|
+
const protocolsQuery = await dwnMessageConstructors[
|
|
127
|
+
DwnInterfaceEnum.ProtocolsQuery
|
|
128
|
+
].create({
|
|
129
|
+
filter: { protocol: protocolUri },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const reply = await sendDwnRpcRequest({
|
|
133
|
+
targetDid,
|
|
134
|
+
dwnEndpointUrls : await getDwnServiceEndpointUrls(targetDid, didDereferencer),
|
|
135
|
+
message : protocolsQuery.message,
|
|
136
|
+
}) as ProtocolsQueryReply;
|
|
137
|
+
|
|
138
|
+
if (reply.status.code !== 200 || !reply.entries?.length) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`AgentDwnApi: Failed to fetch protocol '${protocolUri}' from ` +
|
|
141
|
+
`'${targetDid}'. The recipient may not have the protocol installed.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const definition = reply.entries[0].descriptor.definition;
|
|
146
|
+
cache.set(cacheKey, definition);
|
|
147
|
+
return definition;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extracts the `derivedPublicKey` from an existing `ProtocolContext`-encrypted
|
|
152
|
+
* record in a context on a remote DWN.
|
|
153
|
+
*
|
|
154
|
+
* This key allows an external author to encrypt new records in the same
|
|
155
|
+
* context without knowing the context private key.
|
|
156
|
+
*
|
|
157
|
+
* @param targetDid - The DWN owner's DID
|
|
158
|
+
* @param protocolUri - The protocol URI to search
|
|
159
|
+
* @param rootContextId - The root context ID
|
|
160
|
+
* @param requesterDid - The DID of the requester (used for signing the query)
|
|
161
|
+
* @param didDereferencer - A DID URL dereferencer for resolving service endpoints
|
|
162
|
+
* @param getSigner - Callback to obtain the signer for `requesterDid`
|
|
163
|
+
* @param sendDwnRpcRequest - Callback to send the RPC query
|
|
164
|
+
* @returns The rootKeyId and derivedPublicKey, or `undefined` if no
|
|
165
|
+
* `ProtocolContext` record exists yet
|
|
166
|
+
*/
|
|
167
|
+
export async function extractDerivedPublicKey(
|
|
168
|
+
targetDid: string,
|
|
169
|
+
protocolUri: string,
|
|
170
|
+
rootContextId: string,
|
|
171
|
+
requesterDid: string,
|
|
172
|
+
didDereferencer: DidUrlDereferencer,
|
|
173
|
+
getSigner: GetSignerFn,
|
|
174
|
+
sendDwnRpcRequest: SendDwnRpcRequestFn,
|
|
175
|
+
): Promise<{ rootKeyId: string; derivedPublicKey: PublicKeyJwk } | undefined> {
|
|
176
|
+
const signer = await getSigner(requesterDid);
|
|
177
|
+
|
|
178
|
+
// Query the target's DWN for any record in this context
|
|
179
|
+
const recordsQuery = await dwnMessageConstructors[DwnInterfaceEnum.RecordsQuery].create({
|
|
180
|
+
signer,
|
|
181
|
+
filter: {
|
|
182
|
+
protocol : protocolUri,
|
|
183
|
+
contextId : rootContextId,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const dwnEndpointUrls = await getDwnServiceEndpointUrls(targetDid, didDereferencer);
|
|
188
|
+
const queryReply = await sendDwnRpcRequest<DwnInterfaceEnum.RecordsQuery>({
|
|
189
|
+
targetDid,
|
|
190
|
+
dwnEndpointUrls,
|
|
191
|
+
message: recordsQuery.message,
|
|
192
|
+
}) as RecordsQueryReply;
|
|
193
|
+
|
|
194
|
+
if (queryReply.status.code !== 200 || !queryReply.entries?.length) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Search entries for one with a ProtocolContext recipient entry
|
|
199
|
+
// that includes derivedPublicKey
|
|
200
|
+
for (const entry of queryReply.entries) {
|
|
201
|
+
if (entry.encryption?.recipients) {
|
|
202
|
+
const contextEntry = entry.encryption.recipients.find(
|
|
203
|
+
(r: { header: { derivationScheme: string; derivedPublicKey?: PublicKeyJwk } }) =>
|
|
204
|
+
r.header.derivationScheme === KeyDerivationScheme.ProtocolContext && r.header.derivedPublicKey
|
|
205
|
+
);
|
|
206
|
+
if (contextEntry?.header.derivedPublicKey) {
|
|
207
|
+
return {
|
|
208
|
+
rootKeyId : contextEntry.header.kid,
|
|
209
|
+
derivedPublicKey : contextEntry.header.derivedPublicKey,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
@@ -118,7 +118,7 @@ export async function upgradeExternalRootRecord(
|
|
|
118
118
|
// We must also update the state index and event stream to keep sync and
|
|
119
119
|
// real-time subscribers consistent — without this, the upgraded record
|
|
120
120
|
// would never propagate to remote DWNs or notify subscribers.
|
|
121
|
-
const { messageStore, stateIndex,
|
|
121
|
+
const { messageStore, stateIndex, eventLog } = dwn.storage;
|
|
122
122
|
|
|
123
123
|
// Validate the upgrade only changed encryption and authorization fields.
|
|
124
124
|
// The descriptor, recordId, contextId, and data must remain identical.
|
|
@@ -157,8 +157,8 @@ export async function upgradeExternalRootRecord(
|
|
|
157
157
|
await stateIndex.delete(tenantDid, [originalCid]);
|
|
158
158
|
|
|
159
159
|
// Notify real-time subscribers (mirrors handler behavior)
|
|
160
|
-
if (
|
|
161
|
-
|
|
160
|
+
if (eventLog !== undefined) {
|
|
161
|
+
await eventLog.emit(tenantDid, { message: upgradedMessage }, upgradedIndexes);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// Cache context key info for subsequent writes in this context
|
package/src/hd-identity-vault.ts
CHANGED
|
@@ -250,7 +250,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
250
250
|
jwe : cekJwe,
|
|
251
251
|
key : Convert.string(oldPassword).toUint8Array(),
|
|
252
252
|
crypto : this.crypto,
|
|
253
|
-
keyManager : new LocalKeyManager()
|
|
253
|
+
keyManager : new LocalKeyManager(),
|
|
254
|
+
options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
|
|
254
255
|
}));
|
|
255
256
|
contentEncryptionKey = Convert.uint8Array(contentEncryptionKeyBytes).toObject() as Jwk;
|
|
256
257
|
|
|
@@ -297,7 +298,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
297
298
|
jwe : didJwe,
|
|
298
299
|
key : this._contentEncryptionKey!,
|
|
299
300
|
crypto : this.crypto,
|
|
300
|
-
keyManager : new LocalKeyManager()
|
|
301
|
+
keyManager : new LocalKeyManager(),
|
|
302
|
+
options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
|
|
301
303
|
});
|
|
302
304
|
|
|
303
305
|
// Convert the DID from a byte array to PortableDid format.
|
|
@@ -530,8 +532,6 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
530
532
|
id : 'dwn',
|
|
531
533
|
type : 'DecentralizedWebNode',
|
|
532
534
|
serviceEndpoint : dwnEndpoints,
|
|
533
|
-
enc : '#enc',
|
|
534
|
-
sig : '#sig',
|
|
535
535
|
}
|
|
536
536
|
];
|
|
537
537
|
}
|
|
@@ -761,7 +761,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
761
761
|
jwe : cekJwe,
|
|
762
762
|
key : Convert.string(password).toUint8Array(),
|
|
763
763
|
crypto : this.crypto,
|
|
764
|
-
keyManager : new LocalKeyManager()
|
|
764
|
+
keyManager : new LocalKeyManager(),
|
|
765
|
+
options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
|
|
765
766
|
});
|
|
766
767
|
const contentEncryptionKey = Convert.uint8Array(contentEncryptionKeyBytes).toObject() as Jwk;
|
|
767
768
|
|
package/src/identity-api.ts
CHANGED
|
@@ -254,8 +254,6 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
|
|
|
254
254
|
id : 'dwn',
|
|
255
255
|
type : 'DecentralizedWebNode',
|
|
256
256
|
serviceEndpoint : endpoints,
|
|
257
|
-
enc : '#enc',
|
|
258
|
-
sig : '#sig'
|
|
259
257
|
};
|
|
260
258
|
|
|
261
259
|
// if no other services exist, create a new array with the DWN service
|
package/src/oidc.ts
CHANGED
|
@@ -410,7 +410,7 @@ async function verifyJwt({ jwt }: { jwt: string }): Promise<Record<string, unkno
|
|
|
410
410
|
* using the encryption key passed via QR code.
|
|
411
411
|
*/
|
|
412
412
|
const getAuthRequest = async (request_uri: string, encryption_key: string): Promise<Web5ConnectAuthRequest> => {
|
|
413
|
-
const authRequest = await fetch(request_uri);
|
|
413
|
+
const authRequest = await fetch(request_uri, { signal: AbortSignal.timeout(30_000) });
|
|
414
414
|
const jwe = await authRequest.text();
|
|
415
415
|
const jwt = await decryptAuthRequest({
|
|
416
416
|
jwe,
|
|
@@ -832,6 +832,7 @@ async function submitAuthResponse(
|
|
|
832
832
|
headers : {
|
|
833
833
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
834
834
|
},
|
|
835
|
+
signal: AbortSignal.timeout(30_000),
|
|
835
836
|
});
|
|
836
837
|
}
|
|
837
838
|
|
package/src/permissions-api.ts
CHANGED
|
@@ -114,7 +114,7 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
114
114
|
if (revokedGrantIds.has(entry.recordId)) {
|
|
115
115
|
continue;
|
|
116
116
|
}
|
|
117
|
-
const grant =
|
|
117
|
+
const grant = DwnPermissionGrant.parse(entry);
|
|
118
118
|
grants.push({ grant, message: entry });
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -193,7 +193,7 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
193
193
|
|
|
194
194
|
const requests: PermissionRequestEntry[] = [];
|
|
195
195
|
for (const entry of reply.entries! as DwnDataEncodedRecordsWriteMessage[]) {
|
|
196
|
-
const request =
|
|
196
|
+
const request = DwnPermissionRequest.parse(entry);
|
|
197
197
|
requests.push({ request, message: entry });
|
|
198
198
|
}
|
|
199
199
|
|
|
@@ -275,7 +275,7 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
275
275
|
encodedData: Convert.uint8Array(permissionsGrantBytes).toBase64Url()
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
-
const grant =
|
|
278
|
+
const grant = DwnPermissionGrant.parse(dataEncodedMessage);
|
|
279
279
|
|
|
280
280
|
return { grant, message: dataEncodedMessage };
|
|
281
281
|
}
|
|
@@ -321,7 +321,7 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
321
321
|
encodedData: Convert.uint8Array(permissionRequestBytes).toBase64Url()
|
|
322
322
|
};
|
|
323
323
|
|
|
324
|
-
const request =
|
|
324
|
+
const request = DwnPermissionRequest.parse(dataEncodedMessage);
|
|
325
325
|
|
|
326
326
|
return { request, message: dataEncodedMessage };
|
|
327
327
|
}
|
|
@@ -388,6 +388,11 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
388
388
|
grants: PermissionGrantEntry[],
|
|
389
389
|
delegated: boolean = false
|
|
390
390
|
): Promise<PermissionGrantEntry | undefined> {
|
|
391
|
+
// Two-pass matching: prefer exact scope matches over unified Messages.Read fallback.
|
|
392
|
+
// This ensures that if both a Messages.Sync grant and a Messages.Read grant exist,
|
|
393
|
+
// the specific Messages.Sync grant is returned for MessagesSync lookups.
|
|
394
|
+
let unifiedFallback: PermissionGrantEntry | undefined;
|
|
395
|
+
|
|
391
396
|
for (const entry of grants) {
|
|
392
397
|
const { grant, message } = entry;
|
|
393
398
|
if (delegated === true && grant.delegated !== true) {
|
|
@@ -396,9 +401,19 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
396
401
|
const { messageType, protocol, protocolPath, contextId } = messageParams;
|
|
397
402
|
|
|
398
403
|
if (this.matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId)) {
|
|
399
|
-
|
|
404
|
+
const scopeMessageType = grant.scope.interface + grant.scope.method;
|
|
405
|
+
// Exact match — return immediately
|
|
406
|
+
if (scopeMessageType === messageType) {
|
|
407
|
+
return { grant, message };
|
|
408
|
+
}
|
|
409
|
+
// Unified fallback match — hold for later in case an exact match is found
|
|
410
|
+
if (!unifiedFallback) {
|
|
411
|
+
unifiedFallback = { grant, message };
|
|
412
|
+
}
|
|
400
413
|
}
|
|
401
414
|
}
|
|
415
|
+
|
|
416
|
+
return unifiedFallback;
|
|
402
417
|
}
|
|
403
418
|
|
|
404
419
|
private static matchScopeFromGrant<T extends DwnInterface>(
|
|
@@ -417,7 +432,14 @@ export class AgentPermissionsApi implements PermissionsApi {
|
|
|
417
432
|
|
|
418
433
|
const scope = grant.scope;
|
|
419
434
|
const scopeMessageType = scope.interface + scope.method;
|
|
420
|
-
|
|
435
|
+
|
|
436
|
+
// Messages.Read is a unified scope that covers Messages.Read, Messages.Sync, and Messages.Subscribe.
|
|
437
|
+
// When looking for a MessagesSync or MessagesSubscribe grant, also accept a MessagesRead grant.
|
|
438
|
+
const isMessagesScopeMatch = scopeMessageType === messageType
|
|
439
|
+
|| (scopeMessageType === DwnInterface.MessagesRead
|
|
440
|
+
&& (messageType === DwnInterface.MessagesSync || messageType === DwnInterface.MessagesSubscribe));
|
|
441
|
+
|
|
442
|
+
if (isMessagesScopeMatch) {
|
|
421
443
|
if (isRecordsType(messageType)) {
|
|
422
444
|
const recordScope = scope as DwnRecordsPermissionScope;
|
|
423
445
|
if (recordScope.protocol !== protocol) {
|
|
@@ -270,7 +270,10 @@ export class FlattenedJwe {
|
|
|
270
270
|
? Convert.base64Url(jwe.encrypted_key).toUint8Array()
|
|
271
271
|
: undefined;
|
|
272
272
|
|
|
273
|
-
cek = await JweKeyManagement.decrypt(
|
|
273
|
+
cek = await JweKeyManagement.decrypt(
|
|
274
|
+
{ key, encryptedKey, joseHeader, keyManager, crypto },
|
|
275
|
+
{ minP2cCount: options.minP2cCount }
|
|
276
|
+
);
|
|
274
277
|
|
|
275
278
|
} catch (error: any) {
|
|
276
279
|
// If the error is a CryptoError with code "InvalidJwe" or "AlgorithmNotSupported", re-throw.
|
|
@@ -33,6 +33,15 @@ export interface JweDecryptOptions {
|
|
|
33
33
|
*
|
|
34
34
|
*/
|
|
35
35
|
allowedEncValues?: string[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Minimum acceptable PBES2 iteration count ("p2c") for key derivation during decryption.
|
|
39
|
+
* Per RFC 7518 Section 4.8.1.2, a minimum of 1000 is RECOMMENDED. Set to a lower value
|
|
40
|
+
* only for test environments where speed is prioritized over security.
|
|
41
|
+
*
|
|
42
|
+
* @default 1000
|
|
43
|
+
*/
|
|
44
|
+
minP2cCount?: number;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
/**
|
|
@@ -281,11 +290,12 @@ export interface JweKeyManagementDecryptParams<TKeyManager, TCrypto> {
|
|
|
281
290
|
*/
|
|
282
291
|
joseHeader: JweHeaderParams;
|
|
283
292
|
|
|
284
|
-
/** Key Manager
|
|
293
|
+
/** Key Manager instance responsible for managing cryptographic keys. */
|
|
285
294
|
keyManager: TKeyManager;
|
|
286
295
|
|
|
287
296
|
/** Crypto API instance that provides the necessary cryptographic operations. */
|
|
288
297
|
crypto: TCrypto;
|
|
298
|
+
|
|
289
299
|
}
|
|
290
300
|
|
|
291
301
|
/**
|
|
@@ -424,8 +434,10 @@ export class JweKeyManagement {
|
|
|
424
434
|
*/
|
|
425
435
|
public static async decrypt<TKeyManager extends KeyManager, TCrypto extends CryptoApi>({
|
|
426
436
|
key, encryptedKey, joseHeader, crypto
|
|
427
|
-
}: JweKeyManagementDecryptParams<TKeyManager, TCrypto
|
|
437
|
+
}: JweKeyManagementDecryptParams<TKeyManager, TCrypto>,
|
|
438
|
+
options?: { minP2cCount?: number }
|
|
428
439
|
): Promise<KeyIdentifier | Jwk> {
|
|
440
|
+
const minP2cCount = options?.minP2cCount ?? 1000;
|
|
429
441
|
// Determine the Key Management Mode employed by the algorithm specified by the "alg"
|
|
430
442
|
// (algorithm) Header Parameter.
|
|
431
443
|
switch (joseHeader.alg) {
|
|
@@ -460,6 +472,16 @@ export class JweKeyManagement {
|
|
|
460
472
|
throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
|
|
461
473
|
}
|
|
462
474
|
|
|
475
|
+
// Per RFC 7518, Section 4.8.1.2, a minimum iteration count of 1000 is RECOMMENDED.
|
|
476
|
+
// Enforce this floor to prevent an attacker from supplying a crafted JWE with a
|
|
477
|
+
// trivially low iteration count that would weaken key derivation.
|
|
478
|
+
if (joseHeader.p2c < minP2cCount) {
|
|
479
|
+
throw new CryptoError(
|
|
480
|
+
CryptoErrorCode.InvalidJwe,
|
|
481
|
+
`JOSE Header "p2c" (PBES2 Count) is ${joseHeader.p2c}, which is below the minimum of ${minP2cCount}.`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
463
485
|
if (typeof joseHeader.p2s !== 'string') {
|
|
464
486
|
throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
|
|
465
487
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
|
|
2
2
|
|
|
3
3
|
export const IdentityProtocolDefinition: ProtocolDefinition = {
|
|
4
|
-
protocol : '
|
|
4
|
+
protocol : 'https://identity.foundation/protocols/web5/identity-store',
|
|
5
5
|
published : false,
|
|
6
6
|
types : {
|
|
7
7
|
portableDid: {
|
|
@@ -47,7 +47,7 @@ export const KeyDeliveryProtocolDefinition: ProtocolDefinition = {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export const JwkProtocolDefinition: ProtocolDefinition = {
|
|
50
|
-
protocol : '
|
|
50
|
+
protocol : 'https://identity.foundation/protocols/web5/jwk-store',
|
|
51
51
|
published : false,
|
|
52
52
|
types : {
|
|
53
53
|
privateJwk: {
|
package/src/sync-api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Web5PlatformAgent } from './types/agent.js';
|
|
2
|
-
import type { SyncEngine, SyncIdentityOptions } from './types/sync.js';
|
|
2
|
+
import type { StartSyncParams, SyncConnectivityState, SyncEngine, SyncIdentityOptions } from './types/sync.js';
|
|
3
3
|
|
|
4
4
|
export type SyncApiParams = {
|
|
5
5
|
agent?: Web5PlatformAgent;
|
|
@@ -41,6 +41,10 @@ export class AgentSyncApi implements SyncEngine {
|
|
|
41
41
|
this._syncEngine.agent = agent;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
get connectivityState(): SyncConnectivityState {
|
|
45
|
+
return this._syncEngine.connectivityState;
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
public async registerIdentity(params: { did: string; options?: SyncIdentityOptions }): Promise<void> {
|
|
45
49
|
await this._syncEngine.registerIdentity(params);
|
|
46
50
|
}
|
|
@@ -61,11 +65,11 @@ export class AgentSyncApi implements SyncEngine {
|
|
|
61
65
|
return this._syncEngine.sync(direction);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
public startSync(params:
|
|
68
|
+
public startSync(params: StartSyncParams): Promise<void> {
|
|
65
69
|
return this._syncEngine.startSync(params);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
public stopSync(timeout?: number): Promise<void> {
|
|
69
73
|
return this._syncEngine.stopSync(timeout);
|
|
70
74
|
}
|
|
71
|
-
}
|
|
75
|
+
}
|