Skip to content

Commit

Permalink
Refactor IdentityApi to be scoped to the agent as a tenant. (#911)
Browse files Browse the repository at this point in the history
- simplify `IdentityApi` to be agent-focused and storing both the DID and IdentityMetadata under the agent's tenant.
- Remove the need for `manage` in IdentityApi.
- DIDs that are created/imported are automatically pre-populated in the resolution cache.
- Update vulnerable packages

The DidApi remains unchanged and a default `did.create()` will store the DID document as the tenant of the newly created DID.
  • Loading branch information
LiranCohen authored Sep 26, 2024
1 parent e2df13a commit c92159c
Show file tree
Hide file tree
Showing 22 changed files with 255 additions and 371 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-beers-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": patch
---

Update usage of new IdentityApi behavior internally.
8 changes: 8 additions & 0 deletions .changeset/itchy-mayflies-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@web5/identity-agent": minor
"@web5/proxy-agent": minor
"@web5/user-agent": minor
"@web5/agent": minor
---

Simplify IdentityApi to be agent-focused and storing both the DID and IdentityMetadata under the agent's tenant.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"body-parser@<1.20.3": ">=1.20.3",
"send@<0.19.0": ">=0.19.0",
"serve-static@<1.16.0": ">=1.16.0",
"express@<4.20.0": ">=4.20.0"
"express@<4.20.0": ">=4.20.0",
"rollup@>=4.0.0 <4.22.4": ">=4.22.4"
}
}
}
15 changes: 15 additions & 0 deletions packages/agent/src/did-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ export function isDidRequest<T extends DidInterface>(
return didRequest.messageType === messageType;
}

/**
* This API is used to manage and interact with DIDs within the Web5 Agent framework.
*
* If a DWN Data Store is used, the DID information is stored under DID's own tenant by default.
* If a tenant property is passed, that tenant will be used to store the DID information.
*/
export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> extends UniversalResolver {
/**
* Holds the instance of a `Web5PlatformAgent` that represents the current execution context for
Expand Down Expand Up @@ -170,6 +176,9 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>
// Create the DID and store the generated keys in the Agent's key manager.
const bearerDid = await didMethod.create({ keyManager: this.agent.keyManager, options });

// pre-populate the resolution cache with the document and metadata
await this.cache.set(bearerDid.uri, { didDocument: bearerDid.document, didResolutionMetadata: { }, didDocumentMetadata: bearerDid.metadata });

// Persist the DID to the store, by default, unless the `store` option is set to false.
if (store ?? true) {
// Data stored in the Agent's DID store must be in PortableDid format.
Expand Down Expand Up @@ -260,6 +269,9 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>
const { uri, document, metadata } = bearerDid;
const portableDidWithoutKeys: PortableDid = { uri, document, metadata };

// pre-populate the resolution cache with the document and metadata
await this.cache.set(uri, { didDocument: document, didResolutionMetadata: { }, didDocumentMetadata: metadata });

// Store the DID in the agent's DID store.
// Unless an existing `tenant` is specified, a record that includes the DID's URI, document,
// and metadata will be stored under a new tenant controlled by the imported DID.
Expand All @@ -285,6 +297,9 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>
throw new Error('AgentDidApi: Could not delete, DID not found');
}

// delete from the cache
await this.cache.delete(didUri);

// Delete the data before deleting the associated keys.
await this._store.delete({ id: didUri, agent: this.agent, tenant });

Expand Down
87 changes: 34 additions & 53 deletions packages/agent/src/identity-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export interface IdentityCreateParams<
metadata: RequireOnly<IdentityMetadata, 'name'>;
didMethod?: TMethod;
didOptions?: DidMethodCreateOptions<TKeyManager>[TMethod];
tenant?: string;
store?: boolean;
}

Expand All @@ -35,6 +34,16 @@ export function isPortableIdentity(obj: unknown): obj is PortableIdentity {
&& isPortableDid(obj.did);
}

/**
* This API is used to manage and interact with Identities within the Web5 Agent framework.
* An Identity is a DID that is associated with metadata that describes the Identity.
* Metadata includes A name(label), and whether or not the Identity is connected (delegated to act on the behalf of another DID).
*
* A KeyManager is used to manage the cryptographic keys associated with the Identities.
*
* The `DidApi` is used internally to create, store, and manage DIDs.
* When a DWN Data Store is used, the Identity and DID information are stored under the Agent DID's tenant.
*/
export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyManager> {
/**
* Holds the instance of a `Web5PlatformAgent` that represents the current execution context for
Expand Down Expand Up @@ -71,22 +80,29 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
this._agent = agent;
}

public async create({ metadata, didMethod = 'dht', didOptions, store, tenant }:
get tenant(): string {
if (!this._agent) {
throw new Error('AgentIdentityApi: The agent must be set to perform tenant specific actions.');
}

return this._agent.agentDid.uri;
}

public async create({ metadata, didMethod = 'dht', didOptions, store }:
IdentityCreateParams<TKeyManager>
): Promise<BearerIdentity> {
// Unless an existing `tenant` is specified, a record that includes the DID's URI, document,
// and metadata will be stored under a new tenant controlled by the newly created DID.

const bearerDid = await this.agent.did.create({
method : didMethod,
options : didOptions,
tenant : this.tenant,
store,
tenant
});

// Create the BearerIdentity object.
const identity = new BearerIdentity({
did : bearerDid,
metadata : { ...metadata, uri: bearerDid.uri, tenant: tenant ?? bearerDid.uri }
metadata : { ...metadata, uri: bearerDid.uri, tenant: this.tenant }
});

// Persist the Identity to the store, by default, unless the `store` option is set to false.
Expand All @@ -104,12 +120,10 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
return identity;
}

public async export({ didUri, tenant }: {
public async export({ didUri }: {
didUri: string;
tenant?: string;
}): Promise<PortableIdentity> {
// Attempt to retrieve the Identity from the Agent's Identity store.
const bearerIdentity = await this.get({ didUri, tenant });
const bearerIdentity = await this.get({ didUri });

if (!bearerIdentity) {
throw new Error(`AgentIdentityApi: Failed to export due to Identity not found: ${didUri}`);
Expand All @@ -122,12 +136,10 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
return portableIdentity;
}

public async get({ didUri, tenant }: {
public async get({ didUri }: {
didUri: string;
tenant?: string;
}): Promise<BearerIdentity | undefined> {
// Attempt to retrieve the Identity from the Agent's Identity store.
const storedIdentity = await this._store.get({ id: didUri, agent: this.agent, tenant, useCache: true });
const storedIdentity = await this._store.get({ id: didUri, agent: this.agent, useCache: true });

// If the Identity is not found in the store, return undefined.
if (!storedIdentity) return undefined;
Expand All @@ -150,6 +162,10 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
public async import({ portableIdentity }: {
portableIdentity: PortableIdentity;
}): Promise<BearerIdentity> {

// set the tenant of the portable identity to the agent's tenant
portableIdentity.metadata.tenant = this.tenant;

// Import the PortableDid to the Agent's DID store.
const storedDid = await this.agent.did.import({
portableDid : portableIdentity.portableDid,
Expand Down Expand Up @@ -183,56 +199,21 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
// Retrieve the list of Identities from the Agent's Identity store.
const storedIdentities = await this._store.list({ agent: this.agent, tenant });

const identities = await Promise.all(
storedIdentities.map(async metadata => {
return this.get({ didUri: metadata.uri, tenant: metadata.tenant });
})
);
const identities = await Promise.all(storedIdentities.map(metadata => this.get({ didUri: metadata.uri })));

return identities.filter(identity => typeof identity !== 'undefined') as BearerIdentity[];
}

public async manage({ portableIdentity }: {
portableIdentity: PortableIdentity;
}): Promise<BearerIdentity> {
// Retrieve the DID using the `tenant` stored in the given Identity's metadata.
const storedDid = await this.agent.did.get({
didUri : portableIdentity.metadata.uri,
tenant : portableIdentity.metadata.tenant
});

// Verify the DID is present in the DID store.
if (!storedDid) {
throw new Error(`AgentIdentityApi: Failed to manage Identity: ${portableIdentity.metadata.uri}`);
}

// Create the BearerIdentity object.
const identity = new BearerIdentity({ did: storedDid, metadata: portableIdentity.metadata });

// Store the Identity metadata in the Agent's Identity store.
await this._store.set({
id : identity.did.uri,
data : identity.metadata,
agent : this.agent,
preventDuplicates : true,
useCache : true
});

return identity;
}

public async delete({ didUri, tenant }:{
public async delete({ didUri }:{
didUri: string;
tenant?: string;
}): Promise<void> {
// Attempt to retrieve the Identity from the Agent's Identity store.
const storedIdentity = await this._store.get({ id: didUri, agent: this.agent, tenant, useCache: true });
const storedIdentity = await this._store.get({ id: didUri, agent: this.agent, useCache: true });
if (!storedIdentity) {
throw new Error(`AgentIdentityApi: Failed to purge due to Identity not found: ${didUri}`);
}

// Delete the Identity from the Agent's Identity store.
await this._store.delete({ id: didUri, agent: this.agent, tenant });
await this._store.delete({ id: didUri, agent: this.agent });
}

/**
Expand Down
7 changes: 0 additions & 7 deletions packages/agent/src/test-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,6 @@ export class PlatformAgentTestHarness {
return bearerIdentity;
}

public async preloadResolverCache({ didUri, resolutionResult }: {
didUri: string;
resolutionResult: DidResolutionResult;
}): Promise<void> {
await this.didResolverCache.set(didUri, resolutionResult);
}

public static async setup({ agentClass, agentStores, testDataLocation }: {
agentClass: new (params: any) => Web5PlatformAgent<LocalKeyManager>
agentStores?: 'dwn' | 'memory';
Expand Down
20 changes: 0 additions & 20 deletions packages/agent/tests/dwn-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,15 +1082,6 @@ describe('AgentDwnApi', () => {
}
};

await testHarness.preloadResolverCache({
didUri : testPortableIdentity.portableDid.uri,
resolutionResult : {
didDocument : testPortableIdentity.portableDid.document,
didDocumentMetadata : testPortableIdentity.portableDid.metadata,
didResolutionMetadata : {}
}
});

alice = await testHarness.agent.identity.import({
portableIdentity: testPortableIdentity
});
Expand Down Expand Up @@ -1840,17 +1831,6 @@ describe('AgentDwnApi', () => {
store : false
});

// Since the DID DHT document wasn't published, add the DID DHT document to the resolver
// cache so that DID resolution will succeed during the dereferencing operation.
await testHarness.preloadResolverCache({
didUri : identity.did.uri,
resolutionResult : {
didDocument : identity.did.document,
didDocumentMetadata : identity.did.metadata,
didResolutionMetadata : {}
}
});

try {
await testHarness.agent.dwn.sendRequest({
author : identity.did.uri,
Expand Down
Loading

0 comments on commit c92159c

Please sign in to comment.