diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts index 1baaa030e86f81641442837db0e45697a0255f98..fc913098a4c9fa7b303b7b9f22c90c8353862ede 100644 --- a/apps/shared/src/events/didEvents.ts +++ b/apps/shared/src/events/didEvents.ts @@ -42,3 +42,28 @@ export class EventDidsRegisterIndyFromSeed extends BaseEvent<Array<string>> { ); } } + +export type DidConfiguration = { + entries: Array<{ did: string; jwt: string }>; +}; +export type EventDidsDidConfigurationInput = BaseEventInput<{ + domain: string; + expiryTime: number; +}>; +export class EventDidsDidConfiguration extends BaseEvent<DidConfiguration> { + public static token = 'dids.didConfiguration'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventDidsDidConfiguration) { + return new EventDidsDidConfiguration( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts index 688d68862c41553b2e9d83fc60d16aad11a741a1..2556483468aee2e342aa404e2de312904dff46c2 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts @@ -1,6 +1,8 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { + EventDidsDidConfiguration, + EventDidsDidConfigurationInput, EventDidsRegisterIndyFromSeed, EventDidsRegisterIndyFromSeedInput, EventDidsResolve, @@ -21,6 +23,14 @@ export class DidsController { ); } + @MessagePattern(EventDidsDidConfiguration.token) + public async getDidConfiguration(options: EventDidsDidConfigurationInput) { + return new EventDidsDidConfiguration( + await this.didsService.getDidConfiguration(options), + options.tenantId, + ); + } + @MessagePattern(EventDidsResolve.token) public async resolve(options: EventDidsResolveInput) { return new EventDidsResolve( diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts index 76cd0a8b0a8f1b0ae1f623cc704502774485032c..aeb751b8b2cc00d2cd1b15197340261e725b7bb8 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -7,10 +7,17 @@ import type { EventDidsRegisterIndyFromSeed, EventDidsRegisterIndyFromSeedInput, EventDidsResolve, + DidConfiguration, + EventDidsDidConfiguration, + EventDidsDidConfigurationInput, EventDidsResolveInput, } from '@ocm/shared'; import { + JsonEncoder, + JwaSignatureAlgorithm, + JwsService, + getKeyFromVerificationMethod, DidDocumentService, Hasher, KeyType, @@ -51,6 +58,61 @@ export class DidsService { }); } + public async getDidConfiguration({ + domain, + expiryTime, + tenantId, + }: EventDidsDidConfigurationInput): Promise< + EventDidsDidConfiguration['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + const indyDids = t.dids.getCreatedDids({method: 'indy'}); + const sovDids = t.dids.getCreatedDids({method: 'sov'}); + const webDids = t.dids.getCreatedDids({method: 'web'}); + const dids = (await Promise.all([indyDids, sovDids, webDids])).flatMap((d) => d) + + const jwtEntries: DidConfiguration['entries'] = []; + const jwsService = t.dependencyManager.resolve(JwsService); + + for (const { did, didDocument } of dids) { + const payload = { + iss: did, + exp: expiryTime, + domain, + }; + + const encodedPayload = TypedArrayEncoder.fromString( + JsonEncoder.toString(payload), + ); + + if ( + !didDocument || + !didDocument.verificationMethod || + !didDocument.verificationMethod[0] + ) { + continue; + } + + const vm = didDocument.verificationMethod[0]; + + const key = getKeyFromVerificationMethod(vm); + + const jws = await jwsService.createJwsCompact(t.context, { + key, + payload: encodedPayload, + protectedHeaderOptions: { + alg: JwaSignatureAlgorithm.EdDSA, + kid: did, + }, + }); + + jwtEntries.push({ did, jwt: jws }); + } + + return { entries: jwtEntries }; + }); + } + public async registerDidIndyFromSeed({ tenantId, seed, diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts index 2cbb580fd7142d239214c5338c9b0ff33ef5a63b..17ab54593bef7e624281ed90826729979b33c896 100644 --- a/apps/ssi-abstraction/test/dids.e2e-spec.ts +++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts @@ -1,13 +1,18 @@ import type { INestApplication } from '@nestjs/common'; import type { ClientProxy } from '@nestjs/microservices'; import type { + EventDidsDidConfigurationInput, EventDidsRegisterIndyFromSeedInput, EventDidsResolveInput, } from '@ocm/shared'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; -import { EventDidsRegisterIndyFromSeed, EventDidsResolve } from '@ocm/shared'; +import { + EventDidsDidConfiguration, + EventDidsRegisterIndyFromSeed, + EventDidsResolve, +} from '@ocm/shared'; import { randomBytes } from 'crypto'; import { firstValueFrom } from 'rxjs'; @@ -78,6 +83,29 @@ describe('Dids', () => { ).toBeTruthy(); }); + it(EventDidsDidConfiguration.token, async () => { + const response$ = client.send< + EventDidsDidConfiguration, + EventDidsDidConfigurationInput + >(EventDidsDidConfiguration.token, { + domain: 'https://example.org', + expiryTime: new Date().getTime() / 1000 + 3600, + tenantId, + }); + + const response = await firstValueFrom(response$); + const eventInstance = EventDidsDidConfiguration.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + entries: expect.arrayContaining([ + expect.objectContaining({ + did: 'did:indy:bcovrin:test:9MMeff63VnCpogD2FWfKnJ', + jwt: expect.any(String), + }), + ]), + }); + }); + it(EventDidsResolve.token, async () => { const response$ = client.send<EventDidsResolve, EventDidsResolveInput>( EventDidsResolve.token,