diff --git a/apps/shared/src/events/connectionEvents.ts b/apps/shared/src/events/connectionEvents.ts index 22177f11d50f62c43265e5f978330e6e92c16d3f..1ec09a51c4624dca7b56ce0866b7716e0ad1de4c 100644 --- a/apps/shared/src/events/connectionEvents.ts +++ b/apps/shared/src/events/connectionEvents.ts @@ -1,23 +1,7 @@ -import { - ConnectionRecord, - DidDocument, - JsonTransformer, -} from '@aries-framework/core'; +import { ConnectionRecord, JsonTransformer } from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; -export class EventInfoPublicDid extends BaseEvent<DidDocument> { - public static token = 'didcomm.info.publicDid'; - - public get instance() { - return JsonTransformer.fromJSON(this.data, DidDocument); - } - - public static fromEvent(e: EventInfoPublicDid) { - return new EventInfoPublicDid(e.data, e.id, e.type, e.timestamp); - } -} - export class EventDidcommConnectionsGetAll extends BaseEvent< Array<ConnectionRecord> > { diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6a2d68003d3e031dd5a58390e815df73754c6de --- /dev/null +++ b/apps/shared/src/events/didEvents.ts @@ -0,0 +1,32 @@ +import { DidDocument, JsonTransformer } from '@aries-framework/core'; + +import { BaseEvent } from './baseEvents.js'; + +/** + * + * @todo: this should be removed as it is a weird event that should not be needed + * + */ +export class EventInfoPublicDid extends BaseEvent<DidDocument> { + public static token = 'dids.publicDid'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, DidDocument); + } + + public static fromEvent(e: EventInfoPublicDid) { + return new EventInfoPublicDid(e.data, e.id, e.type, e.timestamp); + } +} + +export class EventDidsResolve extends BaseEvent<DidDocument> { + public static token = 'dids.resolve'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, DidDocument); + } + + public static fromEvent(e: EventDidsResolve) { + return new EventDidsResolve(e.data, e.id, e.type, e.timestamp); + } +} diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index bde9dc3bf812398014890ba91428b1a4a9219a0d..8a7c5425cf8c0c571deb988dc73b0c80275dedda 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -5,3 +5,4 @@ export * from './logging/logger.js'; export * from './logging/logAxiosError.js'; export * from './events/connectionEvents.js'; +export * from './events/didEvents.js'; diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 10bcad88fa13ee5fd2ecadf0af64baac1a9ba33f..ef1ee73eff8a4a2e5e39de3aeec876dd891dbfe1 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -12,6 +12,8 @@ import { CredentialsModule, DidsModule, HttpOutboundTransport, + JwkDidRegistrar, + JwkDidResolver, KeyDidRegistrar, KeyDidResolver, KeyType, @@ -19,6 +21,7 @@ import { PeerDidRegistrar, PeerDidResolver, TypedArrayEncoder, + WebDidResolver, } from '@aries-framework/core'; import { IndyVdrAnonCredsRegistry, @@ -107,8 +110,10 @@ export class AgentService implements OnApplicationShutdown { new IndyVdrSovDidResolver(), new PeerDidResolver(), new KeyDidResolver(), + new JwkDidResolver(), + new WebDidResolver(), ], - registrars: [new PeerDidRegistrar(), new KeyDidRegistrar()], + registrars: [new PeerDidRegistrar(), new KeyDidRegistrar(), new JwkDidRegistrar()], }), askar: new AskarModule({ ariesAskar }), diff --git a/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts b/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6507d0f4c8bbbfa9b65c30fd222a8b341757905 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts @@ -0,0 +1,36 @@ +import { DidDocument } from '@aries-framework/core'; +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { DidsController } from '../dids.controller.js'; +import { DidsService } from '../dids.service.js'; + +describe('DidsController', () => { + let didsController: DidsController; + let didsService: DidsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [DidsController], + providers: [DidsService], + }).compile(); + + didsService = moduleRef.get(DidsService); + didsController = moduleRef.get(DidsController); + }); + + describe('resolve', () => { + it('should resolve a basic did', async () => { + const result = new DidDocument({ id: 'did:key:foo' }); + jest.spyOn(didsService, 'resolve').mockResolvedValue(result); + + const event = await didsController.resolve({ + did: 'did:key:foo', + }); + + expect(event.data).toStrictEqual(result); + }); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..70074c7dab0d350bf381f0dffc50bfd1e34f5694 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts @@ -0,0 +1,15 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { EventDidsResolve } from '@ocm/shared'; + +import { DidsService } from './dids.service.js'; + +@Controller('dids') +export class DidsController { + public constructor(private didsService: DidsService) {} + + @MessagePattern('dids.resolve') + public async resolve({ did }: { did: string }) { + return new EventDidsResolve(await this.didsService.resolve(did)); + } +} diff --git a/apps/ssi-abstraction/src/agent/dids/dids.module.ts b/apps/ssi-abstraction/src/agent/dids/dids.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4428a9a7ca832ede5039c5ca52ada9e8750fe4a --- /dev/null +++ b/apps/ssi-abstraction/src/agent/dids/dids.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { DidsController } from './dids.controller.js'; +import { DidsService } from './dids.service.js'; + +@Module({ + imports: [AgentModule], + providers: [DidsService], + controllers: [DidsController], +}) +export class DidsModule {} diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1c4e8676dedd3d87e0b0726e9afc6e8865cba18 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -0,0 +1,31 @@ +import type { AppAgent } from '../agent.service.js'; + +import { Injectable } from '@nestjs/common'; + +import { AgentService } from '../agent.service.js'; + +@Injectable() +export class DidsService { + public agent: AppAgent; + + public constructor(agentService: AgentService) { + this.agent = agentService.agent; + } + + public async resolve(did: string) { + const { + didDocument, + didResolutionMetadata: { message, error }, + } = await this.agent.dids.resolve(did); + + if (!didDocument) { + throw new Error( + `Could not resolve did: '${did}'. Error: ${error ?? 'None'} Message: ${ + message ?? 'None' + }`, + ); + } + + return didDocument; + } +} diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e276f368de9ff9a215e4ddef581ac94bfc32faec --- /dev/null +++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts @@ -0,0 +1,97 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { Observable } from 'rxjs'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { + EventDidsResolve, +} from '@ocm/shared'; +import { firstValueFrom } from 'rxjs'; + +import { AgentModule } from '../src/agent/agent.module.js'; +import { DidsModule } from '../src/agent/dids/dids.module.js'; +import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; + +describe('Dids', () => { + const TOKEN = 'DIDS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3005), + AgentModule, + DidsModule, + ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]), + ], + }).compile(); + + app = moduleRef.createNestApplication(); + + app.connectMicroservice({ transport: Transport.NATS }); + + await app.startAllMicroservices(); + await app.init(); + + client = app.get(TOKEN); + await client.connect(); + }); + + afterAll(async () => { + await app.close(); + client.close(); + }); + + it(EventDidsResolve.token, async () => { + const response$: Observable<EventDidsResolve> = client.send( + EventDidsResolve.token, + { + did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + }, + ); + + const response = await firstValueFrom(response$); + const eventInstance = EventDidsResolve.fromEvent(response); + + expect(eventInstance.instance.toJSON()).toStrictEqual({ + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + verificationMethod: [ + { + id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + type: 'Ed25519VerificationKey2018', + controller: + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + publicKeyBase58: 'B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u', + }, + ], + authentication: [ + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + ], + assertionMethod: [ + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + ], + keyAgreement: [ + { + id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc', + type: 'X25519KeyAgreementKey2019', + controller: + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + publicKeyBase58: 'JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr', + }, + ], + capabilityInvocation: [ + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + ], + capabilityDelegation: [ + 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + ], + }); + }); +});