diff --git a/apps/shared/package.json b/apps/shared/package.json index 7e03331c11ce5bf77b37e18714e45cba6161174c..8bd91aba98c275d2a099f75f9054d8647874cc54 100644 --- a/apps/shared/package.json +++ b/apps/shared/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@aries-framework/core": "0.4.2", + "@aries-framework/tenants": "^0.4.2", "@elastic/ecs-winston-format": "^1.5.0", "@nestjs/common": "^10.2.10", "@nestjs/microservices": "^10.2.10", @@ -32,10 +33,10 @@ "winston": "^3.11.0" }, "devDependencies": { - "@types/jest": "^29.5.9", - "@types/node": "^20.9.3", "@nestjs/cli": "^10.2.1", "@nestjs/testing": "^10.2.10", + "@types/jest": "^29.5.9", + "@types/node": "^20.9.3", "rimraf": "^5.0.5", "supertest": "^6.1.3", "ts-jest": "^29.1.1", diff --git a/apps/shared/src/events/__tests__/baseEvents.spec.ts b/apps/shared/src/events/__tests__/baseEvents.spec.ts index a037c79981f78c8f8c48180b51486936db3fdc73..fac31bbeb018a81532ecd6d2fdce106328e18a57 100644 --- a/apps/shared/src/events/__tests__/baseEvents.spec.ts +++ b/apps/shared/src/events/__tests__/baseEvents.spec.ts @@ -6,7 +6,7 @@ describe('Base Events', () => { }); it('should create a new base event', () => { - const baseEvent = new BaseEvent({ some: 'data' }); + const baseEvent = new BaseEvent({ some: 'data' }, 'tenantId'); expect(typeof baseEvent.id).toStrictEqual('string'); expect(baseEvent.type).toStrictEqual('BaseEvent'); diff --git a/apps/shared/src/events/__tests__/connectionEvents.spec.ts b/apps/shared/src/events/__tests__/connectionEvents.spec.ts index 70f97f016b6db6c607667ac17ff57f860702386c..ea43996afbc5dc846cb8a1e936b11f14b5a46e34 100644 --- a/apps/shared/src/events/__tests__/connectionEvents.spec.ts +++ b/apps/shared/src/events/__tests__/connectionEvents.spec.ts @@ -17,7 +17,7 @@ describe('Connection Events', () => { }); it('should create a new connections get all event', () => { - const event = new EventDidcommConnectionsGetAll([]); + const event = new EventDidcommConnectionsGetAll([], 'tenantId'); expect(typeof event.id).toStrictEqual('string'); expect(event.type).toStrictEqual('EventDidcommConnectionsGetAll'); @@ -26,7 +26,7 @@ describe('Connection Events', () => { }); it('should create a new connections get by id event', () => { - const event = new EventDidcommConnectionsGetById(null); + const event = new EventDidcommConnectionsGetById(null, 'tenantId'); expect(typeof event.id).toStrictEqual('string'); expect(event.type).toStrictEqual('EventDidcommConnectionsGetById'); @@ -40,6 +40,7 @@ describe('Connection Events', () => { role: DidExchangeRole.Requester, state: DidExchangeState.Completed, }), + 'tenantId', ); expect(typeof event.id).toStrictEqual('string'); @@ -57,6 +58,7 @@ describe('Connection Events', () => { role: DidExchangeRole.Requester, state: DidExchangeState.Completed, }), + 'tenantId', ); expect(typeof event.id).toStrictEqual('string'); diff --git a/apps/shared/src/events/__tests__/didEvents.spec.ts b/apps/shared/src/events/__tests__/didEvents.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1acd0fa0b03d56b86f4390f0be17b3f82ad5b86d --- /dev/null +++ b/apps/shared/src/events/__tests__/didEvents.spec.ts @@ -0,0 +1,29 @@ +import { DidDocument } from '@aries-framework/core'; + +import { EventDidsPublicDid, EventDidsResolve } from '../didEvents.js'; + +describe('Did Events', () => { + it('should return module', () => { + jest.requireActual('../didEvents'); + }); + + it('should create get public did event', () => { + const doc = new DidDocument({ id: 'did:web:123.com' }); + const event = new EventDidsPublicDid(doc, 'tenantId'); + + expect(typeof event.id).toStrictEqual('string'); + expect(event.type).toStrictEqual('EventDidsPublicDid'); + expect(event.timestamp).toBeInstanceOf(Date); + expect(event.instance).toMatchObject(doc); + }); + + it('should create did resolve event', () => { + const doc = new DidDocument({ id: 'did:my:id' }); + const event = new EventDidsResolve(doc, 'tenantId'); + + expect(typeof event.id).toStrictEqual('string'); + expect(event.type).toStrictEqual('EventDidsResolve'); + expect(event.timestamp).toBeInstanceOf(Date); + expect(event.instance).toMatchObject(doc); + }); +}); diff --git a/apps/shared/src/events/__tests__/tenantEvents.spec.ts b/apps/shared/src/events/__tests__/tenantEvents.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3eda9539790a30c70713d41ad2891692696af67d --- /dev/null +++ b/apps/shared/src/events/__tests__/tenantEvents.spec.ts @@ -0,0 +1,24 @@ +import { TenantRecord } from '@aries-framework/tenants'; + +import { EventTenantsCreate } from '../tenantEvents.js'; + +describe('Tenant Events', () => { + it('should return module', () => { + jest.requireActual('../tenantEvents'); + }); + + it('should create a create tenant event', () => { + const tenantRecord = new TenantRecord({ + config: { + label: 'my-label', + walletConfig: { id: 'some-id', key: 'some-key' }, + }, + }); + const event = new EventTenantsCreate(tenantRecord, undefined); + + expect(typeof event.id).toStrictEqual('string'); + expect(event.type).toStrictEqual('EventTenantsCreate'); + expect(event.timestamp).toBeInstanceOf(Date); + expect(event.instance).toMatchObject(tenantRecord); + }); +}); diff --git a/apps/shared/src/events/baseEvents.ts b/apps/shared/src/events/baseEvents.ts index 7be7e863412e97cc954681c1a7145ec1c4984e20..04cebaadadf0ff5d9ae9ac2393189fd7ecd8da66 100644 --- a/apps/shared/src/events/baseEvents.ts +++ b/apps/shared/src/events/baseEvents.ts @@ -1,15 +1,29 @@ import { utils } from '@aries-framework/core'; -export class BaseEvent<T = Record<string, unknown>> { +export class BaseEvent<T = Record<string, unknown>, TenantIdType = string> { public readonly id: string; public readonly type: string; public readonly timestamp: Date; public readonly data: T; + public readonly tenantId: TenantIdType; + + public constructor( + data: T, + tenantId: TenantIdType, + id?: string, + type?: string, + timestamp?: Date, + ) { + this.data = data; + this.tenantId = tenantId; - public constructor(data: T, id?: string, type?: string, timestamp?: Date) { this.id = id ?? utils.uuid(); this.type = type ?? this.constructor.name; this.timestamp = timestamp ?? new Date(); - this.data = data; } } + +export type BaseEventInput< + T extends Record<string, unknown> = Record<string, unknown>, + TenantIdType extends undefined | string = string, +> = TenantIdType extends string ? { tenantId: string } & T : T; diff --git a/apps/shared/src/events/connectionEvents.ts b/apps/shared/src/events/connectionEvents.ts index 1ec09a51c4624dca7b56ce0866b7716e0ad1de4c..a9ae32a9e47c061241b343cb004db41703b03f83 100644 --- a/apps/shared/src/events/connectionEvents.ts +++ b/apps/shared/src/events/connectionEvents.ts @@ -1,7 +1,10 @@ +import type { BaseEventInput } from './baseEvents.js'; + import { ConnectionRecord, JsonTransformer } from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; +export type EventDidcommConnectionsGetAllInput = BaseEventInput; export class EventDidcommConnectionsGetAll extends BaseEvent< Array<ConnectionRecord> > { @@ -12,10 +15,19 @@ export class EventDidcommConnectionsGetAll extends BaseEvent< } public static fromEvent(e: EventDidcommConnectionsGetAll) { - return new EventDidcommConnectionsGetAll(e.data, e.id, e.type, e.timestamp); + return new EventDidcommConnectionsGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); } } +export type EventDidcommConnectionsGetByIdInput = BaseEventInput<{ + id: string; +}>; export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord | null> { public static token = 'didcomm.connections.getById'; @@ -28,6 +40,7 @@ export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord | public static fromEvent(e: EventDidcommConnectionsGetById) { return new EventDidcommConnectionsGetById( e.data, + e.tenantId, e.id, e.type, e.timestamp, @@ -35,6 +48,7 @@ export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord | } } +export type EventDidcommConnectionsCreateWithSelfInput = BaseEventInput; export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionRecord> { public static token = 'didcomm.connections.createWithSelf'; @@ -45,6 +59,7 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR public static fromEvent(e: EventDidcommConnectionsCreateWithSelf) { return new EventDidcommConnectionsCreateWithSelf( e.data, + e.tenantId, e.id, e.type, e.timestamp, @@ -52,6 +67,9 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR } } +export type EventDidcommConnectionsBlockInput = BaseEventInput<{ + idOrDid: string; +}>; export class EventDidcommConnectionsBlock extends BaseEvent<ConnectionRecord | null> { public static token = 'didcomm.connections.block'; @@ -62,6 +80,12 @@ export class EventDidcommConnectionsBlock extends BaseEvent<ConnectionRecord | n } public static fromEvent(e: EventDidcommConnectionsBlock) { - return new EventDidcommConnectionsBlock(e.data, e.id, e.type, e.timestamp); + return new EventDidcommConnectionsBlock( + 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 c6a2d68003d3e031dd5a58390e815df73754c6de..60f5c6854f6343c4e0c0832901044ed155c0a650 100644 --- a/apps/shared/src/events/didEvents.ts +++ b/apps/shared/src/events/didEvents.ts @@ -1,3 +1,5 @@ +import type { BaseEventInput } from './baseEvents.js'; + import { DidDocument, JsonTransformer } from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; @@ -7,18 +9,31 @@ 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> { +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'; 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); + public static fromEvent(e: EventDidsPublicDid) { + return new EventDidsPublicDid( + 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'; @@ -27,6 +42,6 @@ export class EventDidsResolve extends BaseEvent<DidDocument> { } public static fromEvent(e: EventDidsResolve) { - return new EventDidsResolve(e.data, e.id, e.type, e.timestamp); + return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp); } } diff --git a/apps/shared/src/events/tenantEvents.ts b/apps/shared/src/events/tenantEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3552d1214bbbbc490b91da212cb4e8cb4379db8 --- /dev/null +++ b/apps/shared/src/events/tenantEvents.ts @@ -0,0 +1,22 @@ +import type { BaseEventInput } from './baseEvents.js'; + +import { JsonTransformer } from '@aries-framework/core'; +import { TenantRecord } from '@aries-framework/tenants'; + +import { BaseEvent } from './baseEvents.js'; + +export type EventTenantsCreateInput = BaseEventInput< + { label: string }, + undefined +>; +export class EventTenantsCreate extends BaseEvent<TenantRecord, undefined> { + public static token = 'tenants.create'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, TenantRecord); + } + + public static fromEvent(e: EventTenantsCreate) { + return new EventTenantsCreate(e.data, undefined, e.id, e.type, e.timestamp); + } +} diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index 8a7c5425cf8c0c571deb988dc73b0c80275dedda..42fe80034abf82559f144a6549e28bfc314b6b55 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -6,3 +6,4 @@ export * from './logging/logAxiosError.js'; export * from './events/connectionEvents.js'; export * from './events/didEvents.js'; +export * from './events/tenantEvents.js'; diff --git a/apps/ssi-abstraction/package.json b/apps/ssi-abstraction/package.json index 0d6e996af232b503bf9b2593320e6e78a8db66e6..f7e81bf4aa3e3f21ebc755376484af403b7b69b8 100644 --- a/apps/ssi-abstraction/package.json +++ b/apps/ssi-abstraction/package.json @@ -26,6 +26,7 @@ "@aries-framework/core": "0.4.2", "@aries-framework/indy-vdr": "0.4.2", "@aries-framework/node": "0.4.2", + "@aries-framework/tenants": "^0.4.2", "@elastic/ecs-winston-format": "^1.5.0", "@hyperledger/anoncreds-nodejs": "^0.1.0", "@hyperledger/aries-askar-nodejs": "^0.1.0", @@ -45,13 +46,13 @@ "winston": "^3.11.0" }, "devDependencies": { + "@nestjs/cli": "^10.2.1", + "@nestjs/schematics": "^10.0.3", + "@nestjs/testing": "^10.2.10", "@types/express": "^4.17.21", "@types/jest": "^29.5.9", "@types/node": "^20.9.3", "@types/supertest": "^2.0.16", - "@nestjs/cli": "^10.2.1", - "@nestjs/schematics": "^10.0.3", - "@nestjs/testing": "^10.2.10", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "eslint": "^8.54.0", diff --git a/apps/ssi-abstraction/src/agent/__tests__/agent.controller.spec.ts b/apps/ssi-abstraction/src/agent/__tests__/agent.controller.spec.ts deleted file mode 100644 index 5de798e4f6d183ba21e5b251b64afe35b7046136..0000000000000000000000000000000000000000 --- a/apps/ssi-abstraction/src/agent/__tests__/agent.controller.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DidDocument } from '@aries-framework/core'; -import { Test } from '@nestjs/testing'; - -import { mockConfigModule } from '../../config/__tests__/mockConfig.js'; -import { AgentController } from '../agent.controller.js'; -import { AgentService } from '../agent.service.js'; - -describe('AgentController', () => { - let agentController: AgentController; - let agentService: AgentService; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [mockConfigModule()], - controllers: [AgentController], - providers: [AgentService], - }).compile(); - - agentService = moduleRef.get(AgentService); - agentController = moduleRef.get(AgentController); - }); - - describe('public did', () => { - it('should get the public did information of the agent', async () => { - const result = new DidDocument({ id: 'did:key:123' }); - jest.spyOn(agentService, 'getPublicDid').mockResolvedValue(result); - - const event = await agentController.publicDid(); - expect(event.data).toMatchObject(result); - }); - }); -}); diff --git a/apps/ssi-abstraction/src/agent/agent.controller.ts b/apps/ssi-abstraction/src/agent/agent.controller.ts deleted file mode 100644 index 5469aecb6f29b568dae22308a704de57f6dc0477..0000000000000000000000000000000000000000 --- a/apps/ssi-abstraction/src/agent/agent.controller.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Controller } from '@nestjs/common'; -import { MessagePattern } from '@nestjs/microservices'; -import { EventInfoPublicDid } from '@ocm/shared'; - -import { AgentService } from './agent.service.js'; - -@Controller('agent') -export class AgentController { - public constructor(private agent: AgentService) {} - - @MessagePattern(EventInfoPublicDid.token) - public async publicDid() { - const didDocument = await this.agent.getPublicDid(); - - return new EventInfoPublicDid(didDocument); - } -} diff --git a/apps/ssi-abstraction/src/agent/agent.module.ts b/apps/ssi-abstraction/src/agent/agent.module.ts index 23b4de097e83f823f499e090aee94df57e1ffc6e..82fefd59e0864e026b1f5e301c53fffba94e93cc 100644 --- a/apps/ssi-abstraction/src/agent/agent.module.ts +++ b/apps/ssi-abstraction/src/agent/agent.module.ts @@ -1,13 +1,12 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { AgentController } from './agent.controller.js'; import { AgentService } from './agent.service.js'; +import { WithTenantService } from './withTenantService.js'; @Module({ imports: [ConfigModule], - providers: [AgentService], - controllers: [AgentController], - exports: [AgentService], + providers: [AgentService, WithTenantService], + exports: [AgentService, WithTenantService], }) export class AgentModule {} diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index ef1ee73eff8a4a2e5e39de3aeec876dd891dbfe1..76b0bad51df5ce8abd267e2f08d56bf83f86de1c 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -30,6 +30,7 @@ import { IndyVdrSovDidResolver, } from '@aries-framework/indy-vdr'; import { agentDependencies, HttpInboundTransport } from '@aries-framework/node'; +import { TenantsModule } from '@aries-framework/tenants'; import { anoncreds } from '@hyperledger/anoncreds-nodejs'; import { ariesAskar } from '@hyperledger/aries-askar-nodejs'; import { indyVdr } from '@hyperledger/indy-vdr-nodejs'; @@ -113,10 +114,16 @@ export class AgentService implements OnApplicationShutdown { new JwkDidResolver(), new WebDidResolver(), ], - registrars: [new PeerDidRegistrar(), new KeyDidRegistrar(), new JwkDidRegistrar()], + registrars: [ + new PeerDidRegistrar(), + new KeyDidRegistrar(), + new JwkDidRegistrar(), + ], }), askar: new AskarModule({ ariesAskar }), + + tenants: new TenantsModule(), }; } @@ -178,27 +185,6 @@ export class AgentService implements OnApplicationShutdown { } } - public async getPublicDid() { - const dids = await this.agent.dids.getCreatedDids({ method: 'indy' }); - if (dids.length === 0) { - throw new Error('No registered public DIDs'); - } - - if (dids.length > 1) { - throw new Error('Multiple public DIDs found'); - } - - const didRecord = dids[0]; - - if (!didRecord.didDocument) { - throw new Error( - 'A public DID was found, but did not include a DID Document', - ); - } - - return didRecord.didDocument; - } - public async onModuleInit() { await this.agent.initialize(); await this.registerPublicDid(); diff --git a/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts b/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts index 2bcb88b5d4daf15197e5997ca990e4bad6218297..235511f24936f994d0a963d8b5c720d20268e972 100644 --- a/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts +++ b/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts @@ -30,7 +30,9 @@ describe('ConnectionsController', () => { const result: Array<ConnectionRecord> = []; jest.spyOn(connectionsService, 'getAll').mockResolvedValue(result); - const connectionsEvent = await connectionsController.getAll(); + const connectionsEvent = await connectionsController.getAll({ + tenantId: 'some-id', + }); expect(connectionsEvent.data).toStrictEqual(result); }); @@ -43,6 +45,7 @@ describe('ConnectionsController', () => { const connectionsEvent = await connectionsController.getById({ id: 'id', + tenantId: 'some-id', }); expect(connectionsEvent.data).toStrictEqual(result); @@ -61,7 +64,9 @@ describe('ConnectionsController', () => { .mockResolvedValue(result); const connectionsEvent = - await connectionsController.createConnectionWithSelf(); + await connectionsController.createConnectionWithSelf({ + tenantId: 'some-id', + }); expect(connectionsEvent.data).toStrictEqual(result); }); diff --git a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts index 0eb6bc7c9c5bccf4763067b8c66e76ffc66283c5..4f04f50d6352bdf72ce7359ad7d29d8b7d96769a 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts @@ -5,6 +5,10 @@ import { EventDidcommConnectionsGetAll, EventDidcommConnectionsCreateWithSelf, EventDidcommConnectionsBlock, + EventDidcommConnectionsGetAllInput, + EventDidcommConnectionsGetByIdInput, + EventDidcommConnectionsCreateWithSelfInput, + EventDidcommConnectionsBlockInput, } from '@ocm/shared'; import { ConnectionsService } from './connections.service.js'; @@ -14,38 +18,42 @@ export class ConnectionsController { public constructor(private connectionsService: ConnectionsService) {} @MessagePattern(EventDidcommConnectionsGetAll.token) - public async getAll(): Promise<EventDidcommConnectionsGetAll> { + public async getAll( + options: EventDidcommConnectionsGetAllInput, + ): Promise<EventDidcommConnectionsGetAll> { return new EventDidcommConnectionsGetAll( - await this.connectionsService.getAll(), + await this.connectionsService.getAll(options), + options.tenantId, ); } @MessagePattern(EventDidcommConnectionsGetById.token) - public async getById({ - id, - }: { - id: string; - }): Promise<EventDidcommConnectionsGetById> { + public async getById( + options: EventDidcommConnectionsGetByIdInput, + ): Promise<EventDidcommConnectionsGetById> { return new EventDidcommConnectionsGetById( - await this.connectionsService.getById(id), + await this.connectionsService.getById(options), + options.tenantId, ); } @MessagePattern(EventDidcommConnectionsCreateWithSelf.token) - public async createConnectionWithSelf(): Promise<EventDidcommConnectionsCreateWithSelf> { + public async createConnectionWithSelf( + options: EventDidcommConnectionsCreateWithSelfInput, + ): Promise<EventDidcommConnectionsCreateWithSelf> { return new EventDidcommConnectionsCreateWithSelf( - await this.connectionsService.createConnectionWithSelf(), + await this.connectionsService.createConnectionWithSelf(options), + options.tenantId, ); } @MessagePattern(EventDidcommConnectionsBlock.token) - public async blockConnection({ - idOrDid, - }: { - idOrDid: string; - }): Promise<EventDidcommConnectionsBlock> { + public async blockConnection( + options: EventDidcommConnectionsBlockInput, + ): Promise<EventDidcommConnectionsBlock> { return new EventDidcommConnectionsBlock( - await this.connectionsService.blockByIdOrDid(idOrDid), + await this.connectionsService.blockByIdOrDid(options), + options.tenantId, ); } } diff --git a/apps/ssi-abstraction/src/agent/connections/connections.service.ts b/apps/ssi-abstraction/src/agent/connections/connections.service.ts index ba72b2c157f28af5007e74e92860c96c1188a157..027474a5f46376e3b49ee813c5d1d7ab068135da 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.service.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.service.ts @@ -3,6 +3,12 @@ import type { ConnectionRecord, ConnectionStateChangedEvent, } from '@aries-framework/core'; +import type { + EventDidcommConnectionsBlockInput, + EventDidcommConnectionsCreateWithSelfInput, + EventDidcommConnectionsGetAllInput, + EventDidcommConnectionsGetByIdInput, +} from '@ocm/shared'; import { ConnectionEventTypes, @@ -14,79 +20,100 @@ import { Injectable } from '@nestjs/common'; import { MetadataTokens } from '../../common/constants.js'; import { AgentService } from '../agent.service.js'; +import { WithTenantService } from '../withTenantService.js'; @Injectable() export class ConnectionsService { public agent: AppAgent; + public withTenantService: WithTenantService; - public constructor(agentService: AgentService) { + public constructor( + agentService: AgentService, + withTenantService: WithTenantService, + ) { this.agent = agentService.agent; + this.withTenantService = withTenantService; } - public async getAll(): Promise<Array<ConnectionRecord>> { - return await this.agent.connections.getAll(); + public async getAll({ + tenantId, + }: EventDidcommConnectionsGetAllInput): Promise<Array<ConnectionRecord>> { + return this.withTenantService.invoke(tenantId, (t) => + t.connections.getAll(), + ); } - public async getById(id: string): Promise<ConnectionRecord | null> { - return await this.agent.connections.findById(id); + public async getById({ + tenantId, + id, + }: EventDidcommConnectionsGetByIdInput): Promise<ConnectionRecord | null> { + return this.withTenantService.invoke(tenantId, (t) => + t.connections.findById(id), + ); } - public async blockByIdOrDid( - idOrDid: string, - ): Promise<ConnectionRecord | null> { - if (isDid(idOrDid)) { - const records = await this.agent.connections.findAllByQuery({ - theirDid: idOrDid, - }); + public async blockByIdOrDid({ + tenantId, + idOrDid, + }: EventDidcommConnectionsBlockInput): Promise<ConnectionRecord | null> { + return this.withTenantService.invoke(tenantId, async (t) => { + if (isDid(idOrDid)) { + const records = await t.connections.findAllByQuery({ + theirDid: idOrDid, + }); + + if (records.length === 0) { + return null; + } + + if (records.length > 1) { + throw new Error( + 'Found multiple records with the same DID. This should not be possible', + ); + } - if (records.length === 0) { - return null; - } + await t.connections.deleteById(records[0].id); - if (records.length > 1) { - throw new Error( - 'Found multiple records with the same DID. This should not be possible', - ); + return records[0]; } - await this.agent.connections.deleteById(records[0].id); - - return records[0]; - } + const record = await t.connections.findById(idOrDid); + if (!record) return null; - const record = await this.agent.connections.findById(idOrDid); - if (!record) return null; + await t.connections.deleteById(record.id); - await this.agent.connections.deleteById(record.id); - - return record; + return record; + }); } - public async createConnectionWithSelf(): Promise<ConnectionRecord> { - const outOfBandRecord = await this.agent.oob.createInvitation(); - const invitation = outOfBandRecord.outOfBandInvitation; - - void this.agent.oob.receiveInvitation(invitation); - - return new Promise((resolve) => - this.agent.events.on<ConnectionStateChangedEvent>( - ConnectionEventTypes.ConnectionStateChanged, - async ({ payload: { connectionRecord } }) => { - if (connectionRecord.state !== DidExchangeState.Completed) return; - connectionRecord.metadata.set( - MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY, - { - trusted: true, - }, - ); - - const connRepo = - this.agent.dependencyManager.resolve(ConnectionRepository); - await connRepo.update(this.agent.context, connectionRecord); - - resolve(connectionRecord); - }, - ), - ); + public async createConnectionWithSelf({ + tenantId, + }: EventDidcommConnectionsCreateWithSelfInput): Promise<ConnectionRecord> { + return this.withTenantService.invoke(tenantId, async (t) => { + const outOfBandRecord = await t.oob.createInvitation(); + const invitation = outOfBandRecord.outOfBandInvitation; + + void t.oob.receiveInvitation(invitation); + + return new Promise((resolve) => + this.agent.events.on<ConnectionStateChangedEvent>( + ConnectionEventTypes.ConnectionStateChanged, + async ({ payload: { connectionRecord } }) => { + if (connectionRecord.state !== DidExchangeState.Completed) return; + connectionRecord.metadata.set( + MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY, + { + trusted: true, + }, + ); + + const connRepo = t.dependencyManager.resolve(ConnectionRepository); + await connRepo.update(t.context, connectionRecord); + + resolve(connectionRecord); + }, + ), + ); + }); } } 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 c6507d0f4c8bbbfa9b65c30fd222a8b341757905..eb513d9d30947af2b8a02112ec885911fa424805 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 @@ -28,6 +28,7 @@ describe('DidsController', () => { const event = await didsController.resolve({ did: 'did:key:foo', + 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 70074c7dab0d350bf381f0dffc50bfd1e34f5694..c869d0db7f116abed9397c663753c5d2a52a3ad0 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts @@ -1,6 +1,11 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; -import { EventDidsResolve } from '@ocm/shared'; +import { + EventDidsPublicDid, + EventDidsPublicDidInput, + EventDidsResolve, + EventDidsResolveInput, +} from '@ocm/shared'; import { DidsService } from './dids.service.js'; @@ -8,8 +13,19 @@ import { DidsService } from './dids.service.js'; 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)); + @MessagePattern(EventDidsPublicDid.token) + public async publicDid(options: EventDidsPublicDidInput) { + return new EventDidsPublicDid( + await this.didsService.getPublicDid(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidsResolve.token) + public async resolve(options: EventDidsResolveInput) { + return new EventDidsResolve( + await this.didsService.resolve(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 d4428a9a7ca832ede5039c5ca52ada9e8750fe4a..18668fe548266d2334051dc2f806eecb824060f7 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.module.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.module.ts @@ -6,7 +6,7 @@ import { DidsController } from './dids.controller.js'; import { DidsService } from './dids.service.js'; @Module({ - imports: [AgentModule], + imports: [AgentModule ], 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 d1c4e8676dedd3d87e0b0726e9afc6e8865cba18..2adf4c401c4c9d93e10e5f79ff1185bbd6cdf5a8 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -1,31 +1,59 @@ -import type { AppAgent } from '../agent.service.js'; +import type { + EventDidsPublicDidInput, + EventDidsResolveInput, +} from '@ocm/shared'; import { Injectable } from '@nestjs/common'; -import { AgentService } from '../agent.service.js'; +import { WithTenantService } from '../withTenantService.js'; @Injectable() export class DidsService { - public agent: AppAgent; + public withTenantService: WithTenantService; - public constructor(agentService: AgentService) { - this.agent = agentService.agent; + public constructor(withTenantService: WithTenantService) { + this.withTenantService = withTenantService; } - 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; + public async resolve({ did, tenantId }: EventDidsResolveInput) { + return this.withTenantService.invoke(tenantId, async (t) => { + const { + didDocument, + didResolutionMetadata: { message, error }, + } = await t.dids.resolve(did); + + if (!didDocument) { + throw new Error( + `Could not resolve did: '${did}'. Error: ${ + error ?? 'None' + } Message: ${message ?? 'None'}`, + ); + } + + return didDocument; + }); + } + + 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'); + } + + if (dids.length > 1) { + throw new Error('Multiple public DIDs found'); + } + + const didRecord = dids[0]; + + if (!didRecord.didDocument) { + throw new Error( + 'A public DID was found, but did not include a DID Document', + ); + } + + return didRecord.didDocument; + }); } } diff --git a/apps/ssi-abstraction/src/agent/tenants/__tests__/tenants.controller.spec.ts b/apps/ssi-abstraction/src/agent/tenants/__tests__/tenants.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9c8f972984cee9f79d82075d27dae163c54171e --- /dev/null +++ b/apps/ssi-abstraction/src/agent/tenants/__tests__/tenants.controller.spec.ts @@ -0,0 +1,41 @@ +import { TenantRecord } from '@aries-framework/tenants'; +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { TenantsController } from '../tenants.controller.js'; +import { TenantsService } from '../tenants.service.js'; + +describe('TenantsController', () => { + let tenantsController: TenantsController; + let tenantsService: TenantsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [TenantsController], + providers: [TenantsService], + }).compile(); + + tenantsService = moduleRef.get(TenantsService); + tenantsController = moduleRef.get(TenantsController); + }); + + describe('resolve', () => { + it('should resolve a basic did', async () => { + const result = new TenantRecord({ + config: { + label: 'my-label', + walletConfig: { key: 'some-key', id: 'some-id' }, + }, + }); + jest.spyOn(tenantsService, 'create').mockResolvedValue(result); + + const event = await tenantsController.create({ + label: 'my-label', + }); + + expect(event.data).toStrictEqual(result); + }); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/tenants/tenants.controller.ts b/apps/ssi-abstraction/src/agent/tenants/tenants.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..89dc00118c4fff39cccfb8b09e417e5ca75bb7f3 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/tenants/tenants.controller.ts @@ -0,0 +1,20 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { EventTenantsCreate, EventTenantsCreateInput } from '@ocm/shared'; + +import { TenantsService } from './tenants.service.js'; + +@Controller('tenants') +export class TenantsController { + public constructor(private tenantsService: TenantsService) {} + + @MessagePattern(EventTenantsCreate.token) + public async create({ + label, + }: EventTenantsCreateInput): Promise<EventTenantsCreate> { + return new EventTenantsCreate( + await this.tenantsService.create(label), + undefined, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/tenants/tenants.module.ts b/apps/ssi-abstraction/src/agent/tenants/tenants.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..78df866e66b8c977518fe9451d1f3555becbafb5 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/tenants/tenants.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { TenantsController } from './tenants.controller.js'; +import { TenantsService } from './tenants.service.js'; + +@Module({ + imports: [AgentModule], + providers: [TenantsService], + controllers: [TenantsController], +}) +export class TenantsModule {} diff --git a/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts b/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..784e1cfd6cf67ed07e7cd0f7bb4bd1a1e580663d --- /dev/null +++ b/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts @@ -0,0 +1,18 @@ +import type { AppAgent } from '../agent.service.js'; + +import { Injectable } from '@nestjs/common'; + +import { AgentService } from '../agent.service.js'; + +@Injectable() +export class TenantsService { + public agent: AppAgent; + + public constructor(agentService: AgentService) { + this.agent = agentService.agent; + } + + public async create(label: string) { + return await this.agent.modules.tenants.createTenant({ config: { label } }); + } +} diff --git a/apps/ssi-abstraction/src/agent/withTenantService.ts b/apps/ssi-abstraction/src/agent/withTenantService.ts new file mode 100644 index 0000000000000000000000000000000000000000..eacdb08c83150c0a0d652c4ad21b1ffaf79a90e6 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/withTenantService.ts @@ -0,0 +1,34 @@ +import type { AppAgent } from './agent.service.js'; + +import { Injectable } from '@nestjs/common'; + +import { AgentService } from './agent.service.js'; + +@Injectable() +export class WithTenantService { + private agent: AppAgent; + + public constructor(agentService: AgentService) { + this.agent = agentService.agent; + } + + public invoke<T>( + tenantId: string, + cb: (tenant: AppAgent) => Promise<T>, + ): Promise<T> { + // eslint-disable-next-line no-async-promise-executor + return new Promise<T>(async (resolve, reject) => { + await this.agent.modules.tenants.withTenantAgent( + { tenantId }, + async (tenant) => { + try { + const ret = await cb(tenant as unknown as AppAgent); + resolve(ret); + } catch (e) { + reject(e); + } + }, + ); + }); + } +} diff --git a/apps/ssi-abstraction/src/app.module.ts b/apps/ssi-abstraction/src/app.module.ts index 42d993124609ab1823b3e35c109962c6777b41f5..44a25ae0403ab4bd45ec9a8663f09231f491ff2c 100644 --- a/apps/ssi-abstraction/src/app.module.ts +++ b/apps/ssi-abstraction/src/app.module.ts @@ -1,3 +1,4 @@ +import { DidsModule } from '@aries-framework/core'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { TerminusModule } from '@nestjs/terminus'; @@ -5,6 +6,7 @@ import { HealthController } from '@ocm/shared'; import { AgentModule } from './agent/agent.module.js'; import { ConnectionsModule } from './agent/connections/connections.module.js'; +import { TenantsModule } from './agent/tenants/tenants.module.js'; import { config } from './config/config.js'; import { validationSchema } from './config/validation.js'; @@ -18,6 +20,8 @@ import { validationSchema } from './config/validation.js'; }), AgentModule, ConnectionsModule, + DidsModule, + TenantsModule, ], controllers: [HealthController], }) diff --git a/apps/ssi-abstraction/test/agent.e2e-spec.ts b/apps/ssi-abstraction/test/agent.e2e-spec.ts deleted file mode 100644 index b66afcdd386ed2629984b4eb67fa0e050bae97f6..0000000000000000000000000000000000000000 --- a/apps/ssi-abstraction/test/agent.e2e-spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import './setEnvVars.js'; - -import type { INestApplication } from '@nestjs/common'; -import type { ClientProxy } from '@nestjs/microservices'; - -import { DidDocument } from '@aries-framework/core'; -import { ClientsModule, Transport } from '@nestjs/microservices'; -import { Test } from '@nestjs/testing'; -import { EventInfoPublicDid } from '@ocm/shared'; -import { firstValueFrom, type Observable } from 'rxjs'; - -import { AgentModule } from '../src/agent/agent.module.js'; -import { AgentService } from '../src/agent/agent.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('Agent', () => { - const TOKEN = 'AGENT_CLIENT_SERVICE'; - let app: INestApplication; - let client: ClientProxy; - - beforeAll(async () => { - jest - .spyOn(AgentService.prototype, 'getPublicDid') - .mockResolvedValue(new DidDocument(mockDidDocument)); - - const moduleRef = await Test.createTestingModule({ - imports: [ - mockConfigModule(3000), - AgentModule, - 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(); - }); - - it(EventInfoPublicDid.token, async () => { - const response$: Observable<EventInfoPublicDid> = client.send( - EventInfoPublicDid.token, - {}, - ); - - const response = await firstValueFrom(response$); - const eventInstance = EventInfoPublicDid.fromEvent(response); - - expect(eventInstance.instance).toMatchObject(mockDidDocument); - }); - - afterAll(async () => { - await app.close(); - client.close(); - }); -}); diff --git a/apps/ssi-abstraction/test/connections.e2e-spec.ts b/apps/ssi-abstraction/test/connections.e2e-spec.ts index 3f52769ae31e430e6621f072a774fa26dadfe612..ff9a2e49c28d3baa6f6dc04ff345229b77370b6a 100644 --- a/apps/ssi-abstraction/test/connections.e2e-spec.ts +++ b/apps/ssi-abstraction/test/connections.e2e-spec.ts @@ -1,6 +1,11 @@ import type { INestApplication } from '@nestjs/common'; import type { ClientProxy } from '@nestjs/microservices'; -import type { Observable } from 'rxjs'; +import type { + EventDidcommConnectionsGetAllInput, + EventDidcommConnectionsCreateWithSelfInput, + EventDidcommConnectionsGetByIdInput, + EventDidcommConnectionsBlockInput, +} from '@ocm/shared'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; @@ -14,6 +19,8 @@ import { firstValueFrom } from 'rxjs'; import { AgentModule } from '../src/agent/agent.module.js'; import { ConnectionsModule } from '../src/agent/connections/connections.module.js'; +import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; +import { TenantsService } from '../src/agent/tenants/tenants.service.js'; import { MetadataTokens } from '../src/common/constants.js'; import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; @@ -21,6 +28,7 @@ describe('Connections', () => { const TOKEN = 'CONNECTIONS_CLIENT_SERVICE'; let app: INestApplication; let client: ClientProxy; + let tenantId: string; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -28,6 +36,7 @@ describe('Connections', () => { mockConfigModule(3004), AgentModule, ConnectionsModule, + TenantsModule, ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]), ], }).compile(); @@ -41,6 +50,10 @@ describe('Connections', () => { client = app.get(TOKEN); await client.connect(); + + const ts = app.get(TenantsService); + const { id } = await ts.create(TOKEN); + tenantId = id; }); afterAll(async () => { @@ -49,10 +62,10 @@ describe('Connections', () => { }); it(EventDidcommConnectionsGetAll.token, async () => { - const response$: Observable<EventDidcommConnectionsGetAll> = client.send( - EventDidcommConnectionsGetAll.token, - {}, - ); + const response$ = client.send< + EventDidcommConnectionsGetAll, + EventDidcommConnectionsGetAllInput + >(EventDidcommConnectionsGetAll.token, { tenantId }); const response = await firstValueFrom(response$); const eventInstance = EventDidcommConnectionsGetAll.fromEvent(response); @@ -60,10 +73,13 @@ describe('Connections', () => { }); it(EventDidcommConnectionsGetById.token, async () => { - const response$: Observable<EventDidcommConnectionsGetById> = client.send( - EventDidcommConnectionsGetById.token, - { id: 'some-id' }, - ); + const response$ = client.send< + EventDidcommConnectionsGetById, + EventDidcommConnectionsGetByIdInput + >(EventDidcommConnectionsGetById.token, { + id: 'some-id', + tenantId, + }); const response = await firstValueFrom(response$); const eventInstance = EventDidcommConnectionsGetById.fromEvent(response); @@ -71,8 +87,12 @@ describe('Connections', () => { }); it(EventDidcommConnectionsCreateWithSelf.token, async () => { - const response$: Observable<EventDidcommConnectionsCreateWithSelf> = - client.send(EventDidcommConnectionsCreateWithSelf.token, {}); + const response$ = client.send< + EventDidcommConnectionsCreateWithSelf, + EventDidcommConnectionsCreateWithSelfInput + >(EventDidcommConnectionsCreateWithSelf.token, { + tenantId, + }); const response = await firstValueFrom(response$); const eventInstance = @@ -86,10 +106,13 @@ describe('Connections', () => { }); it(EventDidcommConnectionsBlock.token, async () => { - const response$: Observable<EventDidcommConnectionsBlock> = client.send( - EventDidcommConnectionsBlock.token, - { idOrDid: 'some-id' }, - ); + const response$ = client.send< + EventDidcommConnectionsBlock, + EventDidcommConnectionsBlockInput + >(EventDidcommConnectionsBlock.token, { + idOrDid: 'some-id', + tenantId, + }); const response = await firstValueFrom(response$); const eventInstance = EventDidcommConnectionsBlock.fromEvent(response); diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts index e276f368de9ff9a215e4ddef581ac94bfc32faec..8f3bafe813ab0b21b5c7ce63ab71df7e7da923ca 100644 --- a/apps/ssi-abstraction/test/dids.e2e-spec.ts +++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts @@ -1,29 +1,53 @@ import type { INestApplication } from '@nestjs/common'; import type { ClientProxy } from '@nestjs/microservices'; -import type { Observable } from 'rxjs'; +import type { + EventDidsResolveInput, + EventDidsPublicDidInput, +} from '@ocm/shared'; +import { DidDocument } from '@aries-framework/core'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; -import { - EventDidsResolve, -} from '@ocm/shared'; +import { EventDidsResolve, EventDidsPublicDid } 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; let client: ClientProxy; + let tenantId: string; beforeAll(async () => { + jest + .spyOn(DidsService.prototype, 'getPublicDid') + .mockResolvedValue(new DidDocument(mockDidDocument)); const moduleRef = await Test.createTestingModule({ imports: [ mockConfigModule(3005), AgentModule, DidsModule, + TenantsModule, ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]), ], }).compile(); @@ -37,6 +61,10 @@ describe('Dids', () => { client = app.get(TOKEN); await client.connect(); + + const ts = app.get(TenantsService); + const { id } = await ts.create(TOKEN); + tenantId = id; }); afterAll(async () => { @@ -44,11 +72,24 @@ describe('Dids', () => { client.close(); }); + it(EventDidsPublicDid.token, async () => { + const response$ = client.send<EventDidsPublicDid, EventDidsPublicDidInput>( + EventDidsPublicDid.token, + { tenantId }, + ); + + const response = await firstValueFrom(response$); + const eventInstance = EventDidsPublicDid.fromEvent(response); + + expect(eventInstance.instance).toMatchObject(mockDidDocument); + }); + it(EventDidsResolve.token, async () => { - const response$: Observable<EventDidsResolve> = client.send( + const response$ = client.send<EventDidsResolve, EventDidsResolveInput>( EventDidsResolve.token, { did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', + tenantId, }, ); diff --git a/apps/ssi-abstraction/test/jest.config.js b/apps/ssi-abstraction/test/jest.config.js index c03e51b25e5fed650794e8265281358c6198df91..fb2852ff232ad5916c44d175d873f979029b4635 100644 --- a/apps/ssi-abstraction/test/jest.config.js +++ b/apps/ssi-abstraction/test/jest.config.js @@ -3,7 +3,7 @@ import config from '../jest.config.js'; /** @type {import('jest').Config} */ export default { ...config, - testTimeout: 12000, + testTimeout: 24000, rootDir: '.', testRegex: '.*\\.e2e-spec\\.ts$', }; diff --git a/apps/ssi-abstraction/test/tenants.e2e-spec.ts b/apps/ssi-abstraction/test/tenants.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0d372ffeab6467064ca399b3d5973985c65bb05 --- /dev/null +++ b/apps/ssi-abstraction/test/tenants.e2e-spec.ts @@ -0,0 +1,65 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { EventTenantsCreateInput } from '@ocm/shared'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { EventTenantsCreate } from '@ocm/shared'; +import { firstValueFrom } from 'rxjs'; + +import { AgentModule } from '../src/agent/agent.module.js'; +import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; +import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; + +describe('Tenants', () => { + const TOKEN = 'TENANTS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3005), + AgentModule, + TenantsModule, + 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(EventTenantsCreate.token, async () => { + const response$ = client.send<EventTenantsCreate, EventTenantsCreateInput>( + EventTenantsCreate.token, + { + label: 'my-new-tenant', + }, + ); + + const response = await firstValueFrom(response$); + const eventInstance = EventTenantsCreate.fromEvent(response); + + expect(eventInstance.instance.toJSON()).toMatchObject({ + config: { + label: 'my-new-tenant', + walletConfig: { + keyDerivationMethod: 'RAW', + }, + }, + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02d4884cc1332c2c51121970d6f8e40f9f6cc942..f6b4d579ede4f3a4b22c4bf38edc3735f3f4ccc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -682,6 +682,9 @@ importers: '@aries-framework/core': specifier: 0.4.2 version: 0.4.2(expo@49.0.18)(react-native@0.72.7) + '@aries-framework/tenants': + specifier: ^0.4.2 + version: 0.4.2(expo@49.0.18)(react-native@0.72.7) '@elastic/ecs-winston-format': specifier: ^1.5.0 version: 1.5.0 @@ -758,6 +761,9 @@ importers: '@aries-framework/node': specifier: 0.4.2 version: 0.4.2(expo@49.0.18)(react-native@0.72.7) + '@aries-framework/tenants': + specifier: ^0.4.2 + version: 0.4.2(expo@49.0.18)(react-native@0.72.7) '@elastic/ecs-winston-format': specifier: ^1.5.0 version: 1.5.0 @@ -1062,6 +1068,19 @@ packages: - web-streams-polyfill dev: false + /@aries-framework/tenants@0.4.2(expo@49.0.18)(react-native@0.72.7): + resolution: {integrity: sha512-dRgneBY4z6YAn9ieNSeLEqhW+H03aFZwnxcnWhJfSGeHKUl0kMPmjCqvpP3NFhdB/rX92U9OOZDruIv2efM2ig==} + dependencies: + '@aries-framework/core': 0.4.2(expo@49.0.18)(react-native@0.72.7) + async-mutex: 0.4.0 + transitivePeerDependencies: + - domexception + - encoding + - expo + - react-native + - web-streams-polyfill + dev: false + /@babel/code-frame@7.10.4: resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} dependencies: @@ -6144,6 +6163,12 @@ packages: dev: false optional: true + /async-mutex@0.4.0: + resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==} + dependencies: + tslib: 2.6.2 + dev: false + /async-value-promise@1.1.1: resolution: {integrity: sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==} requiresBuild: true @@ -8974,7 +8999,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 5.2.2 - webpack: 5.89.0(@swc/core@1.3.96) + webpack: 5.89.0 dev: true /form-data@3.0.1: @@ -10313,7 +10338,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@swc/core@1.3.96)(@types/node@20.9.0)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@20.9.4)(typescript@5.3.2) transitivePeerDependencies: - babel-plugin-macros - supports-color