diff --git a/libs/askar/src/agent.utils.ts b/libs/askar/src/agent.utils.ts index 0e257348321ede2e466d43c018cf3c1cd1fb61e0..a769ef2e975bd7b2d1a27c4717272aa5c4774570 100644 --- a/libs/askar/src/agent.utils.ts +++ b/libs/askar/src/agent.utils.ts @@ -32,7 +32,7 @@ import { WalletKeyExistsError, WebDidResolver, JwkDidResolver, - TrustPingResponseReceivedEvent, + TrustPingResponseReceivedEvent, DidRecord, } from "@credo-ts/core"; import { AnonCredsCredentialFormatService, @@ -75,6 +75,7 @@ export type SubjectMessage = { import { Request, Response, Express } from "express"; import url from "url"; import { JsonLdCredentialFormatService } from "./credo/JsonLdCredentialFormatService"; +import {EntityNotFoundError} from "@ocm-engine/dtos"; export const importDidsToWallet = async ( agent: Agent, @@ -636,3 +637,13 @@ export const webHookHandler = async <T>( console.log(`Successfully sent web hook to ${url}/topic/${webHookTopic}`); } }; + +export const getFirstDidWebRecord = async (agent: Agent): Promise<DidRecord> => { + const didWebs = await agent.dids.getCreatedDids({ + method: "web", + }); + if (!didWebs.length) { + throw new EntityNotFoundError("Agent does not have did:web"); + } + return didWebs[0]; +}; diff --git a/libs/askar/src/askar-nats/agent.consumer.service.ts b/libs/askar/src/askar-nats/agent.consumer.service.ts index afa0028c2c9e57ef6a259bba3d0de1f48f97a5a1..0cd2b40ebcd7f223c65f72c263bec003e5d46808 100644 --- a/libs/askar/src/askar-nats/agent.consumer.service.ts +++ b/libs/askar/src/askar-nats/agent.consumer.service.ts @@ -4,7 +4,6 @@ import { OnModuleDestroy, OnModuleInit, } from "@nestjs/common"; -import { AgentService } from "../askar/agent.service"; import { ConsumerService } from "@ocm-engine/nats"; import { ConfigService } from "@nestjs/config"; import { IConfAgent } from "@ocm-engine/config"; @@ -15,7 +14,6 @@ import { EventHandlerService } from "./event.handler.service"; export class AgentConsumerService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(AgentConsumerService.name); constructor( - private readonly agentService: AgentService, private readonly consumerService: ConsumerService, private readonly configService: ConfigService, private readonly gatewayClient: GatewayClient, diff --git a/libs/askar/src/askar-nats/askar.nats.module.ts b/libs/askar/src/askar-nats/askar.nats.module.ts index bf6bc7af41ecac01c32c6461a6974c162e13a223..35bc4aa73f1e25fb884acb2f115f75974079b3a5 100644 --- a/libs/askar/src/askar-nats/askar.nats.module.ts +++ b/libs/askar/src/askar-nats/askar.nats.module.ts @@ -3,7 +3,14 @@ import { ConfigModule } from "@nestjs/config"; import { LedgersModule } from "@ocm-engine/ledgers"; import { APP_PIPE } from "@nestjs/core"; import { AgentConsumerService } from "./agent.consumer.service"; -import { AgentService } from "../askar/agent.service"; +import { AgentAnoncredsService } from "../askar/agent.anoncreds.service"; +import { AgentBasicMessagesService } from "../askar/agent.basicMessages.service"; +import { AgentConnectionsService } from "../askar/agent.connections.service"; +import { AgentCredentialsService } from "../askar/agent.credentials.service"; +import { AgentDidsService } from "../askar/agent.dids.service"; +import { AgentJsonldService } from "../askar/agent.jsonld.service"; +import { AgentOobService } from "../askar/agent.oob.service"; +import { AgentProofsService } from "../askar/agent.proofs.service"; import { ConsumerService } from "@ocm-engine/nats"; import { GatewayClient } from "@ocm-engine/clients"; import { EventHandlerService } from "./event.handler.service"; @@ -13,7 +20,14 @@ import { EventHandlerService } from "./event.handler.service"; providers: [ ConsumerService, AgentConsumerService, - AgentService, + AgentAnoncredsService, + AgentBasicMessagesService, + AgentConnectionsService, + AgentCredentialsService, + AgentDidsService, + AgentJsonldService, + AgentOobService, + AgentProofsService, GatewayClient, EventHandlerService, { diff --git a/libs/askar/src/askar-nats/event.handler.service.ts b/libs/askar/src/askar-nats/event.handler.service.ts index e563f4479ccbb79f0583ce581641637c2231a986..3646ff92fdcc0822a14145f23986c2c807796126 100644 --- a/libs/askar/src/askar-nats/event.handler.service.ts +++ b/libs/askar/src/askar-nats/event.handler.service.ts @@ -1,5 +1,12 @@ import { Injectable, Logger } from "@nestjs/common"; -import { AgentService } from "../askar/agent.service"; +import { AgentAnoncredsService } from "../askar/agent.anoncreds.service"; +import { AgentBasicMessagesService } from "../askar/agent.basicMessages.service"; +import { AgentConnectionsService } from "../askar/agent.connections.service"; +import { AgentCredentialsService } from "../askar/agent.credentials.service"; +import { AgentDidsService } from "../askar/agent.dids.service"; +import { AgentJsonldService } from "../askar/agent.jsonld.service"; +import { AgentOobService } from "../askar/agent.oob.service"; +import { AgentProofsService } from "../askar/agent.proofs.service"; import { AcceptProofDto, CloudEventDto, @@ -47,140 +54,149 @@ import { export class EventHandlerService { private readonly logger = new Logger(EventHandlerService.name); - constructor(private readonly agentService: AgentService) {} + constructor( + private readonly agentAnoncredsService: AgentAnoncredsService, + private readonly agentBasicMessagesService: AgentBasicMessagesService, + private readonly agentConnectionsService: AgentConnectionsService, + private readonly agentCredentialsService: AgentCredentialsService, + private readonly agentDidsService: AgentDidsService, + private readonly agentJsonldService: AgentJsonldService, + private readonly agentOobService: AgentOobService, + private readonly agentProofsService: AgentProofsService, + ) {} async handle<T>(event: CloudEventDto<T>) { let data: unknown; let dto; switch (event.type) { case CONNECTION_CREATE: - data = await this.agentService.createInvitation(); + data = await this.agentOobService.createInvitation(); break; case CONNECTION_ACCEPT: dto = event.data as AcceptInvitationRequestDto; - data = await this.agentService.acceptInvitation(dto.invitationUrl); + data = await this.agentOobService.acceptInvitation(dto.invitationUrl); break; case CONNECTION_LIST: - data = await this.agentService.fetchConnections(); + data = await this.agentConnectionsService.fetchConnections(); break; case CONNECTION_GET: dto = event.data as IdReqDto; - data = await this.agentService.getConnectionById(dto.id); + data = await this.agentConnectionsService.getConnectionById(dto.id); break; case CONNECTION_DELETE: dto = event.data as IdReqDto; - data = await this.agentService.deleteConnectionById(dto.id); + data = await this.agentConnectionsService.deleteConnectionById(dto.id); break; case SCHEMA_CREATE: dto = event.data as CreateSchemaRequestDto; - data = await this.agentService.createSchema(dto); + data = await this.agentAnoncredsService.createSchema(dto); break; case SCHEMA_LIST: - data = await this.agentService.fetchSchemas(); + data = await this.agentAnoncredsService.fetchSchemas(); break; case SCHEMA_GET: dto = event.data as IdReqDto; - data = await this.agentService.getSchemaById(dto.id); + data = await this.agentAnoncredsService.getSchemaById(dto.id); break; case CRED_DEF_CREATE: - data = await this.agentService.createCredentialDefinition( + data = await this.agentAnoncredsService.createCredentialDefinition( event.data as CreateCredentialDefinitionRequestDto, ); break; case CRED_DEF_LIST: - data = await this.agentService.fetchCredentialDefinitions(); + data = await this.agentAnoncredsService.fetchCredentialDefinitions(); break; case CRED_DEF_GET: dto = event.data as IdReqDto; - data = await this.agentService.getCredentialDefinitionById(dto.id); + data = await this.agentAnoncredsService.getCredentialDefinitionById(dto.id); break; case CRED_SEND_OFFER: - data = await this.agentService.offerCredential( + data = await this.agentCredentialsService.offerCredential( event.data as OfferCredentialRequestDto, ); break; case CRED_LIST: dto = event.data as CredentialFilterDto; - data = await this.agentService.fetchCredentials(dto); + data = await this.agentCredentialsService.fetchCredentials(dto); break; case CRED_GET: dto = event.data as IdReqDto; - data = await this.agentService.getCredentialById(dto.id); + data = await this.agentCredentialsService.getCredentialById(dto.id); break; case CRED_OFFER_ACCEPT: - data = await this.agentService.acceptCredential( + data = await this.agentCredentialsService.acceptCredential( event.data as AcceptCredentialDto, ); break; case CRED_OFFER_DECLINE: dto = event.data as IdReqDto; - data = await this.agentService.declineCredential(dto.id); + data = await this.agentCredentialsService.declineCredential(dto.id); break; case CRED_DELETE: dto = event.data as IdReqDto; - data = await this.agentService.deleteCredentialById(dto.id); + data = await this.agentCredentialsService.deleteCredentialById(dto.id); break; case PROOF_REQUEST: dto = event.data as RequestProofDto; - data = await this.agentService.requestProof(dto); + data = await this.agentProofsService.requestProof(dto); break; case PROOF_LIST: dto = event.data as ProofFilterDto; - data = await this.agentService.fetchProofs(dto); + data = await this.agentProofsService.fetchProofs(dto); break; case PROOF_GET: dto = event.data as IdReqDto; - data = await this.agentService.getProofById(dto.id); + data = await this.agentProofsService.getProofById(dto.id); break; case PROOF_ACCEPT: - data = await this.agentService.acceptProof( + data = await this.agentProofsService.acceptProof( event.data as AcceptProofDto, ); break; case PROOF_DECLINE: dto = event.data as IdReqDto; - data = await this.agentService.declineProofRequest(dto.id); + data = await this.agentProofsService.declineProofRequest(dto.id); break; case PROOF_DELETE: dto = event.data as IdReqDto; - data = await this.agentService.deleteProofById(dto.id); + data = await this.agentProofsService.deleteProofById(dto.id); break; case MESSAGE_MAKE: dto = event.data as MakeBasicMessageRequestDto; - data = await this.agentService.sendMessage(dto); + data = await this.agentBasicMessagesService.sendMessage(dto); break; case MESSAGE_LIST: dto = event.data as MessageFilterDto; - data = await this.agentService.fetchBasicMessages(dto); + data = await this.agentBasicMessagesService.fetchBasicMessages(dto); break; case MESSAGE_DELETE: dto = event.data as IdReqDto; - data = await this.agentService.deleteMessageById(dto.id); + data = await this.agentBasicMessagesService.deleteMessageById(dto.id); break; } diff --git a/libs/askar/src/askar-rest/askar.rest.module.ts b/libs/askar/src/askar-rest/askar.rest.module.ts index 6a320c0fde2e48e4775bcda5c88ff6a9bf2787b5..853cc04e0e4b64edcd422544052bb782ae381873 100644 --- a/libs/askar/src/askar-rest/askar.rest.module.ts +++ b/libs/askar/src/askar-rest/askar.rest.module.ts @@ -1,5 +1,12 @@ import { Module, ValidationPipe } from "@nestjs/common"; -import { AgentService } from "../askar/agent.service"; +import { AgentAnoncredsService } from "../askar/agent.anoncreds.service"; +import { AgentBasicMessagesService } from "../askar/agent.basicMessages.service"; +import { AgentConnectionsService } from "../askar/agent.connections.service"; +import { AgentCredentialsService } from "../askar/agent.credentials.service"; +import { AgentDidsService } from "../askar/agent.dids.service"; +import { AgentJsonldService } from "../askar/agent.jsonld.service"; +import { AgentOobService } from "../askar/agent.oob.service"; +import { AgentProofsService } from "../askar/agent.proofs.service"; import { ConfigModule } from "@nestjs/config"; import { LedgersModule } from "@ocm-engine/ledgers"; import { APP_PIPE } from "@nestjs/core"; @@ -17,7 +24,14 @@ import { JwtModule } from "@nestjs/jwt"; }), ], providers: [ - AgentService, + AgentAnoncredsService, + AgentBasicMessagesService, + AgentConnectionsService, + AgentCredentialsService, + AgentDidsService, + AgentJsonldService, + AgentOobService, + AgentProofsService, { provide: APP_PIPE, useValue: new ValidationPipe({ diff --git a/libs/askar/src/askar-rest/rest.controller.ts b/libs/askar/src/askar-rest/rest.controller.ts index dfffbdc404afe832a78b24d39f2facc4fff72eda..ceacdf14535b53f2fa1be67d92be6b84e5f9d60b 100644 --- a/libs/askar/src/askar-rest/rest.controller.ts +++ b/libs/askar/src/askar-rest/rest.controller.ts @@ -9,8 +9,14 @@ import { UseFilters, UseGuards, } from "@nestjs/common"; - -import { AgentService } from "../askar/agent.service"; +import { AgentAnoncredsService } from "../askar/agent.anoncreds.service"; +import { AgentBasicMessagesService } from "../askar/agent.basicMessages.service"; +import { AgentConnectionsService } from "../askar/agent.connections.service"; +import { AgentCredentialsService } from "../askar/agent.credentials.service"; +import { AgentDidsService } from "../askar/agent.dids.service"; +import { AgentJsonldService } from "../askar/agent.jsonld.service"; +import { AgentOobService } from "../askar/agent.oob.service"; +import { AgentProofsService } from "../askar/agent.proofs.service"; import { CreateCredentialDefinitionRequestDto, OfferCredentialRequestDto, @@ -38,28 +44,37 @@ import { AuthGuard } from "./auth/auth.guard"; @Controller("v1") @UseGuards(AuthGuard) export class RestController { - constructor(private readonly agentService: AgentService) {} + constructor( + private readonly agentAnoncredsService: AgentAnoncredsService, + private readonly agentBasicMessagesService: AgentBasicMessagesService, + private readonly agentConnectionsService: AgentConnectionsService, + private readonly agentCredentialsService: AgentCredentialsService, + private readonly agentDidsService: AgentDidsService, + private readonly agentJsonldService: AgentJsonldService, + private readonly agentOobService: AgentOobService, + private readonly agentProofsService: AgentProofsService, + ) {} @Get("/invitations") async fetchInvitations(@Query() filter: InvitationFilterDto) { - return this.agentService.fetchInvitations(filter); + return this.agentOobService.fetchInvitations(filter); } @Post("/invitations") createInvitation( @Body() createInvitationRequestDto: CreateInvitationRequestDto, ) { - return this.agentService.createInvitation(createInvitationRequestDto); + return this.agentOobService.createInvitation(createInvitationRequestDto); } @Get("/invitations/:id") getInvitationById(@Param("id") id: string) { - return this.agentService.getInvitationById(id); + return this.agentOobService.getInvitationById(id); } @Delete("/invitations/:id") deleteInvitationById(@Param("id") id: string) { - return this.agentService.deleteInvitationById(id); + return this.agentOobService.deleteInvitationById(id); } @Post("/invitations/accept") @@ -70,186 +85,186 @@ export class RestController { createInvitationDto.invitationUrl || createInvitationDto.shortInvitationUrl; - return this.agentService.acceptInvitation(url); + return this.agentOobService.acceptInvitation(url); } @Get("/connections") async fetchConnections() { - return this.agentService.fetchConnections(); + return this.agentConnectionsService.fetchConnections(); } @Get("/connections/:id") async getConnectionById(@Param("id") id: string) { - return this.agentService.getConnectionById(id); + return this.agentConnectionsService.getConnectionById(id); } @Delete("/connections/:id") async deleteConnectionById(@Param("id") id: string) { - return this.agentService.deleteConnectionById(id); + return this.agentConnectionsService.deleteConnectionById(id); } @Get("/connections/oob/:id") async getConnectionByOobId(@Param("id") id: string) { - return this.agentService.getConnectionByOobId(id); + return this.agentConnectionsService.getConnectionByOobId(id); } @Post("/connections/ping/:id") async pingConnection(@Param("id") id: string) { - return this.agentService.trustPingToConnection(id); + return this.agentConnectionsService.trustPingToConnection(id); } @Post("/schemas") async createSchema(@Body() schemaDto: CreateSchemaRequestDto) { - return this.agentService.createSchema(schemaDto); + return this.agentAnoncredsService.createSchema(schemaDto); } @Post("/schemas/get-by-id") async getSchemaById(@Body() dto: IdReqDto) { - return this.agentService.getSchemaById(dto.id); + return this.agentAnoncredsService.getSchemaById(dto.id); } @Get("/schemas") async fetchSchemas() { - return this.agentService.fetchSchemas(); + return this.agentAnoncredsService.fetchSchemas(); } @Get("/definitions") async fetchCredentialDefinitions() { - return this.agentService.fetchCredentialDefinitions(); + return this.agentAnoncredsService.fetchCredentialDefinitions(); } @Post("/definitions/get-by-id") async getCredentialDefinitionById(@Body() dto: IdReqDto) { - return this.agentService.getCredentialDefinitionById(dto.id); + return this.agentAnoncredsService.getCredentialDefinitionById(dto.id); } @Post("/definitions") async createCredentialDefinition( @Body() credentialDefinitionDto: CreateCredentialDefinitionRequestDto, ) { - return this.agentService.createCredentialDefinition( + return this.agentAnoncredsService.createCredentialDefinition( credentialDefinitionDto, ); } @Post("/credentials/offers") async offerCredential(@Body() dto: OfferCredentialRequestDto) { - return this.agentService.offerCredential(dto); + return this.agentCredentialsService.offerCredential(dto); } @Post("/credentials/jsonld/offers") async offerJsonLdCredential(@Body() data: OfferJsonCredentialRequests) { - return this.agentService.offerJsonLdCredential(data.connectionId, data.doc); + return this.agentJsonldService.offerJsonLdCredential(data.connectionId, data.doc); } @Post("/jsonld/sign") async signJsonLdCredential(@Body() data: SignJsonCredentialRequests) { - return this.agentService.signJsonLdCredential(data.doc); + return this.agentJsonldService.signJsonLdCredential(data.doc); } @Post("/jsonld/credentials/:cred_id/prepare-verifiable-presentation") async signJsonLdPresentationByCredId(@Param("cred_id") credentialId: string) { - return this.agentService.prepareVerifiablePresentationByJsonLdCredId( + return this.agentJsonldService.prepareVerifiablePresentationByJsonLdCredId( credentialId, ); } @Get("/credentials") async fetchCredentials(@Query() credentialFilterDto: CredentialFilterDto) { - return this.agentService.fetchCredentials(credentialFilterDto); + return this.agentCredentialsService.fetchCredentials(credentialFilterDto); } @Get("/credentials/:id") async getCredentialById(@Param("id") credentialId: string) { - return this.agentService.getCredentialById(credentialId); + return this.agentCredentialsService.getCredentialById(credentialId); } @Get("/credentials/:id/format-data") async getCredentialFormatDataById(@Param("id") credentialId: string) { - return this.agentService.getCredentialFormatDataById(credentialId); + return this.agentCredentialsService.getCredentialFormatDataById(credentialId); } @Post("/credentials/offers/accept") async acceptCredential(@Body() dto: AcceptCredentialDto) { - return this.agentService.acceptCredential(dto); + return this.agentCredentialsService.acceptCredential(dto); } @Post("/credentials/offers/:credential_record_id/decline") async declineCredential( @Param("credential_record_id") credentialRecordId: string, ) { - return this.agentService.declineCredential(credentialRecordId); + return this.agentCredentialsService.declineCredential(credentialRecordId); } @Delete("/credentials/:id") async deleteCredentialById(@Param("id") credentialId: string) { - return this.agentService.deleteCredentialById(credentialId); + return this.agentCredentialsService.deleteCredentialById(credentialId); } @Post("/messages") async sendMessage(@Body() message: MakeBasicMessageRequestDto) { - return this.agentService.sendMessage(message); + return this.agentBasicMessagesService.sendMessage(message); } @Get("/messages") async fetchBasicMessages(@Query() filter: MessageFilterDto) { - return this.agentService.fetchBasicMessages(filter); + return this.agentBasicMessagesService.fetchBasicMessages(filter); } @Delete("/messages/:id") async deleteBasicMessage(@Param("id") messageId: string) { - return this.agentService.deleteMessageById(messageId); + return this.agentBasicMessagesService.deleteMessageById(messageId); } @Get("/proofs") async fetchProofs(@Query() proofFilterDto: ProofFilterDto) { - return this.agentService.fetchProofs(proofFilterDto); + return this.agentProofsService.fetchProofs(proofFilterDto); } @Get("/proofs/:proof_record_id") async getProofById(@Param("proof_record_id") proofRecordId: string) { - return this.agentService.getProofById(proofRecordId); + return this.agentProofsService.getProofById(proofRecordId); } @Get("/proofs/:proof_record_id/format-data") async getProofFormatDataById( @Param("proof_record_id") proofRecordId: string, ) { - return this.agentService.getProofFormatDataById(proofRecordId); + return this.agentProofsService.getProofFormatDataById(proofRecordId); } @Post("/proofs/:proof_record_id/acceptance-wait") async proofAcceptanceWait(@Param("proof_record_id") proofRecordId: string) { - return this.agentService.proofAcceptanceWait(proofRecordId); + return this.agentProofsService.proofAcceptanceWait(proofRecordId); } @Post("/proofs/request") async requestProof(@Body() requestProofDto: RequestProofDto) { - return this.agentService.requestProof(requestProofDto); + return this.agentProofsService.requestProof(requestProofDto); } @Post(`/proofs/accept`) async acceptProof(@Body() acceptProofRequestDto: AcceptProofDto) { - return this.agentService.acceptProof(acceptProofRequestDto); + return this.agentProofsService.acceptProof(acceptProofRequestDto); } @Post("/proofs/:proof_record_id/decline") async declineProofRequest(@Param("proof_record_id") proofRecordId: string) { - return this.agentService.declineProofRequest(proofRecordId); + return this.agentProofsService.declineProofRequest(proofRecordId); } @Delete("/proofs/:proof_record_id") async deleteProofById(@Param("proof_record_id") proofRecordId: string) { - return this.agentService.deleteProofById(proofRecordId); + return this.agentProofsService.deleteProofById(proofRecordId); } @Get("/created-dids") async getCreatedDids(): Promise<DidRecordDto[]> { - return this.agentService.getCreatedDids(); + return this.agentDidsService.getCreatedDids(); } @Post("/resolve-did") async resolveDid(@Body() dto: IdReqDto): Promise<DidResolutionResult> { - return this.agentService.resolve(dto.id); + return this.agentDidsService.resolve(dto.id); } } diff --git a/libs/askar/src/askar/agent.anoncreds.service.ts b/libs/askar/src/askar/agent.anoncreds.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..34a454dcfe8c1221cb3f427b377c43c178068de3 --- /dev/null +++ b/libs/askar/src/askar/agent.anoncreds.service.ts @@ -0,0 +1,173 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + CreateCredentialDefinitionRequestDto, + CreddefRecordDto, + CreateSchemaRequestDto, + SchemaRecordDto, + CredentialNotCreatedError, + SchemaNotCreatedError, + EntityNotFoundError, +} from "@ocm-engine/dtos"; + +@Injectable() +export class AgentAnoncredsService { + private readonly logger = new Logger(AgentAnoncredsService.name); + constructor(private readonly askar: AskarService) {} + + fetchSchemas = async (): Promise<SchemaRecordDto[]> => { + let schemaRecords = + await this.askar.agent.modules.anoncreds.getCreatedSchemas({}); + schemaRecords = schemaRecords.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const schemaResponses = schemaRecords.map((singleSchemaRes) => { + const schemaResponse = new SchemaRecordDto(); + schemaResponse.id = singleSchemaRes.schemaId; + schemaResponse.createdAt = singleSchemaRes.createdAt; + schemaResponse.updatedAt = singleSchemaRes.updatedAt; + schemaResponse.name = singleSchemaRes.schema.name; + schemaResponse.attributes = singleSchemaRes.schema.attrNames; + schemaResponse.version = singleSchemaRes.schema.version; + schemaResponse.issuerId = singleSchemaRes.schema.issuerId; + schemaResponse.methodName = singleSchemaRes.methodName; + + return schemaResponse; + }); + + return schemaResponses; + }; + + getSchemaById = async (schemaId: string): Promise<SchemaRecordDto> => { + const agentResponse = await this.askar.agent.modules.anoncreds.getSchema( + schemaId, + ); + + if (!agentResponse || !agentResponse.schema) { + throw new EntityNotFoundError(); + } + + const schemaResponse = new SchemaRecordDto(); + schemaResponse.id = agentResponse.schemaId; + schemaResponse.name = agentResponse.schema.name; + schemaResponse.attributes = agentResponse.schema.attrNames; + schemaResponse.version = agentResponse.schema.version; + schemaResponse.issuerId = agentResponse.schema.issuerId; + + return schemaResponse; + }; + + createSchema = async ( + schema: CreateSchemaRequestDto, + ): Promise<SchemaRecordDto> => { + const dids = await this.askar.agent.dids.getCreatedDids({ method: "indy" }); + + const schemaResult = + await this.askar.agent.modules.anoncreds.registerSchema({ + schema: { + name: schema.name, + issuerId: dids[0].did, + attrNames: schema.attributes, + version: schema.version, + }, + options: {}, + }); + + if (schemaResult.schemaState.state !== "finished") { + throw new SchemaNotCreatedError(); + } + + const response = new SchemaRecordDto(); + + response.name = schemaResult.schemaState.schema.name; + response.id = schemaResult.schemaState.schemaId; + response.issuerId = schemaResult.schemaState.schema.issuerId; + response.version = schemaResult.schemaState.schema.version; + response.attributes = schemaResult.schemaState.schema.attrNames; + + return response; + }; + + fetchCredentialDefinitions = async (): Promise<CreddefRecordDto[]> => { + let credentialDefinitions = + await this.askar.agent.modules.anoncreds.getCreatedCredentialDefinitions( + {}, + ); + credentialDefinitions = credentialDefinitions.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const response: Array<CreddefRecordDto> = []; + for (const credDef of credentialDefinitions) { + const cd = new CreddefRecordDto(); + cd.id = credDef.credentialDefinitionId; + cd.createdAt = credDef.createdAt; + cd.updatedAt = credDef.updatedAt; + cd.schemaId = credDef.credentialDefinition.schemaId; + cd.issuerId = credDef.credentialDefinition.issuerId; + cd.tag = credDef.credentialDefinition.tag; + response.push(cd); + } + + return response; + }; + + getCredentialDefinitionById = async ( + credentialDefinitionId: string, + ): Promise<CreddefRecordDto> => { + const credDefs = + await this.askar.agent.modules.anoncreds.getCreatedCredentialDefinitions({ + credentialDefinitionId, + }); + + const credDef = credDefs[0] || null; + if (!credDef) { + throw new EntityNotFoundError(); + } + + const cd = new CreddefRecordDto(); + cd.id = credDef.credentialDefinitionId; + cd.createdAt = credDef.createdAt; + cd.updatedAt = credDef.updatedAt; + cd.schemaId = credDef.credentialDefinition.schemaId; + cd.issuerId = credDef.credentialDefinition.issuerId; + cd.tag = credDef.credentialDefinition.tag; + + return cd; + }; + + createCredentialDefinition = async ( + credentialDefinitionDto: CreateCredentialDefinitionRequestDto, + ): Promise<CreddefRecordDto> => { + const dids = await this.askar.agent.dids.getCreatedDids({ method: "indy" }); + + const credDef = + await this.askar.agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition: { + tag: credentialDefinitionDto.tag, + issuerId: dids[0].did, + schemaId: credentialDefinitionDto.schemaId, + }, + options: { + supportRevocation: credentialDefinitionDto.supportRevocation ?? false, + }, + }); + + if (credDef.credentialDefinitionState.state !== "finished") { + throw new CredentialNotCreatedError(); + } + + const response = new CreddefRecordDto(); + response.id = credDef.credentialDefinitionState.credentialDefinitionId; + response.schemaId = + credDef.credentialDefinitionState.credentialDefinition.schemaId; + response.issuerId = + credDef.credentialDefinitionState.credentialDefinition.issuerId; + response.tag = credDef.credentialDefinitionState.credentialDefinition.tag; + + return response; + }; +} diff --git a/libs/askar/src/askar/agent.basicMessages.service.ts b/libs/askar/src/askar/agent.basicMessages.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f9454ea8a107799322aa48f00b1e08333d10740 --- /dev/null +++ b/libs/askar/src/askar/agent.basicMessages.service.ts @@ -0,0 +1,111 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + MakeBasicMessageRequestDto, + MessageRecordDto, + MessageFilterDto, +} from "@ocm-engine/dtos"; +import { + BasicMessageRecord, + BasicMessageRole, + ConnectionRecord, + Query, +} from "@credo-ts/core"; + +@Injectable() +export class AgentBasicMessagesService { + private readonly logger = new Logger(AgentBasicMessagesService.name); + constructor(private readonly askar: AskarService) {} + + fetchBasicMessages = async ( + filter: MessageFilterDto, + ): Promise<MessageRecordDto[]> => { + const query: Query<BasicMessageRecord>[] = []; + + if (filter.role) { + const roleQuery: Query<BasicMessageRecord> = { + role: filter.role, + }; + query.push(roleQuery); + } + + if (filter.connectionId) { + const connectionQuery: Query<BasicMessageRecord> = { + connectionId: filter.connectionId, + }; + query.push(connectionQuery); + } + + let messages = await this.askar.agent.basicMessages.findAllByQuery({ + $and: query, + }); + messages = messages.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const connectionIds = messages.map((message) => message.connectionId); + + const connections = await this.askar.agent.connections.findAllByQuery({ + $or: connectionIds.map((p) => ({ id: p })), + }); + const grouppedConnections = connections.reduce( + (acc: { [connId: string]: ConnectionRecord }, conn) => { + acc[conn.id] = conn; + return acc; + }, + {}, + ); + + const response: MessageRecordDto[] = []; + for (const message of messages) { + const connection = grouppedConnections[message.connectionId]; + const label = connection?.theirLabel || ""; + + const t = new MessageRecordDto(); + t.id = message.id; + t.createdAt = message.createdAt; + t.updatedAt = message.updatedAt; + t.connectionId = message.connectionId; + t.role = message.role; + t.content = message.content; + t.sentTime = message.sentTime; + t.from = message.role === BasicMessageRole.Receiver ? label : ""; + t.to = message.role === BasicMessageRole.Sender ? label : ""; + + response.push(t); + } + + return response; + }; + + sendMessage = async ( + dto: MakeBasicMessageRequestDto, + ): Promise<MessageRecordDto> => { + const messageRecord = await this.askar.agent.basicMessages.sendMessage( + dto.connectionId, + dto.message, + ); + + const connRecord = await this.askar.agent.connections.findById( + dto.connectionId, + ); + + const response = new MessageRecordDto(); + response.id = messageRecord.id; + response.createdAt = messageRecord.createdAt; + response.updatedAt = messageRecord.updatedAt; + response.connectionId = messageRecord.connectionId; + response.role = messageRecord.role; + response.content = messageRecord.content; + response.sentTime = messageRecord.sentTime; + response.from = ""; + response.to = connRecord?.theirLabel || ""; + + return response; + }; + + deleteMessageById = async (id: string): Promise<void> => { + await this.askar.agent.basicMessages.deleteById(id); + }; +} diff --git a/libs/askar/src/askar/agent.connections.service.ts b/libs/askar/src/askar/agent.connections.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff7f7d6de587f3596d6c7f5949ed233d29f08c66 --- /dev/null +++ b/libs/askar/src/askar/agent.connections.service.ts @@ -0,0 +1,106 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + ConnectionRecordDto, + EntityNotFoundError, + BaseRecordDto, +} from "@ocm-engine/dtos"; + +@Injectable() +export class AgentConnectionsService { + private readonly logger = new Logger(AgentConnectionsService.name); + constructor(private readonly askar: AskarService) {} + + async fetchConnections(): Promise<ConnectionRecordDto[]> { + //TODO: no ordering in findAllByQuery + const agentResponse = (await this.askar.agent.connections.getAll()).sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const connectionArray = agentResponse.map((singleConnectionRes) => { + const connectionResponse = new ConnectionRecordDto(); + connectionResponse.id = singleConnectionRes.id; + connectionResponse.state = singleConnectionRes.state; + connectionResponse.connectionName = singleConnectionRes.theirLabel; + connectionResponse.alias = singleConnectionRes.alias; + connectionResponse.did = singleConnectionRes.did; + connectionResponse.theirDid = singleConnectionRes.theirDid; + connectionResponse.invitationDid = singleConnectionRes.invitationDid; + connectionResponse.outOfBandId = singleConnectionRes.outOfBandId; + connectionResponse.createdAt = singleConnectionRes.createdAt; + connectionResponse.updatedAt = singleConnectionRes.updatedAt; + connectionResponse.imageUrl = singleConnectionRes.imageUrl; + + return connectionResponse; + }); + + return connectionArray; + } + + getConnectionByOobId = async (oobId: string) => { + const connectionRecords = + await this.askar.agent.connections.findAllByOutOfBandId(oobId); + + const connectionArray = connectionRecords.map((singleConnectionRes) => { + const connectionResponse = new ConnectionRecordDto(); + connectionResponse.id = singleConnectionRes.id; + connectionResponse.state = singleConnectionRes.state; + connectionResponse.connectionName = singleConnectionRes.theirLabel; + connectionResponse.alias = singleConnectionRes.alias; + connectionResponse.did = singleConnectionRes.did; + connectionResponse.theirDid = singleConnectionRes.theirDid; + connectionResponse.invitationDid = singleConnectionRes.invitationDid; + connectionResponse.outOfBandId = singleConnectionRes.outOfBandId; + connectionResponse.createdAt = singleConnectionRes.createdAt; + connectionResponse.updatedAt = singleConnectionRes.updatedAt; + connectionResponse.imageUrl = singleConnectionRes.imageUrl; + + return connectionResponse; + }); + + return connectionArray; + }; + + getConnectionById = async (id: string): Promise<ConnectionRecordDto> => { + const agentResponse = await this.askar.agent.connections.findById(id); + + if (!agentResponse) { + throw new EntityNotFoundError(); + } + + const connectionResponse = new ConnectionRecordDto(); + connectionResponse.id = agentResponse.id; + connectionResponse.state = agentResponse.state; + connectionResponse.connectionName = agentResponse.theirLabel; + connectionResponse.alias = agentResponse.alias; + connectionResponse.did = agentResponse.did; + connectionResponse.theirDid = agentResponse.theirDid; + connectionResponse.invitationDid = agentResponse.invitationDid; + connectionResponse.outOfBandId = agentResponse.outOfBandId; + connectionResponse.createdAt = agentResponse.createdAt; + connectionResponse.updatedAt = agentResponse.updatedAt; + connectionResponse.imageUrl = agentResponse.imageUrl; + + return connectionResponse; + }; + + deleteConnectionById = async (id: string): Promise<void> => { + await this.askar.agent.connections.hangup({ + connectionId: id, + deleteAfterHangup: true + }); + }; + + trustPingToConnection = async (connectionId: string) => { + const msg = await this.askar.agent.connections.sendPing(connectionId, { + responseRequested: true, + withReturnRouting: false, + }); + + const response = new BaseRecordDto(); + response.id = msg.toJSON()["@id"]; + + return response; + }; +} diff --git a/libs/askar/src/askar/agent.credentials.service.ts b/libs/askar/src/askar/agent.credentials.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..1adcb8568c9c34aa4955799e06159347c3e7b57b --- /dev/null +++ b/libs/askar/src/askar/agent.credentials.service.ts @@ -0,0 +1,282 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + OfferCredentialRequestDto, + CredentialRecordDto, + CredentialFilterDto, + CredentialOfferResponseDto, + EntityNotFoundError, + AcceptCredentialDto, + CredentialFormatDataDto, +} from "@ocm-engine/dtos"; +import { + AutoAcceptCredential, + CredentialExchangeRecord, + CredentialState, + Query, +} from "@credo-ts/core"; +import { + waitForCredentialExchangeRecordSubject, +} from "../agent.utils"; + +@Injectable() +export class AgentCredentialsService { + private readonly logger = new Logger(AgentCredentialsService.name); + constructor(private readonly askar: AskarService) {} + + offerCredential = async ( + offerCredentialDto: OfferCredentialRequestDto, + ): Promise<CredentialOfferResponseDto> => { + this.logger.log( + "Incoming request", + JSON.stringify(offerCredentialDto, null, 2), + ); + + if (!offerCredentialDto.connectionId) { + const { credentialRecord, message } = + await this.askar.agent.credentials.createOffer({ + protocolVersion: "v2", + credentialFormats: { + anoncreds: { + credentialDefinitionId: offerCredentialDto.credentialDefinitionId, + attributes: offerCredentialDto.attributes, + }, + }, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }); + + credentialRecord.setTag("xRole", "issuer"); + await this.askar.agent.credentials.update(credentialRecord); + + const outOfBandRecord = await this.askar.agent.oob.createInvitation({ + messages: [message], + handshake: false, + }); + + const credentialUrl = outOfBandRecord.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + + const shortCredentialUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; + + const dto = new CredentialRecordDto(); + dto.id = credentialRecord.id; + dto.state = credentialRecord.state; + dto.connectionId = credentialRecord.connectionId; + dto.attributes = credentialRecord.credentialAttributes; + dto.createdAt = credentialRecord.createdAt; + dto.tags = credentialRecord.getTags(); + + return { + credentialUrl: credentialUrl, + shortCredentialUrl: shortCredentialUrl, + credentialRecord: dto, + }; + } + const credentialExchangeRecord = + await this.askar.agent.credentials.offerCredential({ + protocolVersion: "v2", + connectionId: offerCredentialDto.connectionId, + credentialFormats: { + anoncreds: { + credentialDefinitionId: offerCredentialDto.credentialDefinitionId, + attributes: offerCredentialDto.attributes, + }, + }, + }); + + credentialExchangeRecord.setTag("xRole", "issuer"); + await this.askar.agent.credentials.update(credentialExchangeRecord); + + const dto = new CredentialRecordDto(); + dto.id = credentialExchangeRecord.id; + dto.state = credentialExchangeRecord.state; + dto.connectionId = credentialExchangeRecord.connectionId; + dto.attributes = credentialExchangeRecord.credentialAttributes; + dto.createdAt = credentialExchangeRecord.createdAt; + dto.tags = credentialExchangeRecord.getTags(); + + return { + credentialUrl: null, + shortCredentialUrl: null, + credentialRecord: dto, + }; + }; + + acceptCredential = async ( + acceptCredentialDto: AcceptCredentialDto, + ): Promise<CredentialRecordDto> => { + if (acceptCredentialDto.credentialUrl) { + return this.acceptOobCredentials(acceptCredentialDto.credentialUrl); + } + return this.acceptConnectionCredential(acceptCredentialDto.credentialId); + }; + + + acceptOobCredentials = async (url: string): Promise<CredentialRecordDto> => { + // omit await in order to catch received record in the next line + setTimeout(() => { + this.askar.agent.oob.receiveInvitationFromUrl(url, { + autoAcceptConnection: false, + autoAcceptInvitation: true, + // reuseConnection: true, + }); + }, 20); + + const record = await waitForCredentialExchangeRecordSubject( + this.askar.agentB, + { + state: CredentialState.OfferReceived, + }, + ); + + const acceptedRecord = await this.askar.agent.credentials.acceptOffer({ + credentialRecordId: record.id, + }); + + const response = new CredentialRecordDto(); + response.id = acceptedRecord.id; + response.state = acceptedRecord.state; + response.connectionId = acceptedRecord.connectionId; + response.attributes = acceptedRecord.credentialAttributes; + response.createdAt = acceptedRecord.createdAt; + response.tags = acceptedRecord.getTags(); + + return response; + }; + + acceptConnectionCredential = async ( + credentialRecordId: string, + ): Promise<CredentialRecordDto> => { + const credentialExchangeRecord = + await this.askar.agent.credentials.acceptOffer({ + credentialRecordId, + }); + + const response = new CredentialRecordDto(); + response.id = credentialExchangeRecord.id; + response.state = credentialExchangeRecord.state; + response.connectionId = credentialExchangeRecord.connectionId; + response.attributes = credentialExchangeRecord.credentialAttributes; + response.createdAt = credentialExchangeRecord.createdAt; + response.tags = credentialExchangeRecord.getTags(); + + return response; + }; + + declineCredential = async ( + credentialRecordId: string, + ): Promise<CredentialRecordDto> => { + const credentialExchangeRecord = + await this.askar.agent.credentials.declineOffer(credentialRecordId); + + // send request to the issuer that the request is declined, to mark it as Abondoned + await this.askar.agent.credentials.sendProblemReport({ + credentialRecordId: credentialRecordId, + description: "Decline offer", + }); + + const response = new CredentialRecordDto(); + response.id = credentialExchangeRecord.id; + response.state = credentialExchangeRecord.state; + response.connectionId = credentialExchangeRecord.connectionId; + response.attributes = credentialExchangeRecord.credentialAttributes; + response.createdAt = credentialExchangeRecord.createdAt; + response.tags = credentialExchangeRecord.getTags(); + + return response; + }; + + fetchCredentials = async ( + filter: CredentialFilterDto, + ): Promise<CredentialRecordDto[]> => { + const query: Query<CredentialExchangeRecord>[] = []; + + if (filter.states) { + const stateQuery: Query<CredentialExchangeRecord> = { + $or: filter.states.map((state) => ({ state })), + }; + query.push(stateQuery); + } + + if (filter.connectionId) { + const connectionQuery: Query<CredentialExchangeRecord> = { + connectionId: filter.connectionId, + }; + query.push(connectionQuery); + } + + let credentials = await this.askar.agent.credentials.findAllByQuery({ + $and: query, + }); + credentials = credentials.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const response: CredentialRecordDto[] = []; + for (const offer of credentials) { + const t = new CredentialRecordDto(); + t.id = offer.id; + t.state = offer.state; + t.connectionId = offer.connectionId; + t.createdAt = offer.createdAt; + t.attributes = offer.credentialAttributes; + t.tags = offer.getTags(); + response.push(t); + } + + return response; + }; + + getCredentialById = async ( + credentialId: string, + ): Promise<CredentialRecordDto> => { + const credentialRecord = await this.askar.agent.credentials.findById( + credentialId, + ); + + if (!credentialRecord) { + throw new EntityNotFoundError(); + } + + const credential = new CredentialRecordDto(); + credential.id = credentialRecord.id; + credential.state = credentialRecord.state; + credential.connectionId = credentialRecord.connectionId; + credential.createdAt = credentialRecord.createdAt; + credential.attributes = credentialRecord.credentialAttributes; + credential.tags = credentialRecord.getTags(); + + return credential; + }; + + getCredentialFormatDataById = async ( + credentialId: string, + ): Promise<CredentialFormatDataDto> => { + + const formatData = await this.askar.agent.credentials.getFormatData( + credentialId, + ); + + if (!formatData) { + throw new EntityNotFoundError(); + } + + const dto = new CredentialFormatDataDto(); + dto.proposalAttributes = formatData.proposalAttributes; + dto.offerAttributes = formatData.offerAttributes; + dto.anoncredsProposal = formatData.proposal?.anoncreds; + dto.anoncredsOffer = formatData.offer?.anoncreds; + dto.anoncredsRequest = formatData.request?.anoncreds; + dto.anoncredsCredential = formatData.credential?.anoncreds; + + dto.all = formatData; + + return dto; + }; + + deleteCredentialById = async (id: string): Promise<void> => { + await this.askar.agent.credentials.deleteById(id); + }; +} diff --git a/libs/askar/src/askar/agent.dids.service.ts b/libs/askar/src/askar/agent.dids.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd8b16bc64eff468a49f14ddf8dd2a6a42113f17 --- /dev/null +++ b/libs/askar/src/askar/agent.dids.service.ts @@ -0,0 +1,31 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + DidRecordDto, +} from "@ocm-engine/dtos"; + +@Injectable() +export class AgentDidsService { + private readonly logger = new Logger(AgentDidsService.name); + constructor(private readonly askar: AskarService) {} + + resolve = async (did: string) => { + return this.askar.agent.dids.resolve(did); + }; + + getCreatedDids = async (): Promise<DidRecordDto[]> => { + const didRecords = await this.askar.agent.dids.getCreatedDids(); + return didRecords.map((p) => { + const dto = new DidRecordDto(); + const tags = p.getTags(); + + dto.did = p.did; + dto.role = p.role; + dto.method = tags.method; + dto.tags = tags; + + return dto; + }); + }; + +} diff --git a/libs/askar/src/askar/agent.jsonld.service.ts b/libs/askar/src/askar/agent.jsonld.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..901c3ed8748885dd51357f418995a26ebffe86c3 --- /dev/null +++ b/libs/askar/src/askar/agent.jsonld.service.ts @@ -0,0 +1,263 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + CredentialRecordDto, + CredentialOfferResponseDto, + EntityNotFoundError, + OcmError, + W3cCredentialDto, + W3cJsonLdVerifiableCredentialDto, + W3cJsonLdVerifiablePresentationDto, +} from "@ocm-engine/dtos"; +import { + AutoAcceptCredential, + ClaimFormat, + JsonTransformer, + W3cCredential, + W3cCredentialService, + JsonCredential, + W3cJsonLdVerifiableCredential, +} from "@credo-ts/core"; +import { uuid } from "@credo-ts/core/build/utils/uuid"; +import { + getFirstDidWebRecord, +} from "../agent.utils"; + +@Injectable() +export class AgentJsonldService { + private readonly logger = new Logger(AgentJsonldService.name); + constructor(private readonly askar: AskarService) {} + + signJsonLdCredential = async ( + credToSign: W3cCredentialDto, + ): Promise<W3cJsonLdVerifiableCredentialDto> => { + this.logger.log("Sign json ld credentials"); + + const didRecord = await getFirstDidWebRecord(this.askar.agent); + const verificationMethodList = + didRecord.didDocument?.verificationMethod || []; + if (!verificationMethodList.length) { + throw new EntityNotFoundError( + "DidDocument does not exists or contains no verification methods", + ); + } + + const verificationMethod = verificationMethodList[0]; + + const w3cServ = + this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); + + credToSign.id = didRecord.did + "?uuid=" + uuid(); + if (credToSign.credentialSubject) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + credToSign.credentialSubject.id = credToSign.id; + } + credToSign.issuer = didRecord.did; + credToSign.issuanceDate = new Date().toISOString(); + let credential: W3cCredential; + try { + credential = JsonTransformer.fromJSON(credToSign, W3cCredential); + } catch (e) { + this.logger.log("Incorrect request parameter", e); + throw new OcmError( + "Invalid JSON-LD data format. Please ensure that your JSON-LD contains the following properties: @context, id, type, issuer, issuanceDate, expirationDate, and credentialSubject.", + ); + } + + const vc = await w3cServ.signCredential(this.askar.agent.context, { + format: ClaimFormat.LdpVc, + credential, + proofType: "Ed25519Signature2018", + verificationMethod: verificationMethod.id, + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const jsonVC = vc.toJson() as W3cJsonLdVerifiableCredentialDto; + this.logger.debug(JSON.stringify(jsonVC, null, 2)); + + return jsonVC; + }; + + prepareVerifiablePresentationByJsonLdCredId = async ( + credentialRecordId: string, + ): Promise<W3cJsonLdVerifiablePresentationDto> => { + const didRecord = await getFirstDidWebRecord(this.askar.agent); + const verificationMethodList = + didRecord.didDocument?.verificationMethod || []; + if (!verificationMethodList.length) { + throw new EntityNotFoundError( + "DidDocument does not exists or contains no verification methods", + ); + } + + const verificationMethod = verificationMethodList[0]; + + const credFormatData = await this.askar.agent.credentials.getFormatData( + credentialRecordId, + ); + if (!credFormatData.credential?.jsonld) { + throw new OcmError( + "The JSON-LD credential is either not in your wallet, pending approval, or not in the JSON-LD format.", + ); + } + + const jsonLd = credFormatData.credential.jsonld; + const vc = JsonTransformer.fromJSON(jsonLd, W3cJsonLdVerifiableCredential); + + const w3cServ = + this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); + const presentation = await w3cServ.createPresentation({ + credentials: [vc], + id: didRecord.did + "?uuid=" + uuid(), + }); + + const vp = await w3cServ.signPresentation(this.askar.agent.context, { + format: ClaimFormat.LdpVp, + presentation, + proofPurpose: null, + proofType: "Ed25519Signature2018", + challenge: uuid(), + verificationMethod: verificationMethod.id, + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const jsonVP = vp.toJson() as W3cJsonLdVerifiablePresentationDto; + this.logger.debug(JSON.stringify(jsonVP, null, 2)); + + return jsonVP; + }; + + offerJsonLdCredential = async ( + connectionId: string | undefined, + credToSign: W3cCredentialDto, + ): Promise<CredentialOfferResponseDto> => { + this.logger.log("offerJsonLdCredential", connectionId); + + const didRecord = await getFirstDidWebRecord(this.askar.agent); + const verificationMethodList = + didRecord.didDocument?.verificationMethod || []; + if (!verificationMethodList.length) { + throw new EntityNotFoundError( + "DidDocument does not exists or contains no verification methods", + ); + } + + const verificationMethod = verificationMethodList[0]; + + const w3cServ = + this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); + + credToSign.id = didRecord.did + "?uuid=" + uuid(); + if (credToSign.credentialSubject) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + credToSign.credentialSubject.id = credToSign.id; + } + credToSign.issuer = didRecord.did; + credToSign.issuanceDate = new Date().toISOString(); + let credential: W3cCredential; + try { + credential = JsonTransformer.fromJSON(credToSign, W3cCredential); + } catch (e) { + this.logger.log("Incorrect request parameter", e); + throw new OcmError( + "Invalid JSON-LD data format. Please ensure that your JSON-LD contains the following properties: @context, id, type, issuer, issuanceDate, expirationDate, and credentialSubject.", + ); + } + + const vc = await w3cServ.signCredential(this.askar.agent.context, { + format: ClaimFormat.LdpVc, + credential, + proofType: "Ed25519Signature2018", + verificationMethod: verificationMethod.id, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const jsonVC = vc.toJson() as JsonCredential; + + if (!connectionId) { + // create connection less credential + const { credentialRecord, message } = + await this.askar.agent.credentials.createOffer({ + protocolVersion: "v2", + credentialFormats: { + jsonld: { + credential: jsonVC, + options: { + proofType: "Ed25519Signature2018", + proofPurpose: "assertionMethod", + }, + }, + }, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }); + + credentialRecord.setTag("xRole", "issuer"); + await this.askar.agent.credentials.update(credentialRecord); + + const outOfBandRecord = await this.askar.agent.oob.createInvitation({ + messages: [message], + handshake: false, + }); + + const credentialUrl = outOfBandRecord.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + + const shortCredentialUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; + + const dto = new CredentialRecordDto(); + dto.id = credentialRecord.id; + dto.state = credentialRecord.state; + dto.connectionId = credentialRecord.connectionId; + dto.attributes = credentialRecord.credentialAttributes; + dto.createdAt = credentialRecord.createdAt; + dto.tags = credentialRecord.getTags(); + + return { + credentialUrl: credentialUrl, + shortCredentialUrl: shortCredentialUrl, + credentialRecord: dto, + }; + } + + const credentialExchangeRecord = + await this.askar.agent.credentials.offerCredential({ + connectionId: connectionId, + protocolVersion: "v2", + credentialFormats: { + jsonld: { + credential: jsonVC, + options: { + proofType: "Ed25519Signature2018", + proofPurpose: "assertionMethod", + }, + }, + }, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }); + + this.logger.log(credentialExchangeRecord); + + credentialExchangeRecord.setTag("xRole", "issuer"); + await this.askar.agent.credentials.update(credentialExchangeRecord); + + const dto = new CredentialRecordDto(); + dto.id = credentialExchangeRecord.id; + dto.state = credentialExchangeRecord.state; + dto.connectionId = credentialExchangeRecord.connectionId; + dto.attributes = credentialExchangeRecord.credentialAttributes; + dto.createdAt = credentialExchangeRecord.createdAt; + dto.tags = credentialExchangeRecord.getTags(); + + return { + credentialUrl: null, + shortCredentialUrl: null, + credentialRecord: dto, + }; + }; + +} diff --git a/libs/askar/src/askar/agent.oob.service.ts b/libs/askar/src/askar/agent.oob.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..03cd43fde83195bad82f09d6ede78e110d3d450c --- /dev/null +++ b/libs/askar/src/askar/agent.oob.service.ts @@ -0,0 +1,142 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + ConnectionRecordDto, + CreateInvitationResponseDto, + EntityNotFoundError, + CreateInvitationRequestDto, + InvitationFilterDto, +} from "@ocm-engine/dtos"; +import { + Query, + OutOfBandRecord, +} from "@credo-ts/core"; + +@Injectable() +export class AgentOobService { + private readonly logger = new Logger(AgentOobService.name); + constructor(private readonly askar: AskarService) {} + + createInvitation = async ( + createInvitationRequestDto?: CreateInvitationRequestDto, + ) => { + const outOfBoundRecord = await this.askar.agent.oob.createInvitation( + createInvitationRequestDto, + ); + + const response = new CreateInvitationResponseDto(); + + let longUrl = outOfBoundRecord.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + + if (this.askar.agentConfig.agentOobUrl) { + longUrl = longUrl.replace( + this.askar.agentConfig.agentPeerAddress, + this.askar.agentConfig.agentOobUrl, + ); + } + + //TODO: should we replace the short url with agentOobUrl if we do this we should have a redirect in ingress + response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBoundRecord.outOfBandInvitation.id}`; + response.outOfBandId = outOfBoundRecord.id; + response.createdAt = outOfBoundRecord.createdAt; + response.updatedAt = outOfBoundRecord.updatedAt; + response.role = outOfBoundRecord.role; + response.state = outOfBoundRecord.state; + response.invitationUrl = longUrl; + + return response; + }; + + + acceptInvitation = async ( + invitationUrl: string, + ): Promise<ConnectionRecordDto> => { + const { connectionRecord } = + await this.askar.agent.oob.receiveInvitationFromUrl(invitationUrl); + + if (typeof connectionRecord === "undefined") { + throw new EntityNotFoundError(); + } + + const response = new ConnectionRecordDto(); + response.connectionName = connectionRecord.theirLabel; + response.state = connectionRecord.state; + response.id = connectionRecord.id; + response.did = connectionRecord.did; + response.theirDid = connectionRecord.theirDid; + response.invitationDid = connectionRecord.invitationDid; + response.outOfBandId = connectionRecord.outOfBandId; + response.createdAt = connectionRecord.createdAt; + response.updatedAt = connectionRecord.updatedAt; + response.imageUrl = connectionRecord.imageUrl; + + return response; + }; + + deleteInvitationById = async (id: string) => { + return this.askar.agent.oob.deleteById(id); + }; + + + fetchInvitations = async (filter: InvitationFilterDto) => { + const query: Query<OutOfBandRecord>[] = []; + + if (filter.states) { + const stateQuery: Query<OutOfBandRecord> = { + $or: filter.states.map((state) => ({ state })), + }; + query.push(stateQuery); + } + + if (filter.roles) { + const roleQuery: Query<OutOfBandRecord> = { + $or: filter.roles.map((role) => ({ role })), + }; + + query.push(roleQuery); + } + + let invitations = await this.askar.agent.oob.findAllByQuery({ + $and: query, + }); + invitations = invitations.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const invitationsResponse = invitations.map((invitation) => { + const response = new CreateInvitationResponseDto(); + response.invitationUrl = invitation.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${invitation.outOfBandInvitation.id}`; + response.outOfBandId = invitation.id; + response.createdAt = invitation.createdAt; + response.updatedAt = invitation.updatedAt; + response.role = invitation.role; + response.state = invitation.state; + return response; + }); + + return invitationsResponse; + }; + + getInvitationById = async (oobId: string) => { + const invitation = await this.askar.agent.oob.getById(oobId); + + const response = new CreateInvitationResponseDto(); + response.invitationUrl = invitation.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${invitation.outOfBandInvitation.id}`; + response.outOfBandId = invitation.id; + response.createdAt = invitation.createdAt; + response.updatedAt = invitation.updatedAt; + response.role = invitation.role; + response.state = invitation.state; + return response; + }; + +} diff --git a/libs/askar/src/askar/agent.proofs.service.ts b/libs/askar/src/askar/agent.proofs.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..2eb450f1f8adf1dbb73f51fa8f7f1f97cc780bb5 --- /dev/null +++ b/libs/askar/src/askar/agent.proofs.service.ts @@ -0,0 +1,317 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { AskarService } from "./askar.service"; +import { + AcceptProofDto, + RequestProofDto, + ProofRecordDto, + ProofFilterDto, + RequestProofResponseDto, + EntityNotFoundError, + ProofFormatDataDto, +} from "@ocm-engine/dtos"; +import { + ProofState, + Query, + ProofExchangeRecord, +} from "@credo-ts/core"; +import { AnonCredsRequestedAttribute } from "@credo-ts/anoncreds"; +import { uuid } from "@credo-ts/core/build/utils/uuid"; +import { + + waitForProofExchangeRecordSubject, +} from "../agent.utils"; + +@Injectable() +export class AgentProofsService { + private readonly logger = new Logger(AgentProofsService.name); + constructor(private readonly askar: AskarService) {} + + requestProof = async ( + requestProofDto: RequestProofDto, + ): Promise<RequestProofResponseDto> => { + this.logger.log(JSON.stringify(requestProofDto, null, 2)); + const requestedAttributes: Record<string, AnonCredsRequestedAttribute> = {}; + + for (const attr of requestProofDto.attributes) { + requestedAttributes[uuid()] = { + name: attr.attributeName, + restrictions: [ + { + schema_id: attr.schemaId, + cred_def_id: attr.credentialDefinitionId, + }, + ], + }; + } + + if (!requestProofDto.connectionId) { + this.logger.log("connection Id not detected, creating oob proof"); + const { proofRecord, message } = + await this.askar.agent.proofs.createRequest({ + protocolVersion: "v2", + proofFormats: { + anoncreds: { + name: "proof-request", + version: "1.0", + requested_attributes: requestedAttributes, + }, + }, + }); + + proofRecord.setTag("xRole", "requester"); + await this.askar.agent.proofs.update(proofRecord); + + const outOfBandRecord = await this.askar.agent.oob.createInvitation({ + messages: [message], + handshake: false, + }); + + const proofUrl = outOfBandRecord.outOfBandInvitation.toUrl({ + domain: this.askar.agentConfig.agentPeerAddress, + }); + const shortProofUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; + + const dto = new ProofRecordDto(); + dto.id = proofRecord.id; + dto.connectionId = proofRecord.connectionId; + dto.state = proofRecord.state; + dto.updatedAt = proofRecord.updatedAt; + dto.createdAt = proofRecord.createdAt; + dto.tags = proofRecord.getTags(); + + return { + proofUrl: proofUrl, + shortProofUrl: shortProofUrl, + proofRecord: dto, + }; + } + + this.logger.log(`${requestProofDto.connectionId} detected, issuing proof`); + + const exchangeRecord = await this.askar.agent.proofs.requestProof({ + protocolVersion: "v2", + connectionId: requestProofDto.connectionId, + proofFormats: { + anoncreds: { + name: "proof-request", + version: "1.0", + requested_attributes: requestedAttributes, + }, + }, + }); + + exchangeRecord.setTag("xRole", "requester"); + await this.askar.agent.proofs.update(exchangeRecord); + + const response = new ProofRecordDto(); + response.id = exchangeRecord.id; + response.connectionId = exchangeRecord.connectionId; + response.state = exchangeRecord.state; + response.updatedAt = exchangeRecord.updatedAt; + response.createdAt = exchangeRecord.createdAt; + response.tags = exchangeRecord.getTags(); + + return { + proofUrl: null, + shortProofUrl: null, + proofRecord: response, + }; + }; + + fetchProofs = async (filter: ProofFilterDto): Promise<ProofRecordDto[]> => { + const query: Query<ProofExchangeRecord>[] = []; + + if (filter.states) { + const stateQuery: Query<ProofExchangeRecord> = { + $or: filter.states.map((state) => ({ state })), + }; + query.push(stateQuery); + } + + if (filter.connectionId) { + const connectionQuery: Query<ProofExchangeRecord> = { + connectionId: filter.connectionId, + }; + query.push(connectionQuery); + } + + let proofs = await this.askar.agent.proofs.findAllByQuery({ + $and: query, + }); + proofs = proofs.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + + const response: ProofRecordDto[] = []; + for (const proof of proofs) { + const t = new ProofRecordDto(); + t.id = proof.id; + t.connectionId = proof.connectionId; + t.state = proof.state; + t.updatedAt = proof.updatedAt; + t.createdAt = proof.createdAt; + t.tags = proof.getTags(); + + response.push(t); + } + + return response; + }; + + getProofById = async (proofRecordId: string): Promise<ProofRecordDto> => { + const proofRecord = await this.askar.agent.proofs.findById(proofRecordId); + + if (!proofRecord) { + throw new EntityNotFoundError(); + } + + const proofResponse = new ProofRecordDto(); + + proofResponse.id = proofRecord.id; + proofResponse.connectionId = proofRecord.connectionId; + proofResponse.state = proofRecord.state; + proofResponse.updatedAt = proofRecord.updatedAt; + proofResponse.createdAt = proofRecord.createdAt; + proofResponse.tags = proofRecord.getTags(); + + return proofResponse; + }; + + getProofFormatDataById = async ( + proofRecordId: string, + ): Promise<ProofFormatDataDto> => { + const formatData = await this.askar.agent.proofs.getFormatData( + proofRecordId, + ); + + if (!formatData) { + throw new EntityNotFoundError(); + } + + const dto = new ProofFormatDataDto(); + dto.anoncredsProposal = formatData.proposal?.anoncreds; + dto.anoncredsRequest = formatData.request?.anoncreds; + dto.anoncredsPresentation = formatData.presentation?.anoncreds; + return dto; + }; + + proofAcceptanceWait = async ( + proofRecordId: string, + ): Promise<ProofFormatDataDto> => { + const proofRecord = await this.askar.agent.proofs.findById(proofRecordId); + + if (!proofRecord) { + throw new EntityNotFoundError(); + } + + if (proofRecord.state === ProofState.Done) { + return this.getProofFormatDataById(proofRecordId); + } + + await waitForProofExchangeRecordSubject(this.askar.agentB, { + proofRecordId, + state: ProofState.Done, + timeoutMs: 3 * 60 * 1000, // 3 minutes + }); + + return this.getProofFormatDataById(proofRecordId); + }; + + acceptProof = async ( + acceptProofDto: AcceptProofDto, + ): Promise<ProofRecordDto> => { + if (acceptProofDto.proofUrl) { + return this.acceptOobProof(acceptProofDto.proofUrl); + } + return this.acceptConnectionProof(acceptProofDto.proofId); + }; + + acceptOobProof = async (url: string): Promise<ProofRecordDto> => { + // omit await in order to catch received record in the next line + setTimeout(() => { + this.askar.agent.oob.receiveInvitationFromUrl(url, { + autoAcceptConnection: false, + autoAcceptInvitation: true, + // reuseConnection: true, + }); + }, 20); + + const record = await waitForProofExchangeRecordSubject(this.askar.agentB, { + state: ProofState.RequestReceived, + }); + + const requestedCredentials = + await this.askar.agent.proofs.selectCredentialsForRequest({ + proofRecordId: record.id, + }); + + const acceptedRecord = await this.askar.agent.proofs.acceptRequest({ + proofRecordId: record.id, + proofFormats: requestedCredentials.proofFormats, + }); + + const response = new ProofRecordDto(); + + response.id = acceptedRecord.id; + response.connectionId = acceptedRecord.connectionId; + response.state = acceptedRecord.state; + response.updatedAt = acceptedRecord.updatedAt; + response.createdAt = acceptedRecord.createdAt; + response.tags = acceptedRecord.getTags(); + + return response; + }; + + acceptConnectionProof = async ( + proofRecordId: string, + ): Promise<ProofRecordDto> => { + this.logger.log(`accepting proof request for ${proofRecordId}`); + const requestedCredentials = + await this.askar.agent.proofs.selectCredentialsForRequest({ + proofRecordId, + }); + + this.logger.log(JSON.stringify(requestedCredentials, null, 2)); + + const proof = await this.askar.agent.proofs.acceptRequest({ + proofRecordId, + proofFormats: requestedCredentials.proofFormats, + }); + + this.logger.log(JSON.stringify(proof, null, 2)); + + const response = new ProofRecordDto(); + response.id = proof.id; + response.connectionId = proof.connectionId; + response.state = proof.state; + response.updatedAt = proof.updatedAt; + response.createdAt = proof.createdAt; + response.tags = proof.getTags(); + + return response; + }; + + declineProofRequest = async ( + proofRecordId: string, + ): Promise<ProofRecordDto> => { + const resultFromDecline = await this.askar.agent.proofs.declineRequest({ + proofRecordId, + sendProblemReport: true, + }); + + const declineResponse = new ProofRecordDto(); + declineResponse.id = resultFromDecline.id; + declineResponse.connectionId = resultFromDecline.connectionId; + declineResponse.state = resultFromDecline.state; + declineResponse.updatedAt = resultFromDecline.updatedAt; + declineResponse.createdAt = resultFromDecline.createdAt; + declineResponse.tags = resultFromDecline.getTags(); + + return declineResponse; + }; + + deleteProofById = async (id: string): Promise<void> => { + await this.askar.agent.proofs.deleteById(id); + }; +} diff --git a/libs/askar/src/askar/agent.service.ts b/libs/askar/src/askar/agent.service.ts deleted file mode 100644 index 38caf855474b90dd74ef9eb0c55c545b4076a493..0000000000000000000000000000000000000000 --- a/libs/askar/src/askar/agent.service.ts +++ /dev/null @@ -1,1333 +0,0 @@ -import { Injectable, Logger } from "@nestjs/common"; -import { AskarService } from "./askar.service"; -import { - ConnectionRecordDto, - AcceptProofDto, - CreateCredentialDefinitionRequestDto, - CreddefRecordDto, - CreateInvitationResponseDto, - CreateSchemaRequestDto, - SchemaRecordDto, - CredentialNotCreatedError, - OfferCredentialRequestDto, - CredentialRecordDto, - RequestProofDto, - ProofRecordDto, - MakeBasicMessageRequestDto, - MessageRecordDto, - SchemaNotCreatedError, - CredentialFilterDto, - ProofFilterDto, - MessageFilterDto, - RequestProofResponseDto, - CredentialOfferResponseDto, - EntityNotFoundError, - AcceptCredentialDto, - CredentialFormatDataDto, - ProofFormatDataDto, - CreateInvitationRequestDto, - InvitationFilterDto, - DidRecordDto, - OcmError, - W3cCredentialDto, - BaseRecordDto, - W3cJsonLdVerifiableCredentialDto, - W3cJsonLdVerifiablePresentationDto, -} from "@ocm-engine/dtos"; -import { - AutoAcceptCredential, - BasicMessageRecord, - BasicMessageRole, - ClaimFormat, - ConnectionRecord, - CredentialExchangeRecord, - JsonTransformer, - CredentialState, - ProofState, - Query, - ProofExchangeRecord, - OutOfBandRecord, - W3cCredential, - W3cCredentialService, - DidRecord, - JsonCredential, - W3cJsonLdVerifiableCredential, -} from "@credo-ts/core"; -import { AnonCredsRequestedAttribute } from "@credo-ts/anoncreds"; -import { uuid } from "@credo-ts/core/build/utils/uuid"; -import { - waitForCredentialExchangeRecordSubject, - waitForProofExchangeRecordSubject, -} from "../agent.utils"; - -@Injectable() -export class AgentService { - private readonly logger = new Logger(AgentService.name); - constructor(private readonly askar: AskarService) {} - - createInvitation = async ( - createInvitationRequestDto?: CreateInvitationRequestDto, - ) => { - const outOfBoundRecord = await this.askar.agent.oob.createInvitation( - createInvitationRequestDto, - ); - - const response = new CreateInvitationResponseDto(); - - let longUrl = outOfBoundRecord.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - - if (this.askar.agentConfig.agentOobUrl) { - longUrl = longUrl.replace( - this.askar.agentConfig.agentPeerAddress, - this.askar.agentConfig.agentOobUrl, - ); - } - - //TODO: should we replace the short url with agentOobUrl if we do this we should have a redirect in ingress - response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBoundRecord.outOfBandInvitation.id}`; - response.outOfBandId = outOfBoundRecord.id; - response.createdAt = outOfBoundRecord.createdAt; - response.updatedAt = outOfBoundRecord.updatedAt; - response.role = outOfBoundRecord.role; - response.state = outOfBoundRecord.state; - response.invitationUrl = longUrl; - - return response; - }; - - acceptInvitation = async ( - invitationUrl: string, - ): Promise<ConnectionRecordDto> => { - const { connectionRecord } = - await this.askar.agent.oob.receiveInvitationFromUrl(invitationUrl); - - if (typeof connectionRecord === "undefined") { - throw new EntityNotFoundError(); - } - - const response = new ConnectionRecordDto(); - response.connectionName = connectionRecord.theirLabel; - response.state = connectionRecord.state; - response.id = connectionRecord.id; - response.did = connectionRecord.did; - response.theirDid = connectionRecord.theirDid; - response.invitationDid = connectionRecord.invitationDid; - response.outOfBandId = connectionRecord.outOfBandId; - response.createdAt = connectionRecord.createdAt; - response.updatedAt = connectionRecord.updatedAt; - response.imageUrl = connectionRecord.imageUrl; - - return response; - }; - - deleteInvitationById = async (id: string) => { - return this.askar.agent.oob.deleteById(id); - }; - - fetchInvitations = async (filter: InvitationFilterDto) => { - const query: Query<OutOfBandRecord>[] = []; - - if (filter.states) { - const stateQuery: Query<OutOfBandRecord> = { - $or: filter.states.map((state) => ({ state })), - }; - query.push(stateQuery); - } - - if (filter.roles) { - const roleQuery: Query<OutOfBandRecord> = { - $or: filter.roles.map((role) => ({ role })), - }; - - query.push(roleQuery); - } - - let invitations = await this.askar.agent.oob.findAllByQuery({ - $and: query, - }); - invitations = invitations.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const invitationsResponse = invitations.map((invitation) => { - const response = new CreateInvitationResponseDto(); - response.invitationUrl = invitation.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${invitation.outOfBandInvitation.id}`; - response.outOfBandId = invitation.id; - response.createdAt = invitation.createdAt; - response.updatedAt = invitation.updatedAt; - response.role = invitation.role; - response.state = invitation.state; - return response; - }); - - return invitationsResponse; - }; - - getInvitationById = async (oobId: string) => { - const invitation = await this.askar.agent.oob.getById(oobId); - - const response = new CreateInvitationResponseDto(); - response.invitationUrl = invitation.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - response.shortInvitationUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${invitation.outOfBandInvitation.id}`; - response.outOfBandId = invitation.id; - response.createdAt = invitation.createdAt; - response.updatedAt = invitation.updatedAt; - response.role = invitation.role; - response.state = invitation.state; - return response; - }; - - async fetchConnections(): Promise<ConnectionRecordDto[]> { - //TODO: no ordering in findAllByQuery - const agentResponse = (await this.askar.agent.connections.getAll()).sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const connectionArray = agentResponse.map((singleConnectionRes) => { - const connectionResponse = new ConnectionRecordDto(); - connectionResponse.id = singleConnectionRes.id; - connectionResponse.state = singleConnectionRes.state; - connectionResponse.connectionName = singleConnectionRes.theirLabel; - connectionResponse.alias = singleConnectionRes.alias; - connectionResponse.did = singleConnectionRes.did; - connectionResponse.theirDid = singleConnectionRes.theirDid; - connectionResponse.invitationDid = singleConnectionRes.invitationDid; - connectionResponse.outOfBandId = singleConnectionRes.outOfBandId; - connectionResponse.createdAt = singleConnectionRes.createdAt; - connectionResponse.updatedAt = singleConnectionRes.updatedAt; - connectionResponse.imageUrl = singleConnectionRes.imageUrl; - - return connectionResponse; - }); - - return connectionArray; - } - - getConnectionByOobId = async (oobId: string) => { - const connectionRecords = - await this.askar.agent.connections.findAllByOutOfBandId(oobId); - - const connectionArray = connectionRecords.map((singleConnectionRes) => { - const connectionResponse = new ConnectionRecordDto(); - connectionResponse.id = singleConnectionRes.id; - connectionResponse.state = singleConnectionRes.state; - connectionResponse.connectionName = singleConnectionRes.theirLabel; - connectionResponse.alias = singleConnectionRes.alias; - connectionResponse.did = singleConnectionRes.did; - connectionResponse.theirDid = singleConnectionRes.theirDid; - connectionResponse.invitationDid = singleConnectionRes.invitationDid; - connectionResponse.outOfBandId = singleConnectionRes.outOfBandId; - connectionResponse.createdAt = singleConnectionRes.createdAt; - connectionResponse.updatedAt = singleConnectionRes.updatedAt; - connectionResponse.imageUrl = singleConnectionRes.imageUrl; - - return connectionResponse; - }); - - return connectionArray; - }; - - getConnectionById = async (id: string): Promise<ConnectionRecordDto> => { - const agentResponse = await this.askar.agent.connections.findById(id); - - if (!agentResponse) { - throw new EntityNotFoundError(); - } - - const connectionResponse = new ConnectionRecordDto(); - connectionResponse.id = agentResponse.id; - connectionResponse.state = agentResponse.state; - connectionResponse.connectionName = agentResponse.theirLabel; - connectionResponse.alias = agentResponse.alias; - connectionResponse.did = agentResponse.did; - connectionResponse.theirDid = agentResponse.theirDid; - connectionResponse.invitationDid = agentResponse.invitationDid; - connectionResponse.outOfBandId = agentResponse.outOfBandId; - connectionResponse.createdAt = agentResponse.createdAt; - connectionResponse.updatedAt = agentResponse.updatedAt; - connectionResponse.imageUrl = agentResponse.imageUrl; - - return connectionResponse; - }; - - deleteConnectionById = async (id: string): Promise<void> => { - await this.askar.agent.connections.hangup({ - connectionId: id, - deleteAfterHangup: true - }); - }; - - fetchSchemas = async (): Promise<SchemaRecordDto[]> => { - let schemaRecords = - await this.askar.agent.modules.anoncreds.getCreatedSchemas({}); - schemaRecords = schemaRecords.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const schemaResponses = schemaRecords.map((singleSchemaRes) => { - const schemaResponse = new SchemaRecordDto(); - schemaResponse.id = singleSchemaRes.schemaId; - schemaResponse.createdAt = singleSchemaRes.createdAt; - schemaResponse.updatedAt = singleSchemaRes.updatedAt; - schemaResponse.name = singleSchemaRes.schema.name; - schemaResponse.attributes = singleSchemaRes.schema.attrNames; - schemaResponse.version = singleSchemaRes.schema.version; - schemaResponse.issuerId = singleSchemaRes.schema.issuerId; - schemaResponse.methodName = singleSchemaRes.methodName; - - return schemaResponse; - }); - - return schemaResponses; - }; - - getSchemaById = async (schemaId: string): Promise<SchemaRecordDto> => { - const agentResponse = await this.askar.agent.modules.anoncreds.getSchema( - schemaId, - ); - - if (!agentResponse || !agentResponse.schema) { - throw new EntityNotFoundError(); - } - - const schemaResponse = new SchemaRecordDto(); - schemaResponse.id = agentResponse.schemaId; - schemaResponse.name = agentResponse.schema.name; - schemaResponse.attributes = agentResponse.schema.attrNames; - schemaResponse.version = agentResponse.schema.version; - schemaResponse.issuerId = agentResponse.schema.issuerId; - - return schemaResponse; - }; - - createSchema = async ( - schema: CreateSchemaRequestDto, - ): Promise<SchemaRecordDto> => { - const dids = await this.askar.agent.dids.getCreatedDids({ method: "indy" }); - - const schemaResult = - await this.askar.agent.modules.anoncreds.registerSchema({ - schema: { - name: schema.name, - issuerId: dids[0].did, - attrNames: schema.attributes, - version: schema.version, - }, - options: {}, - }); - - if (schemaResult.schemaState.state !== "finished") { - throw new SchemaNotCreatedError(); - } - - const response = new SchemaRecordDto(); - - response.name = schemaResult.schemaState.schema.name; - response.id = schemaResult.schemaState.schemaId; - response.issuerId = schemaResult.schemaState.schema.issuerId; - response.version = schemaResult.schemaState.schema.version; - response.attributes = schemaResult.schemaState.schema.attrNames; - - return response; - }; - - fetchCredentialDefinitions = async (): Promise<CreddefRecordDto[]> => { - let credentialDefinitions = - await this.askar.agent.modules.anoncreds.getCreatedCredentialDefinitions( - {}, - ); - credentialDefinitions = credentialDefinitions.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const response: Array<CreddefRecordDto> = []; - for (const credDef of credentialDefinitions) { - const cd = new CreddefRecordDto(); - cd.id = credDef.credentialDefinitionId; - cd.createdAt = credDef.createdAt; - cd.updatedAt = credDef.updatedAt; - cd.schemaId = credDef.credentialDefinition.schemaId; - cd.issuerId = credDef.credentialDefinition.issuerId; - cd.tag = credDef.credentialDefinition.tag; - response.push(cd); - } - - return response; - }; - - getCredentialDefinitionById = async ( - credentialDefinitionId: string, - ): Promise<CreddefRecordDto> => { - const credDefs = - await this.askar.agent.modules.anoncreds.getCreatedCredentialDefinitions({ - credentialDefinitionId, - }); - - const credDef = credDefs[0] || null; - if (!credDef) { - throw new EntityNotFoundError(); - } - - const cd = new CreddefRecordDto(); - cd.id = credDef.credentialDefinitionId; - cd.createdAt = credDef.createdAt; - cd.updatedAt = credDef.updatedAt; - cd.schemaId = credDef.credentialDefinition.schemaId; - cd.issuerId = credDef.credentialDefinition.issuerId; - cd.tag = credDef.credentialDefinition.tag; - - return cd; - }; - - createCredentialDefinition = async ( - credentialDefinitionDto: CreateCredentialDefinitionRequestDto, - ): Promise<CreddefRecordDto> => { - const dids = await this.askar.agent.dids.getCreatedDids({ method: "indy" }); - - const credDef = - await this.askar.agent.modules.anoncreds.registerCredentialDefinition({ - credentialDefinition: { - tag: credentialDefinitionDto.tag, - issuerId: dids[0].did, - schemaId: credentialDefinitionDto.schemaId, - }, - options: { - supportRevocation: credentialDefinitionDto.supportRevocation ?? false, - }, - }); - - if (credDef.credentialDefinitionState.state !== "finished") { - throw new CredentialNotCreatedError(); - } - - const response = new CreddefRecordDto(); - response.id = credDef.credentialDefinitionState.credentialDefinitionId; - response.schemaId = - credDef.credentialDefinitionState.credentialDefinition.schemaId; - response.issuerId = - credDef.credentialDefinitionState.credentialDefinition.issuerId; - response.tag = credDef.credentialDefinitionState.credentialDefinition.tag; - - return response; - }; - - signJsonLdCredential = async ( - credToSign: W3cCredentialDto, - ): Promise<W3cJsonLdVerifiableCredentialDto> => { - this.logger.log("Sign json ld credentials"); - - const didRecord = await this.getFirstDidWebRecord(); - const verificationMethodList = - didRecord.didDocument?.verificationMethod || []; - if (!verificationMethodList.length) { - throw new EntityNotFoundError( - "DidDocument does not exists or contains no verification methods", - ); - } - - const verificationMethod = verificationMethodList[0]; - - const w3cServ = - this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); - - credToSign.id = didRecord.did + "?uuid=" + uuid(); - if (credToSign.credentialSubject) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - credToSign.credentialSubject.id = credToSign.id; - } - credToSign.issuer = didRecord.did; - credToSign.issuanceDate = new Date().toISOString(); - let credential: W3cCredential; - try { - credential = JsonTransformer.fromJSON(credToSign, W3cCredential); - } catch (e) { - this.logger.log("Incorrect request parameter", e); - throw new OcmError( - "Invalid JSON-LD data format. Please ensure that your JSON-LD contains the following properties: @context, id, type, issuer, issuanceDate, expirationDate, and credentialSubject.", - ); - } - - const vc = await w3cServ.signCredential(this.askar.agent.context, { - format: ClaimFormat.LdpVc, - credential, - proofType: "Ed25519Signature2018", - verificationMethod: verificationMethod.id, - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const jsonVC = vc.toJson() as W3cJsonLdVerifiableCredentialDto; - this.logger.debug(JSON.stringify(jsonVC, null, 2)); - - return jsonVC; - }; - - prepareVerifiablePresentationByJsonLdCredId = async ( - credentialRecordId: string, - ): Promise<W3cJsonLdVerifiablePresentationDto> => { - const didRecord = await this.getFirstDidWebRecord(); - const verificationMethodList = - didRecord.didDocument?.verificationMethod || []; - if (!verificationMethodList.length) { - throw new EntityNotFoundError( - "DidDocument does not exists or contains no verification methods", - ); - } - - const verificationMethod = verificationMethodList[0]; - - const credFormatData = await this.askar.agent.credentials.getFormatData( - credentialRecordId, - ); - if (!credFormatData.credential?.jsonld) { - throw new OcmError( - "The JSON-LD credential is either not in your wallet, pending approval, or not in the JSON-LD format.", - ); - } - - const jsonLd = credFormatData.credential.jsonld; - const vc = JsonTransformer.fromJSON(jsonLd, W3cJsonLdVerifiableCredential); - - const w3cServ = - this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); - const presentation = await w3cServ.createPresentation({ - credentials: [vc], - id: didRecord.did + "?uuid=" + uuid(), - }); - - const vp = await w3cServ.signPresentation(this.askar.agent.context, { - format: ClaimFormat.LdpVp, - presentation, - proofPurpose: null, - proofType: "Ed25519Signature2018", - challenge: uuid(), - verificationMethod: verificationMethod.id, - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const jsonVP = vp.toJson() as W3cJsonLdVerifiablePresentationDto; - this.logger.debug(JSON.stringify(jsonVP, null, 2)); - - return jsonVP; - }; - - offerJsonLdCredential = async ( - connectionId: string | undefined, - credToSign: W3cCredentialDto, - ): Promise<CredentialOfferResponseDto> => { - this.logger.log("offerJsonLdCredential", connectionId); - - const didRecord = await this.getFirstDidWebRecord(); - const verificationMethodList = - didRecord.didDocument?.verificationMethod || []; - if (!verificationMethodList.length) { - throw new EntityNotFoundError( - "DidDocument does not exists or contains no verification methods", - ); - } - - const verificationMethod = verificationMethodList[0]; - - const w3cServ = - this.askar.agent.context.dependencyManager.resolve(W3cCredentialService); - - credToSign.id = didRecord.did + "?uuid=" + uuid(); - if (credToSign.credentialSubject) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - credToSign.credentialSubject.id = credToSign.id; - } - credToSign.issuer = didRecord.did; - credToSign.issuanceDate = new Date().toISOString(); - let credential: W3cCredential; - try { - credential = JsonTransformer.fromJSON(credToSign, W3cCredential); - } catch (e) { - this.logger.log("Incorrect request parameter", e); - throw new OcmError( - "Invalid JSON-LD data format. Please ensure that your JSON-LD contains the following properties: @context, id, type, issuer, issuanceDate, expirationDate, and credentialSubject.", - ); - } - - const vc = await w3cServ.signCredential(this.askar.agent.context, { - format: ClaimFormat.LdpVc, - credential, - proofType: "Ed25519Signature2018", - verificationMethod: verificationMethod.id, - }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const jsonVC = vc.toJson() as JsonCredential; - - if (!connectionId) { - // create connection less credential - const { credentialRecord, message } = - await this.askar.agent.credentials.createOffer({ - protocolVersion: "v2", - credentialFormats: { - jsonld: { - credential: jsonVC, - options: { - proofType: "Ed25519Signature2018", - proofPurpose: "assertionMethod", - }, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }); - - credentialRecord.setTag("xRole", "issuer"); - await this.askar.agent.credentials.update(credentialRecord); - - const outOfBandRecord = await this.askar.agent.oob.createInvitation({ - messages: [message], - handshake: false, - }); - - const credentialUrl = outOfBandRecord.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - - const shortCredentialUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; - - const dto = new CredentialRecordDto(); - dto.id = credentialRecord.id; - dto.state = credentialRecord.state; - dto.connectionId = credentialRecord.connectionId; - dto.attributes = credentialRecord.credentialAttributes; - dto.createdAt = credentialRecord.createdAt; - dto.tags = credentialRecord.getTags(); - - return { - credentialUrl: credentialUrl, - shortCredentialUrl: shortCredentialUrl, - credentialRecord: dto, - }; - } - - const credentialExchangeRecord = - await this.askar.agent.credentials.offerCredential({ - connectionId: connectionId, - protocolVersion: "v2", - credentialFormats: { - jsonld: { - credential: jsonVC, - options: { - proofType: "Ed25519Signature2018", - proofPurpose: "assertionMethod", - }, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }); - - this.logger.log(credentialExchangeRecord); - - credentialExchangeRecord.setTag("xRole", "issuer"); - await this.askar.agent.credentials.update(credentialExchangeRecord); - - const dto = new CredentialRecordDto(); - dto.id = credentialExchangeRecord.id; - dto.state = credentialExchangeRecord.state; - dto.connectionId = credentialExchangeRecord.connectionId; - dto.attributes = credentialExchangeRecord.credentialAttributes; - dto.createdAt = credentialExchangeRecord.createdAt; - dto.tags = credentialExchangeRecord.getTags(); - - return { - credentialUrl: null, - shortCredentialUrl: null, - credentialRecord: dto, - }; - }; - - offerCredential = async ( - offerCredentialDto: OfferCredentialRequestDto, - ): Promise<CredentialOfferResponseDto> => { - this.logger.log( - "Incoming request", - JSON.stringify(offerCredentialDto, null, 2), - ); - - if (!offerCredentialDto.connectionId) { - const { credentialRecord, message } = - await this.askar.agent.credentials.createOffer({ - protocolVersion: "v2", - credentialFormats: { - anoncreds: { - credentialDefinitionId: offerCredentialDto.credentialDefinitionId, - attributes: offerCredentialDto.attributes, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }); - - credentialRecord.setTag("xRole", "issuer"); - await this.askar.agent.credentials.update(credentialRecord); - - const outOfBandRecord = await this.askar.agent.oob.createInvitation({ - messages: [message], - handshake: false, - }); - - const credentialUrl = outOfBandRecord.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - - const shortCredentialUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; - - const dto = new CredentialRecordDto(); - dto.id = credentialRecord.id; - dto.state = credentialRecord.state; - dto.connectionId = credentialRecord.connectionId; - dto.attributes = credentialRecord.credentialAttributes; - dto.createdAt = credentialRecord.createdAt; - dto.tags = credentialRecord.getTags(); - - return { - credentialUrl: credentialUrl, - shortCredentialUrl: shortCredentialUrl, - credentialRecord: dto, - }; - } - const credentialExchangeRecord = - await this.askar.agent.credentials.offerCredential({ - protocolVersion: "v2", - connectionId: offerCredentialDto.connectionId, - credentialFormats: { - anoncreds: { - credentialDefinitionId: offerCredentialDto.credentialDefinitionId, - attributes: offerCredentialDto.attributes, - }, - }, - }); - - credentialExchangeRecord.setTag("xRole", "issuer"); - await this.askar.agent.credentials.update(credentialExchangeRecord); - - const dto = new CredentialRecordDto(); - dto.id = credentialExchangeRecord.id; - dto.state = credentialExchangeRecord.state; - dto.connectionId = credentialExchangeRecord.connectionId; - dto.attributes = credentialExchangeRecord.credentialAttributes; - dto.createdAt = credentialExchangeRecord.createdAt; - dto.tags = credentialExchangeRecord.getTags(); - - return { - credentialUrl: null, - shortCredentialUrl: null, - credentialRecord: dto, - }; - }; - - acceptCredential = async ( - acceptCredentialDto: AcceptCredentialDto, - ): Promise<CredentialRecordDto> => { - if (acceptCredentialDto.credentialUrl) { - return this.acceptOobCredentials(acceptCredentialDto.credentialUrl); - } - return this.acceptConnectionCredential(acceptCredentialDto.credentialId); - }; - - acceptOobCredentials = async (url: string): Promise<CredentialRecordDto> => { - // omit await in order to catch received record in the next line - setTimeout(() => { - this.askar.agent.oob.receiveInvitationFromUrl(url, { - autoAcceptConnection: false, - autoAcceptInvitation: true, - // reuseConnection: true, - }); - }, 20); - - const record = await waitForCredentialExchangeRecordSubject( - this.askar.agentB, - { - state: CredentialState.OfferReceived, - }, - ); - - const acceptedRecord = await this.askar.agent.credentials.acceptOffer({ - credentialRecordId: record.id, - }); - - const response = new CredentialRecordDto(); - response.id = acceptedRecord.id; - response.state = acceptedRecord.state; - response.connectionId = acceptedRecord.connectionId; - response.attributes = acceptedRecord.credentialAttributes; - response.createdAt = acceptedRecord.createdAt; - response.tags = acceptedRecord.getTags(); - - return response; - }; - - acceptConnectionCredential = async ( - credentialRecordId: string, - ): Promise<CredentialRecordDto> => { - const credentialExchangeRecord = - await this.askar.agent.credentials.acceptOffer({ - credentialRecordId, - }); - - const response = new CredentialRecordDto(); - response.id = credentialExchangeRecord.id; - response.state = credentialExchangeRecord.state; - response.connectionId = credentialExchangeRecord.connectionId; - response.attributes = credentialExchangeRecord.credentialAttributes; - response.createdAt = credentialExchangeRecord.createdAt; - response.tags = credentialExchangeRecord.getTags(); - - return response; - }; - - declineCredential = async ( - credentialRecordId: string, - ): Promise<CredentialRecordDto> => { - const credentialExchangeRecord = - await this.askar.agent.credentials.declineOffer(credentialRecordId); - - // send request to the issuer that the request is declined, to mark it as Abondoned - await this.askar.agent.credentials.sendProblemReport({ - credentialRecordId: credentialRecordId, - description: "Decline offer", - }); - - const response = new CredentialRecordDto(); - response.id = credentialExchangeRecord.id; - response.state = credentialExchangeRecord.state; - response.connectionId = credentialExchangeRecord.connectionId; - response.attributes = credentialExchangeRecord.credentialAttributes; - response.createdAt = credentialExchangeRecord.createdAt; - response.tags = credentialExchangeRecord.getTags(); - - return response; - }; - - fetchCredentials = async ( - filter: CredentialFilterDto, - ): Promise<CredentialRecordDto[]> => { - const query: Query<CredentialExchangeRecord>[] = []; - - if (filter.states) { - const stateQuery: Query<CredentialExchangeRecord> = { - $or: filter.states.map((state) => ({ state })), - }; - query.push(stateQuery); - } - - if (filter.connectionId) { - const connectionQuery: Query<CredentialExchangeRecord> = { - connectionId: filter.connectionId, - }; - query.push(connectionQuery); - } - - let credentials = await this.askar.agent.credentials.findAllByQuery({ - $and: query, - }); - credentials = credentials.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const response: CredentialRecordDto[] = []; - for (const offer of credentials) { - const t = new CredentialRecordDto(); - t.id = offer.id; - t.state = offer.state; - t.connectionId = offer.connectionId; - t.createdAt = offer.createdAt; - t.attributes = offer.credentialAttributes; - t.tags = offer.getTags(); - response.push(t); - } - - return response; - }; - - getCredentialById = async ( - credentialId: string, - ): Promise<CredentialRecordDto> => { - const credentialRecord = await this.askar.agent.credentials.findById( - credentialId, - ); - - if (!credentialRecord) { - throw new EntityNotFoundError(); - } - - const credential = new CredentialRecordDto(); - credential.id = credentialRecord.id; - credential.state = credentialRecord.state; - credential.connectionId = credentialRecord.connectionId; - credential.createdAt = credentialRecord.createdAt; - credential.attributes = credentialRecord.credentialAttributes; - credential.tags = credentialRecord.getTags(); - - return credential; - }; - - getCredentialFormatDataById = async ( - credentialId: string, - ): Promise<CredentialFormatDataDto> => { - - const formatData = await this.askar.agent.credentials.getFormatData( - credentialId, - ); - - if (!formatData) { - throw new EntityNotFoundError(); - } - - const dto = new CredentialFormatDataDto(); - dto.proposalAttributes = formatData.proposalAttributes; - dto.offerAttributes = formatData.offerAttributes; - dto.anoncredsProposal = formatData.proposal?.anoncreds; - dto.anoncredsOffer = formatData.offer?.anoncreds; - dto.anoncredsRequest = formatData.request?.anoncreds; - dto.anoncredsCredential = formatData.credential?.anoncreds; - - dto.all = formatData; - - return dto; - }; - - requestProof = async ( - requestProofDto: RequestProofDto, - ): Promise<RequestProofResponseDto> => { - this.logger.log(JSON.stringify(requestProofDto, null, 2)); - const requestedAttributes: Record<string, AnonCredsRequestedAttribute> = {}; - - for (const attr of requestProofDto.attributes) { - requestedAttributes[uuid()] = { - name: attr.attributeName, - restrictions: [ - { - schema_id: attr.schemaId, - cred_def_id: attr.credentialDefinitionId, - }, - ], - }; - } - - if (!requestProofDto.connectionId) { - this.logger.log("connection Id not detected, creating oob proof"); - const { proofRecord, message } = - await this.askar.agent.proofs.createRequest({ - protocolVersion: "v2", - proofFormats: { - anoncreds: { - name: "proof-request", - version: "1.0", - requested_attributes: requestedAttributes, - }, - }, - }); - - proofRecord.setTag("xRole", "requester"); - await this.askar.agent.proofs.update(proofRecord); - - const outOfBandRecord = await this.askar.agent.oob.createInvitation({ - messages: [message], - handshake: false, - }); - - const proofUrl = outOfBandRecord.outOfBandInvitation.toUrl({ - domain: this.askar.agentConfig.agentPeerAddress, - }); - const shortProofUrl = `${this.askar.agentConfig.agentPeerAddress}/invitations/${outOfBandRecord.outOfBandInvitation.id}`; - - const dto = new ProofRecordDto(); - dto.id = proofRecord.id; - dto.connectionId = proofRecord.connectionId; - dto.state = proofRecord.state; - dto.updatedAt = proofRecord.updatedAt; - dto.createdAt = proofRecord.createdAt; - dto.tags = proofRecord.getTags(); - - return { - proofUrl: proofUrl, - shortProofUrl: shortProofUrl, - proofRecord: dto, - }; - } - - this.logger.log(`${requestProofDto.connectionId} detected, issuing proof`); - - const exchangeRecord = await this.askar.agent.proofs.requestProof({ - protocolVersion: "v2", - connectionId: requestProofDto.connectionId, - proofFormats: { - anoncreds: { - name: "proof-request", - version: "1.0", - requested_attributes: requestedAttributes, - }, - }, - }); - - exchangeRecord.setTag("xRole", "requester"); - await this.askar.agent.proofs.update(exchangeRecord); - - const response = new ProofRecordDto(); - response.id = exchangeRecord.id; - response.connectionId = exchangeRecord.connectionId; - response.state = exchangeRecord.state; - response.updatedAt = exchangeRecord.updatedAt; - response.createdAt = exchangeRecord.createdAt; - response.tags = exchangeRecord.getTags(); - - return { - proofUrl: null, - shortProofUrl: null, - proofRecord: response, - }; - }; - - deleteCredentialById = async (id: string): Promise<void> => { - await this.askar.agent.credentials.deleteById(id); - }; - - fetchProofs = async (filter: ProofFilterDto): Promise<ProofRecordDto[]> => { - const query: Query<ProofExchangeRecord>[] = []; - - if (filter.states) { - const stateQuery: Query<ProofExchangeRecord> = { - $or: filter.states.map((state) => ({ state })), - }; - query.push(stateQuery); - } - - if (filter.connectionId) { - const connectionQuery: Query<ProofExchangeRecord> = { - connectionId: filter.connectionId, - }; - query.push(connectionQuery); - } - - let proofs = await this.askar.agent.proofs.findAllByQuery({ - $and: query, - }); - proofs = proofs.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const response: ProofRecordDto[] = []; - for (const proof of proofs) { - const t = new ProofRecordDto(); - t.id = proof.id; - t.connectionId = proof.connectionId; - t.state = proof.state; - t.updatedAt = proof.updatedAt; - t.createdAt = proof.createdAt; - t.tags = proof.getTags(); - - response.push(t); - } - - return response; - }; - - getProofById = async (proofRecordId: string): Promise<ProofRecordDto> => { - const proofRecord = await this.askar.agent.proofs.findById(proofRecordId); - - if (!proofRecord) { - throw new EntityNotFoundError(); - } - - const proofResponse = new ProofRecordDto(); - - proofResponse.id = proofRecord.id; - proofResponse.connectionId = proofRecord.connectionId; - proofResponse.state = proofRecord.state; - proofResponse.updatedAt = proofRecord.updatedAt; - proofResponse.createdAt = proofRecord.createdAt; - proofResponse.tags = proofRecord.getTags(); - - return proofResponse; - }; - - getProofFormatDataById = async ( - proofRecordId: string, - ): Promise<ProofFormatDataDto> => { - const formatData = await this.askar.agent.proofs.getFormatData( - proofRecordId, - ); - - if (!formatData) { - throw new EntityNotFoundError(); - } - - const dto = new ProofFormatDataDto(); - dto.anoncredsProposal = formatData.proposal?.anoncreds; - dto.anoncredsRequest = formatData.request?.anoncreds; - dto.anoncredsPresentation = formatData.presentation?.anoncreds; - return dto; - }; - - proofAcceptanceWait = async ( - proofRecordId: string, - ): Promise<ProofFormatDataDto> => { - const proofRecord = await this.askar.agent.proofs.findById(proofRecordId); - - if (!proofRecord) { - throw new EntityNotFoundError(); - } - - if (proofRecord.state === ProofState.Done) { - return this.getProofFormatDataById(proofRecordId); - } - - await waitForProofExchangeRecordSubject(this.askar.agentB, { - proofRecordId, - state: ProofState.Done, - timeoutMs: 3 * 60 * 1000, // 3 minutes - }); - - return this.getProofFormatDataById(proofRecordId); - }; - - acceptProof = async ( - acceptProofDto: AcceptProofDto, - ): Promise<ProofRecordDto> => { - if (acceptProofDto.proofUrl) { - return this.acceptOobProof(acceptProofDto.proofUrl); - } - return this.acceptConnectionProof(acceptProofDto.proofId); - }; - - acceptOobProof = async (url: string): Promise<ProofRecordDto> => { - // omit await in order to catch received record in the next line - setTimeout(() => { - this.askar.agent.oob.receiveInvitationFromUrl(url, { - autoAcceptConnection: false, - autoAcceptInvitation: true, - // reuseConnection: true, - }); - }, 20); - - const record = await waitForProofExchangeRecordSubject(this.askar.agentB, { - state: ProofState.RequestReceived, - }); - - const requestedCredentials = - await this.askar.agent.proofs.selectCredentialsForRequest({ - proofRecordId: record.id, - }); - - const acceptedRecord = await this.askar.agent.proofs.acceptRequest({ - proofRecordId: record.id, - proofFormats: requestedCredentials.proofFormats, - }); - - const response = new ProofRecordDto(); - - response.id = acceptedRecord.id; - response.connectionId = acceptedRecord.connectionId; - response.state = acceptedRecord.state; - response.updatedAt = acceptedRecord.updatedAt; - response.createdAt = acceptedRecord.createdAt; - response.tags = acceptedRecord.getTags(); - - return response; - }; - - acceptConnectionProof = async ( - proofRecordId: string, - ): Promise<ProofRecordDto> => { - this.logger.log(`accepting proof request for ${proofRecordId}`); - const requestedCredentials = - await this.askar.agent.proofs.selectCredentialsForRequest({ - proofRecordId, - }); - - this.logger.log(JSON.stringify(requestedCredentials, null, 2)); - - const proof = await this.askar.agent.proofs.acceptRequest({ - proofRecordId, - proofFormats: requestedCredentials.proofFormats, - }); - - this.logger.log(JSON.stringify(proof, null, 2)); - - const response = new ProofRecordDto(); - response.id = proof.id; - response.connectionId = proof.connectionId; - response.state = proof.state; - response.updatedAt = proof.updatedAt; - response.createdAt = proof.createdAt; - response.tags = proof.getTags(); - - return response; - }; - - declineProofRequest = async ( - proofRecordId: string, - ): Promise<ProofRecordDto> => { - const resultFromDecline = await this.askar.agent.proofs.declineRequest({ - proofRecordId, - sendProblemReport: true, - }); - - const declineResponse = new ProofRecordDto(); - declineResponse.id = resultFromDecline.id; - declineResponse.connectionId = resultFromDecline.connectionId; - declineResponse.state = resultFromDecline.state; - declineResponse.updatedAt = resultFromDecline.updatedAt; - declineResponse.createdAt = resultFromDecline.createdAt; - declineResponse.tags = resultFromDecline.getTags(); - - return declineResponse; - }; - - deleteProofById = async (id: string): Promise<void> => { - await this.askar.agent.proofs.deleteById(id); - }; - - resolve = async (did: string) => { - return this.askar.agent.dids.resolve(did); - }; - - fetchBasicMessages = async ( - filter: MessageFilterDto, - ): Promise<MessageRecordDto[]> => { - const query: Query<BasicMessageRecord>[] = []; - - if (filter.role) { - const roleQuery: Query<BasicMessageRecord> = { - role: filter.role, - }; - query.push(roleQuery); - } - - if (filter.connectionId) { - const connectionQuery: Query<BasicMessageRecord> = { - connectionId: filter.connectionId, - }; - query.push(connectionQuery); - } - - let messages = await this.askar.agent.basicMessages.findAllByQuery({ - $and: query, - }); - messages = messages.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const connectionIds = messages.map((message) => message.connectionId); - - const connections = await this.askar.agent.connections.findAllByQuery({ - $or: connectionIds.map((p) => ({ id: p })), - }); - const grouppedConnections = connections.reduce( - (acc: { [connId: string]: ConnectionRecord }, conn) => { - acc[conn.id] = conn; - return acc; - }, - {}, - ); - - const response: MessageRecordDto[] = []; - for (const message of messages) { - const connection = grouppedConnections[message.connectionId]; - const label = connection?.theirLabel || ""; - - const t = new MessageRecordDto(); - t.id = message.id; - t.createdAt = message.createdAt; - t.updatedAt = message.updatedAt; - t.connectionId = message.connectionId; - t.role = message.role; - t.content = message.content; - t.sentTime = message.sentTime; - t.from = message.role === BasicMessageRole.Receiver ? label : ""; - t.to = message.role === BasicMessageRole.Sender ? label : ""; - - response.push(t); - } - - return response; - }; - - sendMessage = async ( - dto: MakeBasicMessageRequestDto, - ): Promise<MessageRecordDto> => { - const messageRecord = await this.askar.agent.basicMessages.sendMessage( - dto.connectionId, - dto.message, - ); - - const connRecord = await this.askar.agent.connections.findById( - dto.connectionId, - ); - - const response = new MessageRecordDto(); - response.id = messageRecord.id; - response.createdAt = messageRecord.createdAt; - response.updatedAt = messageRecord.updatedAt; - response.connectionId = messageRecord.connectionId; - response.role = messageRecord.role; - response.content = messageRecord.content; - response.sentTime = messageRecord.sentTime; - response.from = ""; - response.to = connRecord?.theirLabel || ""; - - return response; - }; - - deleteMessageById = async (id: string): Promise<void> => { - await this.askar.agent.basicMessages.deleteById(id); - }; - - getCreatedDids = async (): Promise<DidRecordDto[]> => { - const didRecords = await this.askar.agent.dids.getCreatedDids(); - return didRecords.map((p) => { - const dto = new DidRecordDto(); - const tags = p.getTags(); - - dto.did = p.did; - dto.role = p.role; - dto.method = tags.method; - dto.tags = tags; - - return dto; - }); - }; - - trustPingToConnection = async (connectionId: string) => { - const msg = await this.askar.agent.connections.sendPing(connectionId, { - responseRequested: true, - withReturnRouting: false, - }); - - const response = new BaseRecordDto(); - response.id = msg.toJSON()["@id"]; - - return response; - }; - - private getFirstDidWebRecord = async (): Promise<DidRecord> => { - const didWebs = await this.askar.agent.dids.getCreatedDids({ - method: "web", - }); - if (!didWebs.length) { - throw new EntityNotFoundError("Agent does not have did:web"); - } - return didWebs[0]; - }; -} diff --git a/libs/askar/src/askar/askar.module.ts b/libs/askar/src/askar/askar.module.ts index c2f542cef457dd735b3ff5514299703b737bf903..1095927b6e3195ab2b9570389a9107cfc0a94a24 100644 --- a/libs/askar/src/askar/askar.module.ts +++ b/libs/askar/src/askar/askar.module.ts @@ -1,6 +1,13 @@ import { Module, Global } from "@nestjs/common"; import { AskarService } from "./askar.service"; -import { AgentService } from "./agent.service"; +import { AgentAnoncredsService } from "./agent.anoncreds.service"; +import { AgentBasicMessagesService } from "./agent.basicMessages.service"; +import { AgentConnectionsService } from "./agent.connections.service"; +import { AgentCredentialsService } from "./agent.credentials.service"; +import { AgentDidsService } from "./agent.dids.service"; +import { AgentJsonldService } from "./agent.jsonld.service"; +import { AgentOobService } from "./agent.oob.service"; +import { AgentProofsService } from "./agent.proofs.service"; import { ConfigModule } from "@nestjs/config"; import { LedgersModule } from "@ocm-engine/ledgers"; import { AgentEventListenerService } from "./agent-event-listener.service"; @@ -10,11 +17,28 @@ import { GatewayClient } from "@ocm-engine/clients"; @Module({ imports: [ConfigModule, LedgersModule], providers: [ - AgentService, + AgentAnoncredsService, + AgentBasicMessagesService, + AgentConnectionsService, + AgentCredentialsService, + AgentDidsService, + AgentJsonldService, + AgentOobService, + AgentProofsService, AskarService, AgentEventListenerService, GatewayClient, ], - exports: [AgentService, AskarService], + exports: [ + AgentAnoncredsService, + AgentBasicMessagesService, + AgentConnectionsService, + AgentCredentialsService, + AgentDidsService, + AgentJsonldService, + AgentOobService, + AgentProofsService, + AskarService + ], }) export class AskarModule {} diff --git a/libs/askar/src/index.ts b/libs/askar/src/index.ts index 5fb756c0f59cf5403e52175ae5134ced9232a5ea..6aecc57083a2f46d7e8e40b0bd578c1ad7ef46b5 100644 --- a/libs/askar/src/index.ts +++ b/libs/askar/src/index.ts @@ -1,5 +1,12 @@ export * from "./askar/askar.module"; -export * from "./askar/agent.service"; +export * from "./askar/agent.anoncreds.service"; +export * from "./askar/agent.basicMessages.service"; +export * from "./askar/agent.connections.service"; +export * from "./askar/agent.credentials.service"; +export * from "./askar/agent.dids.service"; +export * from "./askar/agent.jsonld.service"; +export * from "./askar/agent.oob.service"; +export * from "./askar/agent.proofs.service"; export * from "./askar/askar.service"; export * from "./askar.dynamic.module"; export * from "./askar-rest/askar.rest.module";