diff --git a/apps/shared/package.json b/apps/shared/package.json index 8bd91aba98c275d2a099f75f9054d8647874cc54..e092ae552055278ec57d94efd8a8623dd2c7fccf 100644 --- a/apps/shared/package.json +++ b/apps/shared/package.json @@ -21,6 +21,7 @@ "test:e2e": "jest --config ./test/jest.config.js" }, "dependencies": { + "@aries-framework/anoncreds": "0.4.2", "@aries-framework/core": "0.4.2", "@aries-framework/tenants": "^0.4.2", "@elastic/ecs-winston-format": "^1.5.0", diff --git a/apps/shared/src/events/schemaEvents.ts b/apps/shared/src/events/schemaEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..29cb53b233bd9191134ecf5606a676d784714b78 --- /dev/null +++ b/apps/shared/src/events/schemaEvents.ts @@ -0,0 +1,70 @@ +import type { BaseEventInput } from './baseEvents.js'; +import type { AnonCredsSchema } from '@aries-framework/anoncreds'; + +import { BaseEvent } from './baseEvents.js'; + +export type EventAnonCredsSchemasGetAllInput = BaseEventInput; +export class EventAnonCredsSchemasGetAll extends BaseEvent< + Array<AnonCredsSchema> +> { + public static token = 'anoncreds.schemas.getAll'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsSchemasGetAll) { + return new EventAnonCredsSchemasGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsSchemasGetByIdInput = BaseEventInput<{ + schemaId: string; +}>; +export class EventAnonCredsSchemasGetById extends BaseEvent<AnonCredsSchema | null> { + public static token = 'anoncreds.schemas.getById'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsSchemasGetById) { + return new EventAnonCredsSchemasGetById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsSchemasRegisterInput = BaseEventInput<{ + issuerDid: string; + name: string; + version: string; + attributeNames: Array<string>; +}>; +export class EventAnonCredsSchemasRegister extends BaseEvent<AnonCredsSchema> { + public static token = 'anoncreds.schemas.register'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsSchemasRegister) { + return new EventAnonCredsSchemasRegister( + 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 42fe80034abf82559f144a6549e28bfc314b6b55..7f8ae5efe78d58642c60d2b62c1cd718d0e501e5 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -7,3 +7,4 @@ export * from './logging/logAxiosError.js'; export * from './events/connectionEvents.js'; export * from './events/didEvents.js'; export * from './events/tenantEvents.js'; +export * from './events/schemaEvents.js'; 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 new file mode 100644 index 0000000000000000000000000000000000000000..d761a213358d7eb77c68c21053480efa68193867 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts @@ -0,0 +1,74 @@ +import type { AnonCredsSchema } from '@aries-framework/anoncreds'; + +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { SchemasController } from '../schemas.controller.js'; +import { SchemasService } from '../schemas.service.js'; + +describe('ConnectionsController', () => { + let schemasController: SchemasController; + let schemasService: SchemasService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [SchemasController], + providers: [SchemasService], + }).compile(); + + schemasService = moduleRef.get(SchemasService); + schemasController = moduleRef.get(SchemasController); + }); + + describe('get all', () => { + it('should get all the registered schemas of the agent', async () => { + const result: Array<AnonCredsSchema> = []; + jest.spyOn(schemasService, 'getAll').mockResolvedValue(result); + + const event = await schemasController.getAll({ + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('get by id', () => { + it('should get a schema by id', async () => { + const result: AnonCredsSchema | null = null; + jest.spyOn(schemasService, 'getById').mockResolvedValue(result); + + const event = await schemasController.getById({ + schemaId: 'id', + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('register schema', () => { + it('should register a schema on a ledger', async () => { + const result: AnonCredsSchema = { + name: 'schema-name', + version: '1.0', + issuerId: 'did:indy:123', + attrNames: ['name', 'age'], + }; + + jest.spyOn(schemasService, 'register').mockResolvedValue(result); + + const event = await schemasController.register({ + tenantId: 'some-id', + version: '1.0', + name: 'schema-name', + issuerDid: 'did:indy:123', + attributeNames: ['name', 'age'], + }); + + expect(event.data).toStrictEqual(result); + }); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..180fd98b3be90fd778cef061ae2c415acbf54f7e --- /dev/null +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts @@ -0,0 +1,47 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { + EventAnonCredsSchemasGetAll, + EventAnonCredsSchemasGetAllInput, + EventAnonCredsSchemasGetById, + EventAnonCredsSchemasGetByIdInput, + EventAnonCredsSchemasRegister, + EventAnonCredsSchemasRegisterInput, +} from '@ocm/shared'; + +import { SchemasService } from './schemas.service.js'; + +@Controller('schemas') +export class SchemasController { + public constructor(private schemasService: SchemasService) {} + + @MessagePattern(EventAnonCredsSchemasGetAll.token) + public async getAll( + options: EventAnonCredsSchemasGetAllInput, + ): Promise<EventAnonCredsSchemasGetAll> { + return new EventAnonCredsSchemasGetAll( + await this.schemasService.getAll(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsSchemasGetById.token) + public async getById( + options: EventAnonCredsSchemasGetByIdInput, + ): Promise<EventAnonCredsSchemasGetById> { + return new EventAnonCredsSchemasGetById( + await this.schemasService.getById(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsSchemasRegister.token) + public async register( + options: EventAnonCredsSchemasRegisterInput, + ): Promise<EventAnonCredsSchemasRegister> { + return new EventAnonCredsSchemasRegister( + await this.schemasService.register(options), + options.tenantId, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3faaad8d8902a6551cd0e3cab57313e5e925076 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { SchemasController } from './schemas.controller.js'; +import { SchemasService } from './schemas.service.js'; + +@Module({ + imports: [AgentModule], + providers: [SchemasService], + controllers: [SchemasController], +}) +export class SchemasModule {} diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..5cb4222db53d420bde4ea769d3dc30866dda4ce8 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts @@ -0,0 +1,72 @@ +import type { AnonCredsSchema } from '@aries-framework/anoncreds'; +import type { IndyVdrRegisterSchemaOptions } from '@aries-framework/indy-vdr'; +import type { + EventAnonCredsSchemasGetAllInput, + EventAnonCredsSchemasGetByIdInput, + EventAnonCredsSchemasRegisterInput, +} from '@ocm/shared'; + +import { Injectable } from '@nestjs/common'; + +import { WithTenantService } from '../withTenantService.js'; + +@Injectable() +export class SchemasService { + public withTenantService: WithTenantService; + + public constructor(withTenantService: WithTenantService) { + this.withTenantService = withTenantService; + } + + public async getAll({ + tenantId, + }: EventAnonCredsSchemasGetAllInput): Promise<Array<AnonCredsSchema>> { + return this.withTenantService.invoke(tenantId, async (t) => + (await t.modules.anoncreds.getCreatedSchemas({})).map((r) => r.schema), + ); + } + + public async getById({ + tenantId, + schemaId, + }: EventAnonCredsSchemasGetByIdInput): Promise<AnonCredsSchema | null> { + return this.withTenantService.invoke(tenantId, async (t) => { + const { schema } = await t.modules.anoncreds.getSchema(schemaId); + return schema ?? null; + }); + } + + public async register({ + tenantId, + name, + version, + issuerDid, + attributeNames, + }: EventAnonCredsSchemasRegisterInput): Promise<AnonCredsSchema> { + return this.withTenantService.invoke(tenantId, async (t) => { + const { schemaState } = + await t.modules.anoncreds.registerSchema<IndyVdrRegisterSchemaOptions>({ + schema: { + version, + name, + issuerId: issuerDid, + attrNames: attributeNames, + }, + options: { + endorserMode: 'external', + endorserDid: issuerDid, + }, + }); + + if (schemaState.state !== 'finished') { + throw new Error( + `Error registering schema: ${ + schemaState.state === 'failed' ? schemaState.reason : 'Not Finished' + }`, + ); + } + + return schemaState.schema; + }); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93ebdf1a54006abba74a71cf365a0f0661a32edf..0829affa99021eff4ffd7d9c6979028c77224ae7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -682,6 +682,9 @@ importers: apps/shared: dependencies: + '@aries-framework/anoncreds': + specifier: 0.4.2 + version: 0.4.2(expo@49.0.18)(react-native@0.72.7) '@aries-framework/core': specifier: 0.4.2 version: 0.4.2(expo@49.0.18)(react-native@0.72.7)