Skip to content

Commit 3d1f825

Browse files
authored
[WIP] SyncEngine for specific protocols + delegate sync. (#836)
* first pass at connect flow and grants api * PermissionsApi for Agent, `permissions` API for `Web5` (#833) This refactors a lot of what's in #824 with regards to creating/fetching grants. Satisfies: #827 Introduces a `PermissionsApi` interface and an `AgentPermissionsApi` concrete implementation. The interface implements the following methods `fetchGrants`, `fetchRequests`, `isGrantRevoked`, `createGrant`, `createRequest`, `createRevocation` as convenience methods for dealing with the built-in permission protocol records. The `AgentPermissionsApi` implements an additional static method `matchGrantFromArray` which was moved from a `PermissionsUtil` class, which is used to find the appropriate grant to use when authoring a message. A Private API used in a connected state to find and cache the correct grants to use for the request. A Permissions API which implements `request`, `grant`, `queryRequests`, and `queryGrants` that a user can utilize The `Web5` permissions api introduces 3 helper classes to represent permissions: Class to represent a permission request record. It implements convenience methods similar to the `Record` class where you can `store()`, `import()` or `send()` the underlying request record. Additionally a `grant()` method will create a `PermissionGrant` object. Class to represent a grant record. It implements convenience methods similar to the `Record` class where you can `store()`, `import()` or `send()` the underlying grant record. Additionally a `revoke()` method will create a `GrantRevocation` object, and `isRevoked()` will check if the underlying grant has been revoked. Class to represent a permission grant revocation record. It implements convenience methods similar to the `Record` class where you can `store()` or `send()` the underlying revocation record.
1 parent 34590b2 commit 3d1f825

18 files changed

+2267
-803
lines changed

.changeset/blue-roses-cough.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@web5/agent": minor
3+
"@web5/identity-agent": minor
4+
"@web5/proxy-agent": minor
5+
"@web5/user-agent": minor
6+
---
7+
8+
Add ability to Sync a subset of protocols as a delegate

.changeset/green-dolls-provide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@web5/api": minor
3+
---
4+
5+
Finalize ability to WalletConnect with sync involved

audit-ci.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"ip",
66
"mysql2",
77
"braces",
8-
"GHSA-rv95-896h-c2vc"
8+
"GHSA-rv95-896h-c2vc",
9+
"GHSA-952p-6rrq-rcjv"
910
]
1011
}
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { TtlCache } from '@web5/common';
2+
import { AgentPermissionsApi } from './permissions-api.js';
3+
import { Web5Agent } from './types/agent.js';
4+
import { PermissionGrantEntry } from './types/permissions.js';
5+
import { DwnInterface } from './types/dwn.js';
6+
7+
export class CachedPermissions {
8+
9+
/** the default value for whether a fetch is cached or not */
10+
private cachedDefault: boolean;
11+
12+
/** Holds the instance of {@link AgentPermissionsApi} that helps when dealing with permissions protocol records */
13+
private permissionsApi: AgentPermissionsApi;
14+
15+
/** cache for fetching a permission {@link PermissionGrant}, keyed by a specific MessageType and protocol */
16+
private cachedPermissions: TtlCache<string, PermissionGrantEntry> = new TtlCache({ ttl: 60 * 1000 });
17+
18+
constructor({ agent, cachedDefault }:{ agent: Web5Agent, cachedDefault?: boolean }) {
19+
this.permissionsApi = new AgentPermissionsApi({ agent });
20+
this.cachedDefault = cachedDefault ?? false;
21+
}
22+
23+
public async getPermission<T extends DwnInterface>({ connectedDid, delegateDid, delegate, messageType, protocol, cached = this.cachedDefault }: {
24+
connectedDid: string;
25+
delegateDid: string;
26+
messageType: T;
27+
protocol?: string;
28+
cached?: boolean;
29+
delegate?: boolean;
30+
}): Promise<PermissionGrantEntry> {
31+
// Currently we only support finding grants based on protocols
32+
// A different approach may be necessary when we introduce `protocolPath` and `contextId` specific impersonation
33+
const cacheKey = [ connectedDid, delegateDid, messageType, protocol ].join('~');
34+
const cachedGrant = cached ? this.cachedPermissions.get(cacheKey) : undefined;
35+
if (cachedGrant) {
36+
return cachedGrant;
37+
}
38+
39+
const permissionGrants = await this.permissionsApi.fetchGrants({
40+
author : delegateDid,
41+
target : delegateDid,
42+
grantor : connectedDid,
43+
grantee : delegateDid,
44+
});
45+
46+
// get the delegate grants that match the messageParams and are associated with the connectedDid as the grantor
47+
const grant = await AgentPermissionsApi.matchGrantFromArray(
48+
connectedDid,
49+
delegateDid,
50+
{ messageType, protocol },
51+
permissionGrants,
52+
delegate
53+
);
54+
55+
if (!grant) {
56+
throw new Error(`CachedPermissions: No permissions found for ${messageType}: ${protocol}`);
57+
}
58+
59+
this.cachedPermissions.set(cacheKey, grant);
60+
return grant;
61+
}
62+
63+
public async clear(): Promise<void> {
64+
this.cachedPermissions.clear();
65+
}
66+
}

packages/agent/src/dwn-api.ts

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
DataStoreLevel,
66
Dwn,
77
DwnConfig,
8+
DwnInterfaceName,
89
DwnMethodName,
910
EventLogLevel,
1011
GenericMessage,
@@ -23,8 +24,11 @@ import type {
2324
DwnMessageInstance,
2425
DwnMessageParams,
2526
DwnMessageReply,
27+
DwnMessagesPermissionScope,
2628
DwnMessageWithData,
29+
DwnPermissionScope,
2730
DwnRecordsInterfaces,
31+
DwnRecordsPermissionScope,
2832
DwnResponse,
2933
DwnSigner,
3034
MessageHandler,
@@ -70,6 +74,14 @@ export function isRecordsType(messageType: DwnInterface): messageType is DwnReco
7074
messageType === DwnInterface.RecordsWrite;
7175
}
7276

77+
export function isRecordPermissionScope(scope: DwnPermissionScope): scope is DwnRecordsPermissionScope {
78+
return scope.interface === DwnInterfaceName.Records;
79+
}
80+
81+
export function isMessagesPermissionScope(scope: DwnPermissionScope): scope is DwnMessagesPermissionScope {
82+
return scope.interface === DwnInterfaceName.Messages;
83+
}
84+
7385
export class AgentDwnApi {
7486
/**
7587
* Holds the instance of a `Web5PlatformAgent` that represents the current execution context for

packages/agent/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type * from './types/sync.js';
88
export type * from './types/vc.js';
99

1010
export * from './bearer-identity.js';
11+
export * from './cached-permissions.js';
1112
export * from './crypto-api.js';
1213
export * from './did-api.js';
1314
export * from './dwn-api.js';

packages/agent/src/store-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
165165

166166
// If the write fails, throw an error.
167167
if (!(message && status.code === 202)) {
168-
throw new Error(`${this.name}: Failed to write data to store for: ${id}`);
168+
throw new Error(`${this.name}: Failed to write data to store for ${id}: ${status.detail}`);
169169
}
170170

171171
// Add the ID of the newly created record to the index.

packages/agent/src/sync-api.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SyncEngine } from './types/sync.js';
1+
import type { SyncEngine, SyncIdentityOptions } from './types/sync.js';
22
import type { Web5PlatformAgent } from './types/agent.js';
33

44
export type SyncApiParams = {
@@ -41,10 +41,14 @@ export class AgentSyncApi implements SyncEngine {
4141
this._syncEngine.agent = agent;
4242
}
4343

44-
public async registerIdentity(params: { did: string; }): Promise<void> {
44+
public async registerIdentity(params: { did: string; options: SyncIdentityOptions }): Promise<void> {
4545
await this._syncEngine.registerIdentity(params);
4646
}
4747

48+
public sync(direction?: 'push' | 'pull'): Promise<void> {
49+
return this._syncEngine.sync(direction);
50+
}
51+
4852
public startSync(params: { interval: string; }): Promise<void> {
4953
return this._syncEngine.startSync(params);
5054
}

0 commit comments

Comments
 (0)