Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding PermissionsApi to agent and permissions() api to Web5.dwn with the ability to select delegate grants for Web5.dwn #824

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-baboons-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": minor
---

Support impersonation using delegated grants for DWN record operations using WalletConnect
5 changes: 5 additions & 0 deletions .changeset/gold-lamps-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": patch
---

Introduce a `grants` API for `Web5.dwn`
8 changes: 8 additions & 0 deletions .changeset/polite-days-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@web5/identity-agent": patch
"@web5/proxy-agent": patch
"@web5/user-agent": patch
"@web5/agent": patch
---

Introduce a `PermissionsApi` for Web5Agents
8 changes: 8 additions & 0 deletions .changeset/silly-poets-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@web5/agent": minor
"@web5/identity-agent": minor
"@web5/proxy-agent": minor
"@web5/user-agent": minor
---

Simplify support for Permission Grant logic within agent.
108 changes: 0 additions & 108 deletions packages/agent/src/dwn-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Readable } from '@web5/common';

import {
Cid,
DataEncodedRecordsWriteMessage,
DataStoreLevel,
Dwn,
DwnConfig,
Expand All @@ -11,10 +10,6 @@ import {
GenericMessage,
Message,
MessageStoreLevel,
PermissionGrant,
PermissionScope,
PermissionsProtocol,
RecordsWrite,
ResumableTaskStoreLevel
} from '@tbd54566975/dwn-sdk-js';

Expand All @@ -39,7 +34,6 @@ import type {

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

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

return dwnMessageWithBlob;
}

/**
* NOTE EVERYTHING BELOW THIS LINE IS TEMPORARY
* TODO: Create a `grants` API to handle creating permission requests, grants and revocations
* */

/**
* Performs a RecordsQuery for permission grants that match the given parameters.
*/
public async fetchGrants({ author, target, grantee, grantor }: {
/** author of the query message, defaults to grantee */
author?: string,
/** target of the query message, defaults to author */
target?: string,
grantor: string,
grantee: string
}): Promise<DataEncodedRecordsWriteMessage[]> {
// if no author is provided, use the grantee's DID
author ??= grantee;
// if no target is explicitly provided, use the author
target ??= author;

const { reply: grantsReply } = await this.processRequest({
author,
target,
messageType : DwnInterface.RecordsQuery,
messageParams : {
filter: {
author : grantor, // the author of the grant would be the grantor and the logical author of the message
recipient : grantee, // the recipient of the grant would be the grantee
...DwnPermissionsUtil.permissionsProtocolParams('grant')
}
}
});

if (grantsReply.status.code !== 200) {
throw new Error(`AgentDwnApi: Failed to fetch grants: ${grantsReply.status.detail}`);
}

return grantsReply.entries! as DataEncodedRecordsWriteMessage[];
};

/**
* Check whether a grant is revoked by reading the revocation record for a given grant recordId.
*/
public async isGrantRevoked(author:string, target: string, grantRecordId: string): Promise<boolean> {
const { reply: revocationReply } = await this.processRequest({
author,
target,
messageType : DwnInterface.RecordsRead,
messageParams : {
filter: {
parentId: grantRecordId,
...DwnPermissionsUtil.permissionsProtocolParams('revoke')
}
}
});

if (revocationReply.status.code === 404) {
// no revocation found, the grant is not revoked
return false;
} else if (revocationReply.status.code === 200) {
// a revocation was found, the grant is revoked
return true;
}

throw new Error(`AgentDwnApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`);
}

public async createGrant({ grantedFrom, dateExpires, grantedTo, scope, delegated }:{
dateExpires: string,
grantedFrom: string,
grantedTo: string,
scope: PermissionScope,
delegated?: boolean
}): Promise<{
recordsWrite: RecordsWrite,
dataEncodedMessage: DataEncodedRecordsWriteMessage,
permissionGrantBytes: Uint8Array
}> {
return await PermissionsProtocol.createGrant({
signer: await this.getSigner(grantedFrom),
grantedTo,
dateExpires,
scope,
delegated
});
}

public async createRevocation({ grant, author }:{
author: string,
grant: PermissionGrant
}): Promise<{
recordsWrite: RecordsWrite,
dataEncodedMessage: DataEncodedRecordsWriteMessage,
permissionRevocationBytes: Uint8Array
}> {
return await PermissionsProtocol.createRevocation({
signer: await this.getSigner(author),
grant,
});
}
}
116 changes: 0 additions & 116 deletions packages/agent/src/dwn-permissions-util.ts

This file was deleted.

19 changes: 19 additions & 0 deletions packages/agent/src/identity-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,23 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
// Delete the Identity from the Agent's Identity store.
await this._store.delete({ id: didUri, agent: this.agent, tenant });
}

/**
* Returns the connected Identity, if one is available.
*
* Accepts optional `connectedDid` parameter to filter the a specific connected identity,
* if none is provided the first connected identity is returned.
*/
public async connectedIdentity({ connectedDid }:{ connectedDid?: string } = {}): Promise<BearerIdentity | undefined> {
const identities = await this.list();
if (identities.length < 1) {
return undefined;
}

// If a specific connected DID is provided, return the first identity that matches it.
// Otherwise, return the first connected identity.
return connectedDid ?
identities.find(identity => identity.metadata.connectedDid === connectedDid) :
identities.find(identity => identity.metadata.connectedDid !== undefined);
}
}
3 changes: 2 additions & 1 deletion packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ export * from './types/dwn.js';
export type * from './types/identity.js';
export type * from './types/identity-vault.js';
export type * from './types/key-manager.js';
export type * from './types/permissions.js';
export type * from './types/sync.js';
export type * from './types/vc.js';

export * from './bearer-identity.js';
export * from './crypto-api.js';
export * from './did-api.js';
export * from './dwn-api.js';
export * from './dwn-permissions-util.js';
export * from './dwn-registrar.js';
export * from './hd-identity-vault.js';
export * from './identity-api.js';
export * from './local-key-manager.js';
export * from './permissions-api.js';
export * from './rpc-client.js';
export * from './store-data.js';
export * from './store-did.js';
Expand Down
Loading
Loading