Skip to content

Commit

Permalink
get and set dwn endpoints added to identity api
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Oct 16, 2024
1 parent bd1cb00 commit 91d99bc
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 1 deletion.
41 changes: 41 additions & 0 deletions packages/agent/src/identity-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import type { IdentityMetadata, PortableIdentity } from './types/identity.js';
import { BearerIdentity } from './bearer-identity.js';
import { isPortableDid } from './prototyping/dids/utils.js';
import { InMemoryIdentityStore } from './store-identity.js';
import { getDwnServiceEndpointUrls } from './utils.js';
import { canonicalize } from '@web5/crypto';
import { PortableDid } from '@web5/dids';

export interface IdentityApiParams<TKeyManager extends AgentKeyManager> {
agent?: Web5PlatformAgent<TKeyManager>;
Expand Down Expand Up @@ -216,6 +219,44 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
await this._store.delete({ id: didUri, agent: this.agent });
}

public getDwnEndpoints({ didUri }: { didUri: string; }): Promise<string[]> {
return getDwnServiceEndpointUrls(didUri, this.agent.did);
}

public async setDwnEndpoints({ didUri, endpoints }: { didUri: string; endpoints: string[] }): Promise<void> {
const bearerDid = await this.agent.did.get({ didUri });
if (!bearerDid) {
throw new Error(`AgentIdentityApi: Failed to set DWN endpoints due to DID not found: ${didUri}`);
}

const portableDid = JSON.parse(JSON.stringify(await bearerDid.export())) as PortableDid;
const dwnService = portableDid.document.service?.find(service => service.id.endsWith('dwn'));
if (dwnService) {
// Update the existing DWN Service with the provided endpoints
dwnService.serviceEndpoint = endpoints;
} else {

// create a DWN Service to add to the DID document
const newDwnService = {
id : 'dwn',
type : 'DecentralizedWebNode',
serviceEndpoint : endpoints,
enc : '#enc',
sig : '#sig'
};

// if no other services exist, create a new array with the DWN service
if (!portableDid.document.service) {
portableDid.document.service = [newDwnService];
} else {
// otherwise, push the new DWN service to the existing services
portableDid.document.service.push(newDwnService);
}
}

await this.agent.did.update({ portableDid, tenant: this.agent.agentDid.uri });
}

/**
* Returns the connected Identity, if one is available.
*
Expand Down
5 changes: 4 additions & 1 deletion packages/agent/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PaginationCursor, RecordsDeleteMessage, RecordsWriteMessage } from '@tb
import { Readable } from '@web5/common';
import { utils as didUtils } from '@web5/dids';
import { ReadableWebToNodeStream } from 'readable-web-to-node-stream';
import { DateSort, DwnInterfaceName, DwnMethodName, Message, Records, RecordsWrite } from '@tbd54566975/dwn-sdk-js';
import { DateSort, DwnInterfaceName, DwnMethodName, Message, RecordsWrite } from '@tbd54566975/dwn-sdk-js';

export function blobToIsomorphicNodeReadable(blob: Blob): Readable {
return webReadableToIsomorphicNodeReadable(blob.stream() as ReadableStream<any>);
Expand Down Expand Up @@ -38,6 +38,9 @@ export async function getDwnServiceEndpointUrls(didUri: string, dereferencer: Di
return [];
}

export async function setDwnServiceEndpointUrls(didUri: string, serviceEndpointUrls: string[], dereferencer: DidUrlDereferencer) {
}

Check warning on line 42 in packages/agent/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/agent/src/utils.ts#L42

Added line #L42 was not covered by tests

export function getRecordAuthor(record: RecordsWriteMessage | RecordsDeleteMessage): string | undefined {
return Message.getAuthor(record);
}
Expand Down
155 changes: 155 additions & 0 deletions packages/agent/tests/identity-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TestAgent } from './utils/test-agent.js';
import { AgentIdentityApi } from '../src/identity-api.js';
import { PlatformAgentTestHarness } from '../src/test-harness.js';
import { PortableIdentity } from '../src/index.js';
import { BearerDid, PortableDid } from '@web5/dids';

describe('AgentIdentityApi', () => {

Expand Down Expand Up @@ -220,6 +221,160 @@ describe('AgentIdentityApi', () => {
});
});

describe('setDwnEndpoints()', () => {
it('should set the DWN endpoints for a DID', async () => {
const initialEndpoints = ['https://example.com/dwn'];
// create a new identity
const identity = await testHarness.agent.identity.create({
didMethod : 'dht',
didOptions : {
services: [
{
id : 'dwn',
type : 'DecentralizedWebNode',
serviceEndpoint : initialEndpoints,
enc : '#enc',
sig : '#sig',
}
],
verificationMethods: [
{
algorithm : 'Ed25519',
id : 'sig',
purposes : ['assertionMethod', 'authentication']
},
{
algorithm : 'secp256k1',
id : 'enc',
purposes : ['keyAgreement']
}
]
},
metadata: { name: 'Alice' },
});

// control: get the service endpoints of the created DID
const initialDwnEndpoints = await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect(initialDwnEndpoints).to.deep.equal(initialEndpoints);

// set new endpoints
const newEndpoints = ['https://example.com/dwn2'];
await testHarness.agent.identity.setDwnEndpoints({ didUri: identity.did.uri, endpoints: newEndpoints });

// get the service endpoints of the updated DID
const updatedDwnEndpoints = await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect(updatedDwnEndpoints).to.deep.equal(newEndpoints);
});

it('should throw an error if the service endpoints remain unchanged', async () => {
const initialEndpoints = ['https://example.com/dwn'];
// create a new identity
const identity = await testHarness.agent.identity.create({
didMethod : 'dht',
didOptions : {
services: [
{
id : 'dwn',
type : 'DecentralizedWebNode',
serviceEndpoint : initialEndpoints,
enc : '#enc',
sig : '#sig',
}
],
verificationMethods: [
{
algorithm : 'Ed25519',
id : 'sig',
purposes : ['assertionMethod', 'authentication']
},
{
algorithm : 'secp256k1',
id : 'enc',
purposes : ['keyAgreement']
}
]
},
metadata: { name: 'Alice' },
});

// control: get the service endpoints of the created DID
const initialDwnEndpoints = await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect(initialDwnEndpoints).to.deep.equal(initialEndpoints);

// set the same endpoints
try {
await testHarness.agent.identity.setDwnEndpoints({ didUri: identity.did.uri, endpoints: initialEndpoints });
expect.fail('Expected an error to be thrown');
} catch (error: any) {
expect(error.message).to.include('AgentDidApi: No changes detected');
}
});

it('should throw an error if the DID is not found', async () => {
try {
await testHarness.agent.identity.setDwnEndpoints({ didUri: 'did:method:xyz123', endpoints: ['https://example.com/dwn'] });
expect.fail('Expected an error to be thrown');
} catch (error: any) {
expect(error.message).to.include('AgentIdentityApi: Failed to set DWN endpoints due to DID not found');
}
});

it('should add a DWN service if no services exist', async () => {
// create a new identity without any DWN endpoints or services
const identity = await testHarness.agent.identity.create({
didMethod : 'dht',
metadata : { name: 'Alice' },
});

// control: get the service endpoints of the created DID, should fail
try {
await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect.fail('should have thrown an error');
} catch(error: any) {
expect(error.message).to.include('Failed to dereference');
}

// set new endpoints
const newEndpoints = ['https://example.com/dwn2'];
await testHarness.agent.identity.setDwnEndpoints({ didUri: identity.did.uri, endpoints: newEndpoints });

// get the service endpoints of the updated DID
const updatedDwnEndpoints = await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect(updatedDwnEndpoints).to.deep.equal(newEndpoints);
});

it('should add a DWN service if one does not exist in the services list', async () => {
// create a new identity without a DWN service
const identity = await testHarness.agent.identity.create({
didMethod : 'dht',
didOptions : {
services: [{
id : 'some-service', // non DWN service
type : 'SomeService',
serviceEndpoint : ['https://example.com/some-service'],
}]
},
metadata: { name: 'Alice' },
});

// control: get the service endpoints of the created DID, should fail
try {
await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect.fail('should have thrown an error');
} catch(error: any) {
expect(error.message).to.include('Failed to dereference');
}

// set new endpoints
const newEndpoints = ['https://example.com/dwn2'];
await testHarness.agent.identity.setDwnEndpoints({ didUri: identity.did.uri, endpoints: newEndpoints });

// get the service endpoints of the updated DID
const updatedDwnEndpoints = await testHarness.agent.identity.getDwnEndpoints({ didUri: identity.did.uri });
expect(updatedDwnEndpoints).to.deep.equal(newEndpoints);
});
});

describe('connectedIdentity', () => {
it('returns a connected Identity', async () => {
// create multiple identities, some that are connected, and some that are not
Expand Down

0 comments on commit 91d99bc

Please sign in to comment.