diff --git a/apps/shared/src/events/revocationEvents.ts b/apps/shared/src/events/revocationEvents.ts index fc182bc4d3f9e7c504a7d30613c047ee1974320d..0e0e83a4c64d090ae216df47c79e2b8584a0389e 100644 --- a/apps/shared/src/events/revocationEvents.ts +++ b/apps/shared/src/events/revocationEvents.ts @@ -77,3 +77,26 @@ export class EventAnonCredsRevocationRegisterRevocationStatusList extends BaseEv ); } } + +export type EventAnonCredsRevocationTailsFileInput = BaseEventInput<{ + revocationRegistryDefinitionId: string; +}>; +export class EventAnonCredsRevocationTailsFile extends BaseEvent<{ + tailsFile: Uint8Array; +}> { + public static token = 'anoncreds.revocation.tailsFile'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsRevocationTailsFile) { + return new EventAnonCredsRevocationTailsFile( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/ssi-abstraction/.env.example b/apps/ssi-abstraction/.env.example index c86bb6f517ea646acfa281aa0c8ad78bcd3008d1..e7ce57b5cd5a7ccba1ac2b0c35f663220b679ec4 100644 --- a/apps/ssi-abstraction/.env.example +++ b/apps/ssi-abstraction/.env.example @@ -3,6 +3,7 @@ AFJ_EXT_PORT=3010 NATS_URL=nats://localhost:4222 ECSURL=http://localhost:9200/ AGENT_HOST=https://gaiax.vereign.com +TAILS_SERVER_BASE_URL=http://localhost:3000/tails AGENT_NAME=ssi-abstraction-agent AGENT_PEER_PORT=:4000 diff --git a/apps/ssi-abstraction/package.json b/apps/ssi-abstraction/package.json index 95d50889e6a8ff3d6b771c4e9eb986195898cdf8..f7770f308e79f6b5d2bcdaa9e11656d3302695a0 100644 --- a/apps/ssi-abstraction/package.json +++ b/apps/ssi-abstraction/package.json @@ -34,6 +34,7 @@ "@ocm/shared": "workspace:*", "axios": "^1.6.2", "express": "^4.17.3", + "form-data": "^4.0.0", "joi": "^17.6.0", "nats": "^2.18.0", "rxjs": "^7.2.0", diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 315eef3fd95483f1605f50a90c570e950fc0fa1c..d9792cb426f8e0d39524e4fefff9231f2e872e94 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -49,6 +49,7 @@ import { parseDid } from '../common/utils.js'; import { LEDGERS } from '../config/ledger.js'; import { AgentLogger } from './logger.js'; +import { TailsFileService } from './revocation/TailsFileService.js'; export type TenantAgent = Agent<Omit<AgentService['modules'], 'tenants'>>; export type AppAgent = Agent<AgentService['modules']>; @@ -99,6 +100,8 @@ export class AgentService implements OnApplicationShutdown { const { autoAcceptConnection, autoAcceptCredential, autoAcceptProof } = this.configService.get('agent'); + const tailsServerBaseUrl = this.configService.get('tailsServerBaseUrl'); + return { connections: new ConnectionsModule({ autoAcceptConnections: autoAcceptConnection, @@ -131,6 +134,7 @@ export class AgentService implements OnApplicationShutdown { anoncreds: new AnonCredsModule({ anoncreds, registries: [new IndyVdrAnonCredsRegistry()], + tailsFileService: new TailsFileService({ tailsServerBaseUrl }), }), indyVdr: new IndyVdrModule({ indyVdr, networks: this.ledgers }), diff --git a/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts b/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts new file mode 100644 index 0000000000000000000000000000000000000000..56f0d436f3e7de029a5450faa7d099564f813607 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts @@ -0,0 +1,50 @@ +import type { AnonCredsRevocationRegistryDefinition } from '@aries-framework/anoncreds'; +import type { AgentContext } from '@aries-framework/core'; + +import { BasicTailsFileService } from '@aries-framework/anoncreds/build/services/tails/BasicTailsFileService.js'; +import FormData from 'form-data'; +import fs from 'fs'; + +export class TailsFileService extends BasicTailsFileService { + private tailsServerBaseUrl?: string; + + public constructor(options?: { + tailsDirectoryPath?: string; + tailsServerBaseUrl?: string; + }) { + super(options); + this.tailsServerBaseUrl = options?.tailsServerBaseUrl; + } + + public async uploadTailsFile( + agentContext: AgentContext, + options: { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition; + }, + ) { + const revocationRegistryDefinition = options.revocationRegistryDefinition; + const localTailsFilePath = revocationRegistryDefinition.value.tailsLocation; + const pathParts = localTailsFilePath.split('/'); + const tailsFileId = pathParts[pathParts.length - 1]; + + const data = new FormData(); + const readStream = fs.createReadStream(localTailsFilePath); + data.append('file', readStream); + + const response = await agentContext.config.agentDependencies.fetch( + `${this.tailsServerBaseUrl}/${tailsFileId}`, + { + method: 'PUT', + body: data, + }, + ); + + if (response.status !== 200) { + throw new Error('Cannot upload tails file'); + } + + return { + tailsFileUrl: `${this.tailsServerBaseUrl}/${encodeURIComponent(tailsFileId)}`, + }; + } +} diff --git a/apps/ssi-abstraction/src/agent/revocation/revocation.controller.ts b/apps/ssi-abstraction/src/agent/revocation/revocation.controller.ts index 8267524027129d4873cb94c1f0309c73773514df..6b9f4be362124877e8abc4ed64fb59e3e2b4aa96 100644 --- a/apps/ssi-abstraction/src/agent/revocation/revocation.controller.ts +++ b/apps/ssi-abstraction/src/agent/revocation/revocation.controller.ts @@ -7,6 +7,8 @@ import { EventAnonCredsRevocationRegisterRevocationStatusListInput, EventAnonCredsRevocationRevoke, EventAnonCredsRevocationRevokeInput, + EventAnonCredsRevocationTailsFile, + EventAnonCredsRevocationTailsFileInput, } from '@ocm/shared'; import { RevocationService } from './revocation.service.js'; @@ -48,4 +50,14 @@ export class RevocationController { options.tenantId, ); } + + @MessagePattern(EventAnonCredsRevocationTailsFile.token) + public async getTailsFile( + options: EventAnonCredsRevocationTailsFileInput, + ): Promise<EventAnonCredsRevocationTailsFile> { + return new EventAnonCredsRevocationTailsFile( + await this.revocationService.getTailsFile(options), + options.tenantId, + ); + } } diff --git a/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts b/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts index 8ecff401bb63fead3f7949b33f9c8c29763dc48d..e4a4bba48505ddb8c0eee279135c1ef5d219a27b 100644 --- a/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts +++ b/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts @@ -5,9 +5,12 @@ import type { EventAnonCredsRevocationRegisterRevocationStatusListInput, EventAnonCredsRevocationRevoke, EventAnonCredsRevocationRevokeInput, + EventAnonCredsRevocationTailsFile, + EventAnonCredsRevocationTailsFileInput, } from '@ocm/shared'; import { Injectable } from '@nestjs/common'; +import { readFile } from 'node:fs/promises'; import { AgentService } from '../agent.service.js'; import { WithTenantService } from '../withTenantService.js'; @@ -216,4 +219,31 @@ export class RevocationService { return {}; }); } + + public async getTailsFile({ + tenantId, + revocationRegistryDefinitionId, + }: EventAnonCredsRevocationTailsFileInput): Promise< + EventAnonCredsRevocationTailsFile['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + const result = await t.modules.anoncreds.getRevocationRegistryDefinition( + revocationRegistryDefinitionId, + ); + + if (!result.revocationRegistryDefinition) { + throw new Error( + `error: ${result.resolutionMetadata.error}. Message: ${result.resolutionMetadata.message}`, + ); + } + + const { tailsLocation } = result.revocationRegistryDefinition.value; + + const content = await readFile(tailsLocation); + + return { + tailsFile: Uint8Array.from(content), + }; + }); + } } diff --git a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts index d0305c275c11367a605d981b25ed8bd70048794f..29af564530a0c00a3f1fa304791980b45b614a2a 100644 --- a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts +++ b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts @@ -8,6 +8,7 @@ import { validationSchema } from '../validation.js'; const mockConfig = (port: number = 3001, withLedger = false): AppConfig => ({ agentHost: '', port: 3000, + tailsServerBaseUrl: 'http://localhost:8080', jwtSecret: '', nats: { url: 'localhost', diff --git a/apps/ssi-abstraction/src/config/config.ts b/apps/ssi-abstraction/src/config/config.ts index d7d46517b762181b96a30eb7fca27bb22024dcac..85ff0a2c9606236d69dca31f49c05bba1c0bd582 100644 --- a/apps/ssi-abstraction/src/config/config.ts +++ b/apps/ssi-abstraction/src/config/config.ts @@ -4,6 +4,7 @@ export interface AppConfig { agentHost: string; port: number; jwtSecret: string; + tailsServerBaseUrl: string; nats: { url: string; @@ -29,6 +30,7 @@ export const config = (): AppConfig => ({ agentHost: process.env.AGENT_HOST || '', port: parseInt(process.env.PORT || '3000'), jwtSecret: process.env.JWT_SECRET || '', + tailsServerBaseUrl: process.env.TAILS_SERVER_BASE_URL || '', nats: { url: process.env.NATS_URL || '', diff --git a/apps/ssi-abstraction/src/config/validation.ts b/apps/ssi-abstraction/src/config/validation.ts index a485d6a9f15a105053046c36e88b2b5ea0c0abf3..92df765fa0c75bf87b6f17d5e0ae306232aec2d1 100644 --- a/apps/ssi-abstraction/src/config/validation.ts +++ b/apps/ssi-abstraction/src/config/validation.ts @@ -5,6 +5,7 @@ export const validationSchema = Joi.object({ NATS_USER: Joi.string().optional(), NATS_PASSWORD: Joi.string().optional(), PORT: Joi.number().required(), + TAILS_SERVER_BASE_URL: Joi.string().required(), AGENT_NAME: Joi.string().required(), AGENT_WALLET_ID: Joi.string().required(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7a16bee15ad47b5a2a799e2e1b31ad5e5f5cee0..effefd8894d8528d2386170c79b926723200dd9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,10 +19,10 @@ importers: version: 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/core': specifier: ^10.3.0 - version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/microservices': specifier: ^10.3.0 - version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(nats@2.19.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@typescript-eslint/eslint-plugin': specifier: ^6.12.0 version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) @@ -631,7 +631,7 @@ importers: version: 3.1.1(@nestjs/common@10.3.0)(reflect-metadata@0.1.14) '@nestjs/core': specifier: ^10.3.0 - version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/microservices': specifier: ^10.3.0 version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(nats@2.19.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -647,6 +647,9 @@ importers: express: specifier: ^4.17.3 version: 4.18.2 + form-data: + specifier: ^4.0.0 + version: 4.0.0 joi: specifier: ^17.6.0 version: 17.11.1 @@ -668,7 +671,7 @@ importers: version: 10.1.0(chokidar@3.5.3)(typescript@5.3.3) '@nestjs/testing': specifier: ^10.2.10 - version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/microservices@10.3.0) + version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0) '@types/express': specifier: ^4.17.21 version: 4.17.21 @@ -4338,37 +4341,6 @@ packages: transitivePeerDependencies: - encoding - /@nestjs/core@10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1): - resolution: {integrity: sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA==} - requiresBuild: true - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - '@nestjs/websockets': ^10.0.0 - reflect-metadata: ^0.1.12 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true - dependencies: - '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/microservices': 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nuxtjs/opencollective': 0.3.2 - fast-safe-stringify: 2.1.1 - iterare: 1.2.1 - path-to-regexp: 3.2.0 - reflect-metadata: 0.1.14 - rxjs: 7.8.1 - tslib: 2.6.2 - uid: 2.0.2 - transitivePeerDependencies: - - encoding - /@nestjs/mapped-types@2.0.4(@nestjs/common@10.3.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14): resolution: {integrity: sha512-xl+gUSp0B+ln1VSNoUftlglk8dfpUes3DHGxKZ5knuBxS5g2H/8p9/DSBOYWUfO5f4u9s6ffBPZ71WO+tbe5SA==} peerDependencies: @@ -4432,49 +4404,6 @@ packages: rxjs: 7.8.1 tslib: 2.6.2 - /@nestjs/microservices@10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1): - resolution: {integrity: sha512-CZj27dEN4Rh6t9cRXv5EEg+HwkOUk02DDdS7x4eLcphnP4wgsLNDEo8vQ2gbQHFGpZhLUyeeynjNGkpV9T3+og==} - peerDependencies: - '@grpc/grpc-js': '*' - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - '@nestjs/websockets': ^10.0.0 - amqp-connection-manager: '*' - amqplib: '*' - cache-manager: '*' - ioredis: '*' - kafkajs: '*' - mqtt: '*' - nats: '*' - reflect-metadata: ^0.1.12 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@grpc/grpc-js': - optional: true - '@nestjs/websockets': - optional: true - amqp-connection-manager: - optional: true - amqplib: - optional: true - cache-manager: - optional: true - ioredis: - optional: true - kafkajs: - optional: true - mqtt: - optional: true - nats: - optional: true - dependencies: - '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) - iterare: 1.2.1 - reflect-metadata: 0.1.14 - rxjs: 7.8.1 - tslib: 2.6.2 - /@nestjs/platform-express@10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0): resolution: {integrity: sha512-E4hUW48bYv8OHbP9XQg6deefmXb0pDSSuE38SdhA0mJ37zGY7C5EqqBUdlQk4ttfD+OdnbIgJ1zOokT6dd2d7A==} peerDependencies: @@ -4599,7 +4528,7 @@ packages: dependencies: '@nestjs/axios': 3.0.1(@nestjs/common@10.3.0)(axios@1.6.5)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/microservices': 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(nats@2.19.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) boxen: 5.1.2 check-disk-space: 3.4.0 @@ -4607,25 +4536,6 @@ packages: rxjs: 7.8.1 dev: false - /@nestjs/testing@10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/microservices@10.3.0): - resolution: {integrity: sha512-8DM+bw1qASCvaEnoHUQhypCOf54+G5R21MeFBMvnSk5DtKaWVZuzDP2GjLeYCpTH19WeP6LrrjHv3rX2LKU02A==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - dependencies: - '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/microservices': 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(nats@2.19.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) - tslib: 2.6.2 - dev: true - /@nestjs/testing@10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0): resolution: {integrity: sha512-8DM+bw1qASCvaEnoHUQhypCOf54+G5R21MeFBMvnSk5DtKaWVZuzDP2GjLeYCpTH19WeP6LrrjHv3rX2LKU02A==} peerDependencies: @@ -9120,7 +9030,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.89.0(@swc/core@1.3.103) + webpack: 5.89.0 dev: true /form-data@3.0.1: