Skip to content

Commit 0862ffc

Browse files
authored
Adding PermissionsApi to agent and permissions() api to Web5.dwn with the ability to select delegate grants for Web5.dwn (#824)
* 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 #### `PermissionsApi` 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. #### `dwn.connected` A Private API usedin a connected state to find and cache the correct grants to use for the request. #### `dwn.permissions` 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: #### `PermissionRequest` 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. #### `PermissionGrant` 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. #### `GrantRevocation` 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. * package lock update after rebase * add additional comments around the signer for the permissions DWN api
1 parent ac08f55 commit 0862ffc

39 files changed

+13211
-6024
lines changed

.changeset/fuzzy-baboons-own.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@web5/api": minor
3+
---
4+
5+
Support impersonation using delegated grants for DWN record operations using WalletConnect

.changeset/gold-lamps-obey.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@web5/api": patch
3+
---
4+
5+
Introduce a `grants` API for `Web5.dwn`

.changeset/polite-days-wash.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@web5/identity-agent": patch
3+
"@web5/proxy-agent": patch
4+
"@web5/user-agent": patch
5+
"@web5/agent": patch
6+
---
7+
8+
Introduce a `PermissionsApi` for Web5Agents

.changeset/silly-poets-sing.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+
Simplify support for Permission Grant logic within agent.

packages/agent/src/dwn-api.ts

-108
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Readable } from '@web5/common';
22

33
import {
44
Cid,
5-
DataEncodedRecordsWriteMessage,
65
DataStoreLevel,
76
Dwn,
87
DwnConfig,
@@ -11,10 +10,6 @@ import {
1110
GenericMessage,
1211
Message,
1312
MessageStoreLevel,
14-
PermissionGrant,
15-
PermissionScope,
16-
PermissionsProtocol,
17-
RecordsWrite,
1813
ResumableTaskStoreLevel
1914
} from '@tbd54566975/dwn-sdk-js';
2015

@@ -39,7 +34,6 @@ import type {
3934

4035
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
4136
import { blobToIsomorphicNodeReadable, getDwnServiceEndpointUrls, isRecordsWrite, webReadableToIsomorphicNodeReadable } from './utils.js';
42-
import { DwnPermissionsUtil } from './dwn-permissions-util.js';
4337

4438
export type DwnMessageWithBlob<T extends DwnInterface> = {
4539
message: DwnMessage[T];
@@ -450,106 +444,4 @@ export class AgentDwnApi {
450444

451445
return dwnMessageWithBlob;
452446
}
453-
454-
/**
455-
* NOTE EVERYTHING BELOW THIS LINE IS TEMPORARY
456-
* TODO: Create a `grants` API to handle creating permission requests, grants and revocations
457-
* */
458-
459-
/**
460-
* Performs a RecordsQuery for permission grants that match the given parameters.
461-
*/
462-
public async fetchGrants({ author, target, grantee, grantor }: {
463-
/** author of the query message, defaults to grantee */
464-
author?: string,
465-
/** target of the query message, defaults to author */
466-
target?: string,
467-
grantor: string,
468-
grantee: string
469-
}): Promise<DataEncodedRecordsWriteMessage[]> {
470-
// if no author is provided, use the grantee's DID
471-
author ??= grantee;
472-
// if no target is explicitly provided, use the author
473-
target ??= author;
474-
475-
const { reply: grantsReply } = await this.processRequest({
476-
author,
477-
target,
478-
messageType : DwnInterface.RecordsQuery,
479-
messageParams : {
480-
filter: {
481-
author : grantor, // the author of the grant would be the grantor and the logical author of the message
482-
recipient : grantee, // the recipient of the grant would be the grantee
483-
...DwnPermissionsUtil.permissionsProtocolParams('grant')
484-
}
485-
}
486-
});
487-
488-
if (grantsReply.status.code !== 200) {
489-
throw new Error(`AgentDwnApi: Failed to fetch grants: ${grantsReply.status.detail}`);
490-
}
491-
492-
return grantsReply.entries! as DataEncodedRecordsWriteMessage[];
493-
};
494-
495-
/**
496-
* Check whether a grant is revoked by reading the revocation record for a given grant recordId.
497-
*/
498-
public async isGrantRevoked(author:string, target: string, grantRecordId: string): Promise<boolean> {
499-
const { reply: revocationReply } = await this.processRequest({
500-
author,
501-
target,
502-
messageType : DwnInterface.RecordsRead,
503-
messageParams : {
504-
filter: {
505-
parentId: grantRecordId,
506-
...DwnPermissionsUtil.permissionsProtocolParams('revoke')
507-
}
508-
}
509-
});
510-
511-
if (revocationReply.status.code === 404) {
512-
// no revocation found, the grant is not revoked
513-
return false;
514-
} else if (revocationReply.status.code === 200) {
515-
// a revocation was found, the grant is revoked
516-
return true;
517-
}
518-
519-
throw new Error(`AgentDwnApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`);
520-
}
521-
522-
public async createGrant({ grantedFrom, dateExpires, grantedTo, scope, delegated }:{
523-
dateExpires: string,
524-
grantedFrom: string,
525-
grantedTo: string,
526-
scope: PermissionScope,
527-
delegated?: boolean
528-
}): Promise<{
529-
recordsWrite: RecordsWrite,
530-
dataEncodedMessage: DataEncodedRecordsWriteMessage,
531-
permissionGrantBytes: Uint8Array
532-
}> {
533-
return await PermissionsProtocol.createGrant({
534-
signer: await this.getSigner(grantedFrom),
535-
grantedTo,
536-
dateExpires,
537-
scope,
538-
delegated
539-
});
540-
}
541-
542-
public async createRevocation({ grant, author }:{
543-
author: string,
544-
grant: PermissionGrant
545-
}): Promise<{
546-
recordsWrite: RecordsWrite,
547-
dataEncodedMessage: DataEncodedRecordsWriteMessage,
548-
permissionRevocationBytes: Uint8Array
549-
}> {
550-
return await PermissionsProtocol.createRevocation({
551-
signer: await this.getSigner(author),
552-
grant,
553-
});
554-
}
555447
}

packages/agent/src/dwn-permissions-util.ts

-116
This file was deleted.

packages/agent/src/identity-api.ts

+19
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,23 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
235235
// Delete the Identity from the Agent's Identity store.
236236
await this._store.delete({ id: didUri, agent: this.agent, tenant });
237237
}
238+
239+
/**
240+
* Returns the connected Identity, if one is available.
241+
*
242+
* Accepts optional `connectedDid` parameter to filter the a specific connected identity,
243+
* if none is provided the first connected identity is returned.
244+
*/
245+
public async connectedIdentity({ connectedDid }:{ connectedDid?: string } = {}): Promise<BearerIdentity | undefined> {
246+
const identities = await this.list();
247+
if (identities.length < 1) {
248+
return undefined;
249+
}
250+
251+
// If a specific connected DID is provided, return the first identity that matches it.
252+
// Otherwise, return the first connected identity.
253+
return connectedDid ?
254+
identities.find(identity => identity.metadata.connectedDid === connectedDid) :
255+
identities.find(identity => identity.metadata.connectedDid !== undefined);
256+
}
238257
}

packages/agent/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ export * from './types/dwn.js';
33
export type * from './types/identity.js';
44
export type * from './types/identity-vault.js';
55
export type * from './types/key-manager.js';
6+
export type * from './types/permissions.js';
67
export type * from './types/sync.js';
78
export type * from './types/vc.js';
89

910
export * from './bearer-identity.js';
1011
export * from './crypto-api.js';
1112
export * from './did-api.js';
1213
export * from './dwn-api.js';
13-
export * from './dwn-permissions-util.js';
1414
export * from './dwn-registrar.js';
1515
export * from './hd-identity-vault.js';
1616
export * from './identity-api.js';
1717
export * from './local-key-manager.js';
18+
export * from './permissions-api.js';
1819
export * from './rpc-client.js';
1920
export * from './store-data.js';
2021
export * from './store-did.js';

0 commit comments

Comments
 (0)