From e865b95f2affb5c914a33cbdf617ce6eac09a03e Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <berend@animo.id> Date: Mon, 4 Dec 2023 12:58:32 +0100 Subject: [PATCH] fix(ssi): do not register did by default but make it an event Signed-off-by: Berend Sliedrecht <berend@animo.id> --- apps/shared/src/events/didEvents.ts | 46 +++---- .../src/agent/agent.service.ts | 38 +----- .../dids/__tests__/dids.controller.spec.ts | 16 +++ .../src/agent/dids/dids.controller.ts | 12 +- .../src/agent/dids/dids.module.ts | 3 +- .../src/agent/dids/dids.service.ts | 62 +++++---- .../src/agent/ledger/register.ts | 4 - .../src/agent/schemas/schemas.service.ts | 2 +- apps/ssi-abstraction/src/app.module.ts | 2 + .../src/config/__tests__/mockConfig.ts | 10 +- apps/ssi-abstraction/test/dids.e2e-spec.ts | 44 +++---- apps/ssi-abstraction/test/schemas.e2e-spec.ts | 119 ++++++++++++++++++ 12 files changed, 224 insertions(+), 134 deletions(-) create mode 100644 apps/ssi-abstraction/test/schemas.e2e-spec.ts diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts index 60f5c68..e300545 100644 --- a/apps/shared/src/events/didEvents.ts +++ b/apps/shared/src/events/didEvents.ts @@ -4,44 +4,36 @@ 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 type EventDidsPublicDidInput = BaseEventInput; -/** - * - * @todo: this should be removed as it is a weird event that should not be needed - * - */ -export class EventDidsPublicDid extends BaseEvent<DidDocument> { - public static token = 'dids.publicDid'; +export type EventDidsResolveInput = BaseEventInput<{ did: string }>; +export class EventDidsResolve extends BaseEvent<DidDocument> { + public static token = 'dids.resolve'; public get instance() { return JsonTransformer.fromJSON(this.data, DidDocument); } - public static fromEvent(e: EventDidsPublicDid) { - return new EventDidsPublicDid( - e.data, - e.tenantId, - e.id, - e.type, - e.timestamp, - ); + public static fromEvent(e: EventDidsResolve) { + return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp); } } -export type EventDidsResolveInput = BaseEventInput<{ did: string }>; -export class EventDidsResolve extends BaseEvent<DidDocument> { - public static token = 'dids.resolve'; +export type EventDidsRegisterIndyFromSeedInput = BaseEventInput<{ + seed: string; +}>; +export class EventDidsRegisterIndyFromSeed extends BaseEvent<Array<string>> { + public static token = 'dids.register.indy.fromSeed'; public get instance() { - return JsonTransformer.fromJSON(this.data, DidDocument); + return this.data; } - public static fromEvent(e: EventDidsResolve) { - return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp); + public static fromEvent(e: EventDidsRegisterIndyFromSeed) { + return new EventDidsRegisterIndyFromSeed( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); } } diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 76b0bad..9df1a70 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -16,11 +16,9 @@ import { JwkDidResolver, KeyDidRegistrar, KeyDidResolver, - KeyType, LogLevel, PeerDidRegistrar, PeerDidResolver, - TypedArrayEncoder, WebDidResolver, } from '@aries-framework/core'; import { @@ -40,7 +38,6 @@ import { logger } from '@ocm/shared'; import { LEDGERS } from '../config/ledger.js'; -import { registerPublicDids } from './ledger/register.js'; import { AgentLogger } from './logger.js'; export type AppAgent = Agent<AgentService['modules']>; @@ -127,7 +124,7 @@ export class AgentService implements OnApplicationShutdown { }; } - public get ledgers() { + private get ledgers() { const ledgerIds = this.configService.get('agent.ledgerIds'); if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') { @@ -153,41 +150,8 @@ export class AgentService implements OnApplicationShutdown { }); } - private async registerPublicDid() { - const { publicDidSeed, ledgerIds } = this.configService.get('agent'); - - if (!publicDidSeed) { - logger.info('No public did seed provided, skipping registration'); - return; - } - - if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') { - return; - } - - const registeredPublicDidResponses = await registerPublicDids({ - alias: this.config.label, - ledgerIds, - seed: publicDidSeed, - }); - - for (const publicDidResponse of registeredPublicDidResponses) { - await this.agent.dids.import({ - overwrite: true, - did: publicDidResponse.did, - privateKeys: [ - { - keyType: KeyType.Ed25519, - privateKey: TypedArrayEncoder.fromString(publicDidSeed), - }, - ], - }); - } - } - public async onModuleInit() { await this.agent.initialize(); - await this.registerPublicDid(); logger.info('Agent initialized'); } 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 index eb513d9..7bdb15e 100644 --- a/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts +++ b/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts @@ -34,4 +34,20 @@ describe('DidsController', () => { expect(event.data).toStrictEqual(result); }); }); + + describe('register indy did from seed', () => { + it('should register an indy did from seed', async () => { + const result = ['did:indy:bcovrin:test:mock']; + jest + .spyOn(didsService, 'registerDidIndyFromSeed') + .mockResolvedValue(result); + + const event = await didsController.registerFromSeed({ + seed: 'random-secure-seed', + tenantId: 'some-id', + }); + + 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 index c869d0d..688d688 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts @@ -1,8 +1,8 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { - EventDidsPublicDid, - EventDidsPublicDidInput, + EventDidsRegisterIndyFromSeed, + EventDidsRegisterIndyFromSeedInput, EventDidsResolve, EventDidsResolveInput, } from '@ocm/shared'; @@ -13,10 +13,10 @@ import { DidsService } from './dids.service.js'; export class DidsController { public constructor(private didsService: DidsService) {} - @MessagePattern(EventDidsPublicDid.token) - public async publicDid(options: EventDidsPublicDidInput) { - return new EventDidsPublicDid( - await this.didsService.getPublicDid(options), + @MessagePattern(EventDidsRegisterIndyFromSeed.token) + public async registerFromSeed(options: EventDidsRegisterIndyFromSeedInput) { + return new EventDidsRegisterIndyFromSeed( + await this.didsService.registerDidIndyFromSeed(options), options.tenantId, ); } diff --git a/apps/ssi-abstraction/src/agent/dids/dids.module.ts b/apps/ssi-abstraction/src/agent/dids/dids.module.ts index 18668fe..d0a3f15 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.module.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { AgentModule } from '../agent.module.js'; @@ -6,7 +7,7 @@ import { DidsController } from './dids.controller.js'; import { DidsService } from './dids.service.js'; @Module({ - imports: [AgentModule ], + imports: [AgentModule, ConfigModule], providers: [DidsService], controllers: [DidsController], }) diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts index 2adf4c4..cf78072 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -1,18 +1,23 @@ -import type { - EventDidsPublicDidInput, - EventDidsResolveInput, -} from '@ocm/shared'; +import type { EventDidsResolveInput } from '@ocm/shared'; +import { KeyType, TypedArrayEncoder } from '@aries-framework/core'; import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { registerPublicDids } from '../ledger/register.js'; import { WithTenantService } from '../withTenantService.js'; @Injectable() export class DidsService { - public withTenantService: WithTenantService; + private withTenantService: WithTenantService; + private configService: ConfigService; - public constructor(withTenantService: WithTenantService) { + public constructor( + withTenantService: WithTenantService, + configService: ConfigService, + ) { this.withTenantService = withTenantService; + this.configService = configService; } public async resolve({ did, tenantId }: EventDidsResolveInput) { @@ -34,26 +39,35 @@ export class DidsService { }); } - public async getPublicDid({ tenantId }: EventDidsPublicDidInput) { - return this.withTenantService.invoke(tenantId, async (t) => { - const dids = await t.dids.getCreatedDids({ method: 'indy' }); - if (dids.length === 0) { - throw new Error('No registered public DIDs'); - } + public async registerDidIndyFromSeed({ + tenantId, + seed, + }: { + tenantId: string; + seed: string; + }): Promise<Array<string>> { + const ledgerIds = this.configService.get('agent.ledgerIds'); - if (dids.length > 1) { - throw new Error('Multiple public DIDs found'); - } + const registeredPublicDidResponses = await registerPublicDids({ + ledgerIds, + seed, + }); - const didRecord = dids[0]; + for (const publicDidResponse of registeredPublicDidResponses) { + await this.withTenantService.invoke(tenantId, (t) => + t.dids.import({ + overwrite: true, + did: publicDidResponse.did, + privateKeys: [ + { + keyType: KeyType.Ed25519, + privateKey: TypedArrayEncoder.fromString(seed), + }, + ], + }), + ); + } - if (!didRecord.didDocument) { - throw new Error( - 'A public DID was found, but did not include a DID Document', - ); - } - - return didRecord.didDocument; - }); + return registeredPublicDidResponses.map((r) => r.did); } } diff --git a/apps/ssi-abstraction/src/agent/ledger/register.ts b/apps/ssi-abstraction/src/agent/ledger/register.ts index 1396428..8600a45 100644 --- a/apps/ssi-abstraction/src/agent/ledger/register.ts +++ b/apps/ssi-abstraction/src/agent/ledger/register.ts @@ -6,14 +6,12 @@ import axios from 'axios'; import { LEDGERS } from '../../config/ledger.js'; type RegisterPublicDidOptions = { - alias: string; ledgerIds: Array<LedgerIds>; seed: string; }; type LedgerRegistrationBody = { role?: 'ENDORSER'; - alias?: string; seed: string; }; @@ -25,7 +23,6 @@ type RegisterPublicDidResponse = { export const registerPublicDids = async ({ ledgerIds, - alias, seed, }: RegisterPublicDidOptions): Promise<Array<RegisterPublicDidResponse>> => { const responses: Array<RegisterPublicDidResponse> = []; @@ -36,7 +33,6 @@ export const registerPublicDids = async ({ const body: LedgerRegistrationBody = { role: 'ENDORSER', - alias, seed, }; diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts index 5cb4222..da3a7dc 100644 --- a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts @@ -58,7 +58,7 @@ export class SchemasService { }, }); - if (schemaState.state !== 'finished') { + if (schemaState.state !== 'finished' && schemaState.state !== 'action') { throw new Error( `Error registering schema: ${ schemaState.state === 'failed' ? schemaState.reason : 'Not Finished' diff --git a/apps/ssi-abstraction/src/app.module.ts b/apps/ssi-abstraction/src/app.module.ts index 44a25ae..52662f0 100644 --- a/apps/ssi-abstraction/src/app.module.ts +++ b/apps/ssi-abstraction/src/app.module.ts @@ -6,6 +6,7 @@ import { HealthController } from '@ocm/shared'; import { AgentModule } from './agent/agent.module.js'; import { ConnectionsModule } from './agent/connections/connections.module.js'; +import { SchemasModule } from './agent/schemas/schemas.module.js'; import { TenantsModule } from './agent/tenants/tenants.module.js'; import { config } from './config/config.js'; import { validationSchema } from './config/validation.js'; @@ -21,6 +22,7 @@ import { validationSchema } from './config/validation.js'; AgentModule, ConnectionsModule, DidsModule, + SchemasModule, TenantsModule, ], controllers: [HealthController], diff --git a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts index 8a5eb2e..202a2b0 100644 --- a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts +++ b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts @@ -5,7 +5,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { validationSchema } from '../validation.js'; -const mockConfig = (port: number = 3001): AppConfig => ({ +const mockConfig = (port: number = 3001, withLedger = false): AppConfig => ({ agentHost: '', port: 3000, jwtSecret: '', @@ -16,19 +16,19 @@ const mockConfig = (port: number = 3001): AppConfig => ({ name: 'my-test-agent', walletId: utils.uuid(), walletKey: 'some-key', - ledgerIds: [], + ledgerIds: withLedger ? ['BCOVRIN_TEST'] : [], host: 'http://localhost', inboundPort: port, path: '', - publicDidSeed: '', + publicDidSeed: withLedger ? '12312367897123300000000000000000' : '', autoAcceptConnection: true, autoAcceptCredential: AutoAcceptCredential.ContentApproved, }, }); -export const mockConfigModule = (port: number = 3000) => +export const mockConfigModule = (port: number = 3000, withLedger = false) => ConfigModule.forRoot({ - load: [() => mockConfig(port)], + load: [() => mockConfig(port, withLedger)], validationSchema, }); diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts index 8f3bafe..52c8a7b 100644 --- a/apps/ssi-abstraction/test/dids.e2e-spec.ts +++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts @@ -1,37 +1,21 @@ import type { INestApplication } from '@nestjs/common'; import type { ClientProxy } from '@nestjs/microservices'; import type { + EventDidsRegisterIndyFromSeedInput, EventDidsResolveInput, - EventDidsPublicDidInput, } from '@ocm/shared'; -import { DidDocument } from '@aries-framework/core'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; -import { EventDidsResolve, EventDidsPublicDid } from '@ocm/shared'; +import { EventDidsRegisterIndyFromSeed, 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 { DidsService } from '../src/agent/dids/dids.service.js'; import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; import { TenantsService } from '../src/agent/tenants/tenants.service.js'; import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; -const mockDidDocument = { - context: ['https://w3id.org/did/v1'], - id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM', - verificationMethod: [ - { - id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey', - type: 'Ed25519VerificationKey2018', - controller: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM', - publicKeyBase58: '4SySYXQUtuK26zfC7RpQpWYMThfbXphUf8LWyXXmxyTX', - }, - ], - authentication: ['did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey'], -}; - describe('Dids', () => { const TOKEN = 'DIDS_CLIENT_SERVICE'; let app: INestApplication; @@ -39,12 +23,9 @@ describe('Dids', () => { let tenantId: string; beforeAll(async () => { - jest - .spyOn(DidsService.prototype, 'getPublicDid') - .mockResolvedValue(new DidDocument(mockDidDocument)); const moduleRef = await Test.createTestingModule({ imports: [ - mockConfigModule(3005), + mockConfigModule(3005, true), AgentModule, DidsModule, TenantsModule, @@ -72,16 +53,21 @@ describe('Dids', () => { client.close(); }); - it(EventDidsPublicDid.token, async () => { - const response$ = client.send<EventDidsPublicDid, EventDidsPublicDidInput>( - EventDidsPublicDid.token, - { tenantId }, - ); + it(EventDidsRegisterIndyFromSeed.token, async () => { + const response$ = client.send< + EventDidsRegisterIndyFromSeed, + EventDidsRegisterIndyFromSeedInput + >(EventDidsRegisterIndyFromSeed.token, { + seed: '12312367897123300000000000000000', + tenantId, + }); const response = await firstValueFrom(response$); - const eventInstance = EventDidsPublicDid.fromEvent(response); + const eventInstance = EventDidsRegisterIndyFromSeed.fromEvent(response); - expect(eventInstance.instance).toMatchObject(mockDidDocument); + expect(eventInstance.instance).toMatchObject( + expect.arrayContaining(['did:indy:bcovrin:test:9MMeff63VnCpogD2FWfKnJ']), + ); }); it(EventDidsResolve.token, async () => { diff --git a/apps/ssi-abstraction/test/schemas.e2e-spec.ts b/apps/ssi-abstraction/test/schemas.e2e-spec.ts new file mode 100644 index 0000000..acfe669 --- /dev/null +++ b/apps/ssi-abstraction/test/schemas.e2e-spec.ts @@ -0,0 +1,119 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { + EventAnonCredsSchemasGetAllInput, + EventAnonCredsSchemasGetByIdInput, + EventAnonCredsSchemasRegisterInput, +} from '@ocm/shared'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { + EventAnonCredsSchemasGetAll, + EventAnonCredsSchemasGetById, + EventAnonCredsSchemasRegister, +} 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 { DidsService } from '../src/agent/dids/dids.service.js'; +import { SchemasModule } from '../src/agent/schemas/schemas.module.js'; +import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; +import { TenantsService } from '../src/agent/tenants/tenants.service.js'; +import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; + +describe('Schemas', () => { + const TOKEN = 'SCHEMAS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + let tenantId: string; + let issuerDid: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3004, true), + AgentModule, + SchemasModule, + TenantsModule, + 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(); + + const ts = app.get(TenantsService); + const { id } = await ts.create(TOKEN); + tenantId = id; + + const ds = app.get(DidsService); + const [did] = await ds.registerDidIndyFromSeed({ + tenantId, + seed: '12312367897123300000000000000000', + }); + issuerDid = did; + }); + + afterAll(async () => { + await app.close(); + client.close(); + }); + + it(EventAnonCredsSchemasGetAll.token, async () => { + const response$ = client.send< + EventAnonCredsSchemasGetAll, + EventAnonCredsSchemasGetAllInput + >(EventAnonCredsSchemasGetAll.token, { tenantId }); + const response = await firstValueFrom(response$); + const eventInstance = EventAnonCredsSchemasGetAll.fromEvent(response); + + expect(eventInstance.instance).toEqual(expect.arrayContaining([])); + }); + + it(EventAnonCredsSchemasGetById.token, async () => { + const response$ = client.send< + EventAnonCredsSchemasGetById, + EventAnonCredsSchemasGetByIdInput + >(EventAnonCredsSchemasGetById.token, { tenantId, schemaId: 'some-id' }); + const response = await firstValueFrom(response$); + const eventInstance = EventAnonCredsSchemasGetById.fromEvent(response); + + expect(eventInstance.instance).toEqual(null); + }); + + it(EventAnonCredsSchemasRegister.token, async () => { + const version = new Date().getTime().toString(); + const attributeNames = ['names', 'age']; + const name = 'my-schema'; + const response$ = client.send< + EventAnonCredsSchemasRegister, + EventAnonCredsSchemasRegisterInput + >(EventAnonCredsSchemasRegister.token, { + tenantId, + name, + version, + issuerDid, + attributeNames, + }); + + const response = await firstValueFrom(response$); + const eventInstance = EventAnonCredsSchemasRegister.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + attrNames: attributeNames, + issuerId: issuerDid, + name, + version, + }); + }); +}); -- GitLab