diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 84bf572d2..850b3c16f 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -324,8 +324,40 @@ export interface IAgentStatus { isInitialized: boolean; } +export interface ISchema { + uri:string; +} +export interface IFields { + path: string[]; +} +export interface IConstraints { + fields: IFields[]; +} + +export interface IInputDescriptors { + + id:string; + name?:string; + purpose?:string; + schema:ISchema[]; + constraints?:IConstraints; + +} + +export interface IProofRequestPresentationDefinition { + id:string; + name: string; + input_descriptors:IInputDescriptors[]; +} + +export interface IPresentationExchange { + presentationDefinition:IProofRequestPresentationDefinition; + +} + interface IProofFormats { - indy: IndyProof; + indy?: IndyProof; + presentationExchange? : IPresentationExchange; } interface IndyProof { diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 22b4e57bb..dbefb5ef7 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -4,6 +4,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; import { AutoAccept } from '@credebl/enum/enum'; import { IProofFormats } from '../interfaces/verification.interface'; +import { ProofRequestType } from '../enum/verification.enum'; export class ProofRequestAttribute { @@ -69,6 +70,128 @@ class ProofPayload { protocolVersion: string; } +export class Fields { + @ApiProperty() + @IsArray() + @IsNotEmpty({ message: 'path is required.' }) + path: string[]; +} + +export class Constraints { + @ApiProperty({type: () => [Fields]}) + @IsOptional() + @IsNotEmpty({ message: 'Fields are required.' }) + @ValidateNested() + @Type(() => Fields) + fields: Fields[]; +} + + +export class Schema { + @ApiProperty() + @IsNotEmpty({ message: 'uri is required.' }) + @IsString() + uri:string; + +} +export class InputDescriptors { + @ApiProperty() + @IsNotEmpty({ message: 'id is required.' }) + @IsString() + id:string; + + @ApiProperty() + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'name is required.' }) + name:string; + + @ApiProperty() + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'purpose is required.' }) + purpose:string; + + + @ApiProperty({type: () => [Schema]}) + @IsNotEmpty({ message: 'schema is required.' }) + @ValidateNested() + @Type(() => Schema) + schema:Schema[]; + + + @ApiProperty({type: () => Constraints}) + @IsOptional() + @IsNotEmpty({ message: 'Constraints are required.' }) + @ValidateNested() + @Type(() => Constraints) + constraints:Constraints; + +} + +export class ProofRequestPresentationDefinition { + + @IsString() + @IsNotEmpty({ message: 'id is required.' }) + id: string; + + @IsString() + @IsOptional() + name: string; + + @ApiProperty({type: () => [InputDescriptors]}) + @IsNotEmpty({ message: 'inputDescriptors is required.' }) + @IsArray({ message: 'inputDescriptors must be an array' }) + @IsObject({ each: true }) + @Type(() => InputDescriptors) + @ValidateNested() + // eslint-disable-next-line camelcase + input_descriptors:InputDescriptors[]; +} + +export class ProofRequestAttributeDto { + @ApiProperty({ + 'example': [ + { + attributeName: 'attributeName', + condition: '>=', + value: 'predicates', + credDefId: 'string', + schemaId: 'string' + } + ], + type: () => [ProofRequestAttribute] +}) +@IsArray({ message: 'attributes must be in array' }) +@ValidateNested() +@IsObject({ each: true }) +@IsNotEmpty({ message: 'please provide valid attributes' }) +@Type(() => ProofRequestAttribute) +attributes?: ProofRequestAttribute[]; +} + +export class IndyDto { + @ApiProperty({ + 'example': { + 'attributes': [ + { + attributeName: 'attributeName', + condition: '>=', + value: 'predicates', + credDefId: 'string', + schemaId: 'string' + } + ] + }, + type: () => [ProofRequestAttributeDto] + }) + @ValidateNested() + @IsObject({ each: true }) + @IsNotEmpty({ message: 'please provide valid attributes' }) + @Type(() => ProofRequestAttributeDto) + indy: ProofRequestAttributeDto; +} + export class RequestProofDto extends ProofPayload { @ApiProperty() @IsString() @@ -78,29 +201,67 @@ export class RequestProofDto extends ProofPayload { connectionId: string; @ApiProperty({ - 'example': [ + 'example': + { + 'indy': [ + { + attributeName: 'attributeName', + condition: '>=', + value: 'predicates', + credDefId: 'string', + schemaId: 'string' + } + ] + }, + type: () => [IndyDto] + }) + @IsOptional() + @ValidateNested() + @IsObject({ message: 'ProofFormatDto must be an object' }) + @IsNotEmpty({ message: 'ProofFormatDto must not be empty' }) + @Type(() => IndyDto) + proofFormats?: IndyDto; + + @ApiProperty({ + 'example': { - attributeName: 'attributeName', - condition: '>=', - value: 'predicates', - credDefId: 'string', - schemaId: 'string' - } - ], - type: () => [ProofRequestAttribute] + id: '32f54163-7166-48f1-93d8-ff217bdb0653', + inputDescriptors: [ + { + 'id': 'healthcare_input_1', + 'name': 'Medical History', + 'schema': [ + { + 'uri': 'https://health-schemas.org/1.0.1/medical_history.json' + } + + ], + 'constraints': { + 'fields': [ + { + 'path': ['$.PatientID'] + } + ] + } + } + ] + }, + type: () => [ProofRequestPresentationDefinition] }) - @IsArray({ message: 'attributes must be in array' }) + @IsOptional() @ValidateNested() - @IsObject({ each: true }) - @IsNotEmpty({ message: 'please provide valid attributes' }) - @Type(() => ProofRequestAttribute) - attributes: ProofRequestAttribute[]; + @IsObject({ message: 'presentationDefinition must be an object' }) + @IsNotEmpty({ message: 'presentationDefinition must not be empty' }) + @Type(() => ProofRequestPresentationDefinition) + presentationDefinition?:ProofRequestPresentationDefinition; @ApiPropertyOptional() @IsOptional() @IsString({ message: 'comment must be in string' }) comment: string; + type:ProofRequestType; + orgId: string; @ApiPropertyOptional() @@ -156,85 +317,6 @@ export class OutOfBandRequestProof extends ProofPayload { autoAcceptProof: string; } -export class Fields { - @ApiProperty() - @IsArray() - @IsNotEmpty({ message: 'path is required.' }) - path: string[]; - } - -export class Constraints { - @ApiProperty({type: () => [Fields]}) - @IsOptional() - @IsNotEmpty({ message: 'Fields are required.' }) - @ValidateNested() - @Type(() => Fields) - fields: Fields[]; - } - - -export class Schema { - @ApiProperty() - @IsNotEmpty({ message: 'uri is required.' }) - @IsString() - uri:string; - -} -export class InputDescriptors { - @ApiProperty() - @IsNotEmpty({ message: 'id is required.' }) - @IsString() - id:string; - - @ApiProperty() - @IsString() - @IsOptional() - @IsNotEmpty({ message: 'name is required.' }) - name:string; - - @ApiProperty() - @IsString() - @IsOptional() - @IsNotEmpty({ message: 'purpose is required.' }) - purpose:string; - - - @ApiProperty({type: () => [Schema]}) - @IsNotEmpty({ message: 'schema is required.' }) - @ValidateNested() - @Type(() => Schema) - schema:Schema[]; - - - @ApiProperty({type: () => Constraints}) - @IsOptional() - @IsNotEmpty({ message: 'Constraints are required.' }) - @ValidateNested() - @Type(() => Constraints) - constraints:Constraints; - -} - -export class ProofRequestPresentationDefinition { - - @IsString() - @IsNotEmpty({ message: 'id is required.' }) - id: string; - - @IsString() - @IsOptional() - name: string; - - @ApiProperty({type: () => [InputDescriptors]}) - @IsNotEmpty({ message: 'inputDescriptors is required.' }) - @IsArray({ message: 'inputDescriptors must be an array' }) - @IsObject({ each: true }) - @Type(() => InputDescriptors) - @ValidateNested() - // eslint-disable-next-line camelcase - input_descriptors:InputDescriptors[]; -} - export class SendProofRequestPayload { @ApiPropertyOptional() diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 371b710ed..bc21cca38 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -170,7 +170,10 @@ export class VerificationController { @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBody({ type: RequestProofDto }) + @ApiBody({ type: RequestProofDto })@ApiQuery({ + name: 'requestType', + enum: ProofRequestType + }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @@ -178,11 +181,24 @@ export class VerificationController { @Res() res: Response, @User() user: IUserRequest, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, - @Body() requestProof: RequestProofDto + @Body() requestProof: RequestProofDto, + @Query('requestType') requestType:ProofRequestType = ProofRequestType.INDY ): Promise { - const attributeArray = []; - for (const attrData of requestProof.attributes) { + if (requestType === ProofRequestType.INDY) { + if (!requestProof.proofFormats) { + throw new BadRequestException(`type: ${requestType} requires proofFormats`); + } + } + + if (requestType === ProofRequestType.PRESENTATIONEXCHANGE) { + if (!requestProof.presentationDefinition) { + throw new BadRequestException(`type: ${requestType} requires presentationDefinition`); + } + } + if (requestProof.proofFormats) { + const attributeArray = []; + for (const attrData of requestProof.proofFormats.indy.attributes) { if (0 === attributeArray.length) { attributeArray.push(Object.values(attrData)[0]); } else if (!attributeArray.includes(Object.values(attrData)[0])) { @@ -192,8 +208,10 @@ export class VerificationController { } } + } requestProof.orgId = orgId; + requestProof.type = requestType; const proofData = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/verification/verification.service.ts b/apps/api-gateway/src/verification/verification.service.ts index 8a51651b4..4a49f48b8 100644 --- a/apps/api-gateway/src/verification/verification.service.ts +++ b/apps/api-gateway/src/verification/verification.service.ts @@ -7,6 +7,8 @@ import { WebhookPresentationProofDto } from './dto/webhook-proof.dto'; import { IProofPresentationDetails, IProofPresentationList } from '@credebl/common/interfaces/verification.interface'; import { IPresentation, IProofRequest, IProofRequestSearchCriteria } from './interfaces/verification.interface'; import { IProofPresentation } from './interfaces/verification.interface'; +// To do make a similar interface in API-gateway +import { IRequestProof } from 'apps/verification/src/interfaces/verification.interface'; @Injectable() @@ -43,7 +45,25 @@ export class VerificationService extends BaseService { * @param orgId * @returns Requested proof presentation details */ - sendProofRequest(requestProof: RequestProofDto, user: IUserRequest): Promise { + sendProofRequest(requestProofDto: RequestProofDto, user: IUserRequest): Promise { + const requestProof: IRequestProof = { + orgId: requestProofDto.orgId, + type: requestProofDto.type, + comment: requestProofDto.comment, + autoAcceptProof: requestProofDto.autoAcceptProof, + connectionId: requestProofDto.connectionId, + goalCode: requestProofDto.goalCode, + parentThreadId: requestProofDto.parentThreadId, + protocolVersion: requestProofDto.protocolVersion, + willConfirm: requestProofDto.willConfirm + }; + if (requestProofDto.proofFormats) { + requestProof.attributes = requestProofDto.proofFormats.indy.attributes; + } + if (requestProofDto.presentationDefinition) { + requestProof.presentationDefinition = requestProofDto.presentationDefinition; + } + const payload = { requestProof, user }; return this.sendNatsMessage(this.verificationServiceProxy, 'send-proof-request', payload); } diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 2cc744017..3807574ce 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -8,13 +8,20 @@ interface IProofRequestAttribute { value?: string; credDefId?: string; schemaId?: string; - credentialName: string; + credentialName?: string; +} + +export enum ProofRequestType { + INDY = 'indy', + PRESENTATIONEXCHANGE = 'presentationExchange' } export interface IRequestProof { orgId: string; connectionId?: string; - attributes: IProofRequestAttribute[]; + attributes?: IProofRequestAttribute[]; + type: ProofRequestType; + presentationDefinition?:IProofRequestPresentationDefinition; comment: string; autoAcceptProof: AutoAccept; protocolVersion?: string; @@ -122,7 +129,8 @@ export interface IPresentationExchange { } export interface IPresentationExchangeProofFormats { - presentationExchange : IPresentationExchange; + presentationExchange? : IPresentationExchange; + indy?: IndyProof } export interface ISendPresentationExchangeProofRequestPayload { protocolVersion: string; @@ -143,7 +151,7 @@ export interface ISendProofRequestPayload { comment?: string; connectionId?: string; proofFormats?: IProofFormats; - autoAcceptProof?: string; + autoAcceptProof?: AutoAccept; label?: string; goalCode?: string; parentThreadId?: string; @@ -151,6 +159,7 @@ export interface ISendProofRequestPayload { imageUrl?: string; isShortenUrl?: boolean; type?:string; + orgId?: string; presentationDefinition?:IProofRequestPresentationDefinition; reuseConnection?: boolean; recipientKey?:string; @@ -175,7 +184,7 @@ export interface IWSendProofRequestPayload { export interface IProofRequestPayload { url: string; apiKey?: string; - orgId?: string + orgId?: string; proofRequestPayload: ISendProofRequestPayload | ISendPresentationExchangeProofRequestPayload; } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 32bfa0598..c7ca7c990 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -6,7 +6,7 @@ import { IGetAllProofPresentations, IProofRequestSearchCriteria, IGetProofPresen import { VerificationRepository } from './repositories/verification.repository'; import { CommonConstants } from '@credebl/common/common.constant'; import { agent_invitations, org_agents, organisation, presentations } from '@prisma/client'; -import { OrgAgentType } from '@credebl/enum/enum'; +import { AutoAccept, OrgAgentType } from '@credebl/enum/enum'; import { ResponseMessages } from '@credebl/common/response-messages'; import * as QRCode from 'qrcode'; import { OutOfBandVerification } from '../templates/out-of-band-verification.template'; @@ -167,58 +167,57 @@ export class VerificationService { * @param orgId * @returns Requested proof presentation details */ - async sendProofRequest(requestProof: IRequestProof): Promise { + async sendProofRequest(requestProof: ISendProofRequestPayload): Promise { try { const comment = requestProof.comment ? requestProof.comment : ''; - let proofRequestPayload: ISendProofRequestPayload = { - protocolVersion: '', - comment: '', - connectionId: '', - proofFormats: { - indy: { - name: '', - requested_attributes: {}, - requested_predicates: {}, - version: '' - } - }, - autoAcceptProof: '', - label: '', - goalCode: '', - parentThreadId: '', - willConfirm: false - }; + const getAgentDetails = await this.verificationRepository.getAgentEndPoint(requestProof.orgId); - const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof); + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + const verificationMethodLabel = 'request-proof'; + const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); + + const payload: IProofRequestPayload = { + orgId: requestProof.orgId, + url, + proofRequestPayload: {} + }; - proofRequestPayload = { - protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', + const proofRequestPayload = { comment, connectionId: requestProof.connectionId, - proofFormats: { - indy: { - name: 'Proof Request', - version: '1.0', - // eslint-disable-next-line camelcase - requested_attributes: requestedAttributes, - // eslint-disable-next-line camelcase - requested_predicates: requestedPredicates - } - }, - autoAcceptProof: requestProof.autoAcceptProof ? requestProof.autoAcceptProof : 'never', + autoAcceptProof: requestProof.autoAcceptProof ? requestProof.autoAcceptProof : AutoAccept.Never, goalCode: requestProof.goalCode || undefined, parentThreadId: requestProof.parentThreadId || undefined, willConfirm: requestProof.willConfirm || undefined }; - const getAgentDetails = await this.verificationRepository.getAgentEndPoint(requestProof.orgId); + if (requestProof.type === ProofRequestType.INDY) { + const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof as IRequestProof); + payload.proofRequestPayload = { + protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', + proofFormats: { + indy: { + name: 'Proof Request', + version: '1.0', + requested_attributes: requestedAttributes, + requested_predicates: requestedPredicates + } + }, + ...proofRequestPayload + }; - const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); - const verificationMethodLabel = 'request-proof'; - const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); - - const payload = { orgId: requestProof.orgId, url, proofRequestPayload }; + } else if (requestProof.type === ProofRequestType.PRESENTATIONEXCHANGE) { + payload.proofRequestPayload = { + protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v2', + proofFormats: { + presentationExchange: { + presentationDefinition: requestProof.presentationDefinition + } + }, + ...proofRequestPayload + }; + } const getProofPresentationById = await this._sendProofRequest(payload); return getProofPresentationById?.response; @@ -346,8 +345,16 @@ export class VerificationService { // Destructuring 'outOfBandRequestProof' to remove emailId, as it is not used while agent operation - const { isShortenUrl, emailId, type, ...updateOutOfBandRequestProof } = outOfBandRequestProof; - outOfBandRequestProof.autoAcceptProof = outOfBandRequestProof.autoAcceptProof || 'always'; + const { isShortenUrl, emailId, type, reuseConnection, ...updateOutOfBandRequestProof } = outOfBandRequestProof; + let recipientKey: string | undefined; + if (true === reuseConnection) { + const data: agent_invitations[] = await this.verificationRepository.getRecipientKeyByOrgId(user.orgId); + if (data && 0 < data.length) { + const [firstElement] = data; + recipientKey = firstElement?.recipientKey ?? undefined; + } + } + outOfBandRequestProof.autoAcceptProof = outOfBandRequestProof.autoAcceptProof || AutoAccept.Always; let payload: IProofRequestPayload;