diff --git a/apps/shared/src/events/credentialDefinitionEvents.ts b/apps/shared/src/events/credentialDefinitionEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc5741abb187c43dd9351f1941caf10367e1f5de --- /dev/null +++ b/apps/shared/src/events/credentialDefinitionEvents.ts @@ -0,0 +1,74 @@ +import type { BaseEventInput } from './baseEvents.js'; +import type { AnonCredsCredentialDefinition } from '@aries-framework/anoncreds'; + +import { BaseEvent } from './baseEvents.js'; + +export type CredentialDefinitionWithId = AnonCredsCredentialDefinition & { + credentialDefinitionId: string; +}; + +export type EventAnonCredsCredentialDefinitionsGetAllInput = BaseEventInput; +export class EventAnonCredsCredentialDefinitionsGetAll extends BaseEvent< + Array<CredentialDefinitionWithId> +> { + public static token = 'anoncreds.credentialDefinitions.getAll'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsCredentialDefinitionsGetAll) { + return new EventAnonCredsCredentialDefinitionsGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsCredentialDefinitionsGetByIdInput = BaseEventInput<{ + credentialDefinitionId: string; +}>; +export class EventAnonCredsCredentialDefinitionsGetById extends BaseEvent<CredentialDefinitionWithId | null> { + public static token = 'anoncreds.credentialDefinitions.getById'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsCredentialDefinitionsGetById) { + return new EventAnonCredsCredentialDefinitionsGetById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsCredentialDefinitionsRegisterInput = BaseEventInput<{ + schemaId: string; + tag: string; + issuerDid: string; +}>; + +export class EventAnonCredsCredentialDefinitionsRegister extends BaseEvent<CredentialDefinitionWithId> { + public static token = 'anoncreds.credentialDefinitions.register'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsCredentialDefinitionsRegister) { + return new EventAnonCredsCredentialDefinitionsRegister( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts index 60f5c6854f6343c4e0c0832901044ed155c0a650..e300545be74213c4172a34a216ecdd7eaf50c21a 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/shared/src/index.ts b/apps/shared/src/index.ts index 7f8ae5efe78d58642c60d2b62c1cd718d0e501e5..8fa24d341f31ec9e1d424ae5b860438608c4d807 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -8,3 +8,4 @@ export * from './events/connectionEvents.js'; export * from './events/didEvents.js'; export * from './events/tenantEvents.js'; export * from './events/schemaEvents.js'; +export * from './events/credentialDefinitionEvents.js'; diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 76b0bad51df5ce8abd267e2f08d56bf83f86de1c..9df1a7034eb1dec7eb9352566ea01366f26e7786 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/credentialDefinitions/__tests__/credentialDefinitions.controller.spec.ts b/apps/ssi-abstraction/src/agent/credentialDefinitions/__tests__/credentialDefinitions.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b90afbccd45bbd629db0a107460ccaef2ce33c91 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/credentialDefinitions/__tests__/credentialDefinitions.controller.spec.ts @@ -0,0 +1,84 @@ +import type { AnonCredsCredentialDefinition } from '@aries-framework/anoncreds'; + +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { CredentialDefinitionsController } from '../credentialDefinitions.controller.js'; +import { CredentialDefinitionsService } from '../credentialDefinitions.service.js'; + +describe('CredentialDefinitionsController', () => { + let credentialDefinitionsController: CredentialDefinitionsController; + let credentialDefinitionsService: CredentialDefinitionsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [CredentialDefinitionsController], + providers: [CredentialDefinitionsService], + }).compile(); + + credentialDefinitionsService = moduleRef.get(CredentialDefinitionsService); + credentialDefinitionsController = moduleRef.get( + CredentialDefinitionsController, + ); + }); + + describe('get all', () => { + it('should get all the registered credentialDefinitions of the agent', async () => { + const result: Array<AnonCredsCredentialDefinition> = []; + jest + .spyOn(credentialDefinitionsService, 'getAll') + .mockResolvedValue(result); + + const event = await credentialDefinitionsController.getAll({ + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('get by id', () => { + it('should get a credentialDefinition by id', async () => { + const result: AnonCredsCredentialDefinition | null = null; + jest + .spyOn(credentialDefinitionsService, 'getById') + .mockResolvedValue(result); + + const event = await credentialDefinitionsController.getById({ + credentialDefinitionId: 'id', + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('register credentialDefinition', () => { + it('should register a credentialDefinition on a ledger', async () => { + const result: AnonCredsCredentialDefinition = { + tag: 'some-tag', + type: 'CL', + issuerId: 'did:indy:issuer', + schemaId: 'schemaid:123:default', + value: { + primary: {}, + }, + }; + + jest + .spyOn(credentialDefinitionsService, 'register') + .mockResolvedValue(result); + + const event = await credentialDefinitionsController.register({ + tenantId: 'some-tenant-id', + tag: 'some-tag', + issuerDid: 'did:indy:issuer', + schemaId: 'schemaid:123:default', + }); + + expect(event.data).toStrictEqual(result); + }); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.controller.ts b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e75b94722c15e629dd8549c60f1d7f08f0a723c --- /dev/null +++ b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.controller.ts @@ -0,0 +1,49 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { + EventAnonCredsCredentialDefinitionsGetAll, + EventAnonCredsCredentialDefinitionsGetAllInput, + EventAnonCredsCredentialDefinitionsGetById, + EventAnonCredsCredentialDefinitionsGetByIdInput, + EventAnonCredsCredentialDefinitionsRegister, + EventAnonCredsCredentialDefinitionsRegisterInput, +} from '@ocm/shared'; + +import { CredentialDefinitionsService } from './credentialDefinitions.service.js'; + +@Controller('credentialDefinitions') +export class CredentialDefinitionsController { + public constructor( + private credentialDefinitionsService: CredentialDefinitionsService, + ) {} + + @MessagePattern(EventAnonCredsCredentialDefinitionsGetAll.token) + public async getAll( + options: EventAnonCredsCredentialDefinitionsGetAllInput, + ): Promise<EventAnonCredsCredentialDefinitionsGetAll> { + return new EventAnonCredsCredentialDefinitionsGetAll( + await this.credentialDefinitionsService.getAll(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsCredentialDefinitionsGetById.token) + public async getById( + options: EventAnonCredsCredentialDefinitionsGetByIdInput, + ): Promise<EventAnonCredsCredentialDefinitionsGetById> { + return new EventAnonCredsCredentialDefinitionsGetById( + await this.credentialDefinitionsService.getById(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsCredentialDefinitionsRegister.token) + public async register( + options: EventAnonCredsCredentialDefinitionsRegisterInput, + ): Promise<EventAnonCredsCredentialDefinitionsRegister> { + return new EventAnonCredsCredentialDefinitionsRegister( + await this.credentialDefinitionsService.register(options), + options.tenantId, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.module.ts b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..0bff1d8222ca053214c4051ce051c6b772b14ef4 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { CredentialDefinitionsController } from './credentialDefinitions.controller.js'; +import { CredentialDefinitionsService } from './credentialDefinitions.service.js'; + +@Module({ + imports: [AgentModule], + providers: [CredentialDefinitionsService], + controllers: [CredentialDefinitionsController], +}) +export class CredentialDefinitionsModule {} diff --git a/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..32b2b619a18f502f25228540eaaf1620990b9ff3 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts @@ -0,0 +1,88 @@ +import type { AnonCredsCredentialDefinition } from '@aries-framework/anoncreds'; +import type { IndyVdrRegisterCredentialDefinitionOptions } from '@aries-framework/indy-vdr'; +import type { + EventAnonCredsCredentialDefinitionsGetAllInput, + EventAnonCredsCredentialDefinitionsGetByIdInput, + EventAnonCredsCredentialDefinitionsRegisterInput, +} from '@ocm/shared'; + +import { Injectable } from '@nestjs/common'; + +import { WithTenantService } from '../withTenantService.js'; + +@Injectable() +export class CredentialDefinitionsService { + public withTenantService: WithTenantService; + + public constructor(withTenantService: WithTenantService) { + this.withTenantService = withTenantService; + } + + public async getAll({ + tenantId, + }: EventAnonCredsCredentialDefinitionsGetAllInput): Promise< + Array<AnonCredsCredentialDefinition> + > { + return this.withTenantService.invoke(tenantId, async (t) => + (await t.modules.anoncreds.getCreatedCredentialDefinitions({})).map( + (r) => r.credentialDefinition, + ), + ); + } + + public async getById({ + tenantId, + credentialDefinitionId, + }: EventAnonCredsCredentialDefinitionsGetByIdInput): Promise<AnonCredsCredentialDefinition | null> { + return this.withTenantService.invoke(tenantId, async (t) => { + const { credentialDefinition } = + await t.modules.anoncreds.getCredentialDefinition( + credentialDefinitionId, + ); + return credentialDefinition ?? null; + }); + } + + public async register({ + tenantId, + schemaId, + issuerDid, + tag, + }: EventAnonCredsCredentialDefinitionsRegisterInput): Promise< + AnonCredsCredentialDefinition & { credentialDefinitionId: string } + > { + return this.withTenantService.invoke(tenantId, async (t) => { + const { credentialDefinitionState } = + await t.modules.anoncreds.registerCredentialDefinition<IndyVdrRegisterCredentialDefinitionOptions>( + { + credentialDefinition: { + issuerId: issuerDid, + type: 'CL', + schemaId, + tag, + }, + options: { + endorserMode: 'internal', + endorserDid: issuerDid, + }, + }, + ); + + if (credentialDefinitionState.state !== 'finished') { + throw new Error( + `Error registering credentialDefinition: ${ + credentialDefinitionState.state === 'failed' + ? credentialDefinitionState.reason + : 'Not Finished' + }`, + ); + } + + return { + credentialDefinitionId: + credentialDefinitionState.credentialDefinitionId, + ...credentialDefinitionState.credentialDefinition, + }; + }); + } +} 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 eb513d9d30947af2b8a02112ec885911fa424805..7bdb15e4d35aadc60eca85b1b1c056b094cf41d8 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 c869d0db7f116abed9397c663753c5d2a52a3ad0..688d68862c41553b2e9d83fc60d16aad11a741a1 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 18668fe548266d2334051dc2f806eecb824060f7..d0a3f1559bf3705567da090720d4164fb3910a86 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 2adf4c401c4c9d93e10e5f79ff1185bbd6cdf5a8..cf7807236f57ca373f86fe84a05b95dba260dda5 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 1396428d29e68d21ab7df21390bdd070fdb1336c..8600a454b48e609e2798aba6482cbd719720c419 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/__tests__/schemas.controller.spec.ts b/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts index d761a213358d7eb77c68c21053480efa68193867..95fb19a52438c757538e3ee4e479d87b7ceecd74 100644 --- a/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts +++ b/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts @@ -7,7 +7,7 @@ import { AgentModule } from '../../agent.module.js'; import { SchemasController } from '../schemas.controller.js'; import { SchemasService } from '../schemas.service.js'; -describe('ConnectionsController', () => { +describe('SchemassController', () => { let schemasController: SchemasController; let schemasService: SchemasService; diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts index 5cb4222db53d420bde4ea769d3dc30866dda4ce8..9f07491a7d3d927a8e8d701a54714783d187c3a3 100644 --- a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts @@ -42,7 +42,9 @@ export class SchemasService { version, issuerDid, attributeNames, - }: EventAnonCredsSchemasRegisterInput): Promise<AnonCredsSchema> { + }: EventAnonCredsSchemasRegisterInput): Promise< + AnonCredsSchema & { schemaId: string } + > { return this.withTenantService.invoke(tenantId, async (t) => { const { schemaState } = await t.modules.anoncreds.registerSchema<IndyVdrRegisterSchemaOptions>({ @@ -53,7 +55,7 @@ export class SchemasService { attrNames: attributeNames, }, options: { - endorserMode: 'external', + endorserMode: 'internal', endorserDid: issuerDid, }, }); @@ -66,7 +68,7 @@ export class SchemasService { ); } - return schemaState.schema; + return { schemaId: schemaState.schemaId, ...schemaState.schema }; }); } } diff --git a/apps/ssi-abstraction/src/app.module.ts b/apps/ssi-abstraction/src/app.module.ts index 44a25ae0403ab4bd45ec9a8663f09231f491ff2c..876afc5e240475e99fb9ca9649fe4af9087b52dd 100644 --- a/apps/ssi-abstraction/src/app.module.ts +++ b/apps/ssi-abstraction/src/app.module.ts @@ -6,6 +6,8 @@ import { HealthController } from '@ocm/shared'; import { AgentModule } from './agent/agent.module.js'; import { ConnectionsModule } from './agent/connections/connections.module.js'; +import { CredentialDefinitionsModule } from './agent/credentialDefinitions/credentialDefinitions.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'; @@ -20,7 +22,10 @@ import { validationSchema } from './config/validation.js'; }), AgentModule, ConnectionsModule, + SchemasModule, + CredentialDefinitionsModule, 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 8a5eb2ed2efb4ddbcbea7f3d4975a60a29a83af7..202a2b05ede19e8d39aa47dc5790dcb89157c001 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/credentialDefinitions.e2e-spec.ts b/apps/ssi-abstraction/test/credentialDefinitions.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f87be6acd96465e540ba843a504c689fa6a10af --- /dev/null +++ b/apps/ssi-abstraction/test/credentialDefinitions.e2e-spec.ts @@ -0,0 +1,137 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { + EventAnonCredsCredentialDefinitionsGetAllInput, + EventAnonCredsCredentialDefinitionsGetByIdInput, + EventAnonCredsCredentialDefinitionsRegisterInput, +} from '@ocm/shared'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { + EventAnonCredsCredentialDefinitionsGetById, + EventAnonCredsCredentialDefinitionsGetAll, + EventAnonCredsCredentialDefinitionsRegister, +} from '@ocm/shared'; +import { firstValueFrom } from 'rxjs'; + +import { AgentModule } from '../src/agent/agent.module.js'; +import { CredentialDefinitionsModule } from '../src/agent/credentialDefinitions/credentialDefinitions.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 { SchemasService } from '../src/agent/schemas/schemas.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'; + +describe('CredentialDefinitions', () => { + const TOKEN = 'CREDENTIAL_DEFINITIONS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + let tenantId: string; + + let issuerDid: string; + let schemaId: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3004, true), + AgentModule, + SchemasModule, + CredentialDefinitionsModule, + 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 tenantsService = app.get(TenantsService); + const { id } = await tenantsService.create(TOKEN); + tenantId = id; + + const didsService = app.get(DidsService); + const [did] = await didsService.registerDidIndyFromSeed({ + tenantId, + seed: '12312367897123300000000000000000', + }); + issuerDid = did; + + const schemaService = app.get(SchemasService); + const { schemaId: sid } = await schemaService.register({ + issuerDid, + tenantId, + name: 'test-schema-name', + version: `1.${new Date().getTime()}`, + attributeNames: ['none'], + }); + schemaId = sid; + }); + + afterAll(async () => { + await app.close(); + client.close(); + }); + + it(EventAnonCredsCredentialDefinitionsGetAll.token, async () => { + const response$ = client.send< + EventAnonCredsCredentialDefinitionsGetAll, + EventAnonCredsCredentialDefinitionsGetAllInput + >(EventAnonCredsCredentialDefinitionsGetAll.token, { tenantId }); + const response = await firstValueFrom(response$); + const eventInstance = + EventAnonCredsCredentialDefinitionsGetAll.fromEvent(response); + + expect(eventInstance.instance).toEqual(expect.arrayContaining([])); + }); + + it(EventAnonCredsCredentialDefinitionsGetById.token, async () => { + const response$ = client.send< + EventAnonCredsCredentialDefinitionsGetById, + EventAnonCredsCredentialDefinitionsGetByIdInput + >(EventAnonCredsCredentialDefinitionsGetById.token, { + tenantId, + credentialDefinitionId: 'some-id', + }); + const response = await firstValueFrom(response$); + const eventInstance = + EventAnonCredsCredentialDefinitionsGetById.fromEvent(response); + + expect(eventInstance.instance).toEqual(null); + }); + + it(EventAnonCredsCredentialDefinitionsRegister.token, async () => { + const tag = `tag:${new Date().getTime()}`; + const response$ = client.send< + EventAnonCredsCredentialDefinitionsRegister, + EventAnonCredsCredentialDefinitionsRegisterInput + >(EventAnonCredsCredentialDefinitionsRegister.token, { + tenantId, + schemaId, + issuerDid, + tag, + }); + + const response = await firstValueFrom(response$); + const eventInstance = + EventAnonCredsCredentialDefinitionsRegister.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + schemaId, + tag, + issuerId: issuerDid, + type: 'CL', + }); + }); +}); diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts index 8f3bafe813ab0b21b5c7ce63ab71df7e7da923ca..52c8a7b66caaba8d59e7d2c64d98ce725e977532 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 0000000000000000000000000000000000000000..018b1d3b63b436f0fc7c320f18e7259a0ef8a5ca --- /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 = `1.${new Date().getTime()}`; + 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, + }); + }); +});