diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index a7cee044b..112a5a71a 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -9,7 +9,7 @@ import { IProofPresentationDetails } from '@credebl/common/interfaces/verificati @Controller() export class AgentServiceController { - constructor(private readonly agentServiceService: AgentServiceService) {} + constructor(private readonly agentServiceService: AgentServiceService) { } //DONE @@ -45,7 +45,7 @@ export class AgentServiceController { return this.agentServiceService.createCredentialDefinition(payload); } - // DONE + // DONE @MessagePattern({ cmd: 'agent-get-credential-definition' }) async getCredentialDefinitionById(payload: IGetCredDefAgentRedirection): Promise { return this.agentServiceService.getCredentialDefinitionById(payload); @@ -107,7 +107,7 @@ export class AgentServiceController { }): Promise { return this.agentServiceService.sendProofRequest(payload.proofRequestPayload, payload.url, payload.apiKey); } -//DONE + //DONE @MessagePattern({ cmd: 'agent-verify-presentation' }) async verifyPresentation(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.verifyPresentation(payload.url, payload.apiKey); @@ -119,9 +119,8 @@ export class AgentServiceController { return this.agentServiceService.getConnections(payload.url, payload.apiKey); } - //DONE - @MessagePattern({ cmd: 'agent-get-connections-by-connectionId' }) - async getConnectionsByconnectionId(payload: { url: string; apiKey: string }): Promise { + @MessagePattern({ cmd: 'agent-get-connection-details-by-connectionId' }) + async getConnectionsByconnectionId(payload: { url: string, apiKey: string }): Promise { return this.agentServiceService.getConnectionsByconnectionId(payload.url, payload.apiKey); } @@ -205,5 +204,22 @@ export class AgentServiceController { async getOrgAgentApiKey(payload: { orgId: string }): Promise { return this.agentServiceService.getOrgAgentApiKey(payload.orgId); } - + + @MessagePattern({ cmd: 'agent-receive-invitation-url' }) + async receiveInvitationUrl(payload: { + url, + apiKey, + receiveInvitationUrl + }): Promise { + return this.agentServiceService.receiveInvitationUrl(payload.receiveInvitationUrl, payload.url, payload.apiKey); + } + + @MessagePattern({ cmd: 'agent-receive-invitation' }) + async receiveInvitation(payload: { + url, + apiKey, + receiveInvitation + }): Promise { + return this.agentServiceService.receiveInvitation(payload.receiveInvitation, payload.url, payload.apiKey); + } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 9d2be9a16..d358abc3d 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -19,7 +19,7 @@ import * as dotenv from 'dotenv'; import * as fs from 'fs'; import { map } from 'rxjs/operators'; dotenv.config(); -import { IGetCredDefAgentRedirection, IConnectionDetails, IUserRequestInterface, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, IOutOfBandCredentialOffer, IAgentSpinUpSatus, ICreateTenant, IAgentStatus, ICreateOrgAgent, IOrgAgentsResponse, IProofPresentation, IAgentProofRequest, IPresentation } from './interface/agent-service.interface'; +import { IGetCredDefAgentRedirection, IConnectionDetails, IUserRequestInterface, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, IOutOfBandCredentialOffer, IAgentSpinUpSatus, ICreateTenant, IAgentStatus, ICreateOrgAgent, IOrgAgentsResponse, IProofPresentation, IAgentProofRequest, IPresentation, IReceiveInvitationUrl, IReceiveInvitation } from './interface/agent-service.interface'; import { AgentSpinUpStatus, AgentType, Ledgers, OrgAgentType } from '@credebl/enum/enum'; import { AgentServiceRepository } from './repositories/agent-service.repository'; import { ledgers, org_agents, organisation, platform_config } from '@prisma/client'; @@ -851,7 +851,6 @@ export class AgentServiceService { { headers: { 'authorization': platformAdminSpinnedUp.org_agents[0].apiKey } } ); - this.logger.debug(`API Response Data: ${JSON.stringify(tenantDetails)}`); return tenantDetails; } @@ -896,7 +895,6 @@ export class AgentServiceService { }; schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { - this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; }) .catch(error => { @@ -917,7 +915,6 @@ export class AgentServiceService { }; schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { - this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; }) .catch(error => { @@ -942,7 +939,6 @@ export class AgentServiceService { const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_GET_SCHEMA_BY_ID.replace('#', `${payload.schemaId}`)}`; schemaResponse = await this.commonService.httpGet(url, payload.schemaId) .then(async (schema) => { - this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; }); @@ -951,7 +947,6 @@ export class AgentServiceService { schemaResponse = await this.commonService.httpGet(url, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { - this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; }); } @@ -977,7 +972,6 @@ export class AgentServiceService { credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { - this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; }); @@ -990,7 +984,6 @@ export class AgentServiceService { }; credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { - this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; }); } @@ -1010,7 +1003,6 @@ export class AgentServiceService { const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_GET_CRED_DEF_BY_ID.replace('#', `${payload.credentialDefinitionId}`)}`; credDefResponse = await this.commonService.httpGet(url, payload.credentialDefinitionId) .then(async (credDef) => { - this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; }); @@ -1018,7 +1010,6 @@ export class AgentServiceService { const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CRED_DEF}`.replace('@', `${payload.payload.credentialDefinitionId}`).replace('#', `${payload.tenantId}`); credDefResponse = await this.commonService.httpGet(url, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { - this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; }); } @@ -1147,7 +1138,7 @@ export class AgentServiceService { try { const data = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response) .catch(error => this.handleAgentSpinupStatusErrors(error)); @@ -1309,6 +1300,30 @@ export class AgentServiceService { } } + async receiveInvitationUrl(receiveInvitationUrl: IReceiveInvitationUrl, url: string, apiKey: string): Promise { + try { + const receiveInvitationUrlRes = await this.commonService + .httpPost(url, receiveInvitationUrl, { headers: { 'authorization': apiKey } }) + .then(async response => response); + return receiveInvitationUrlRes; + } catch (error) { + this.logger.error(`Error in receive invitation in agent service : ${JSON.stringify(error)}`); + throw error; + } + } + + async receiveInvitation(receiveInvitation: IReceiveInvitation, url: string, apiKey: string): Promise { + try { + const receiveInvitationRes = await this.commonService + .httpPost(url, receiveInvitation, { headers: { 'authorization': apiKey } }) + .then(async response => response); + return receiveInvitationRes; + } catch (error) { + this.logger.error(`Error in receive invitation in agent service : ${JSON.stringify(error)}`); + throw error; + } + } + async getOrgAgentApiKey(orgId: string): Promise { try { let agentApiKey; diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 95c27446c..2c6e7f4c9 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -15,7 +15,7 @@ export interface IAgentSpinupDto { tenant?: boolean; ledgerName?: string[]; platformAdminEmail?: string; - apiKey?:string; + apiKey?: string; } export interface IOutOfBandCredentialOffer { @@ -83,26 +83,6 @@ export interface ITenantCredDef { agentEndPoint?: string; } -export interface ITenantCredDefDto { - tag: string; - schemaId: string; - issuerId: string; -} - -export interface IGetCredDefAgentRedirection { - credentialDefinitionId?: string; - tenantId?: string; - payload?: IGetCredDefFromTenantPayload; - apiKey?: string; - agentEndPoint?: string; - agentType?: string; - method?: string; -} - -export interface IGetCredDefFromTenantPayload { - credentialDefinitionId: string; -} - export interface IWalletProvision { orgId: string; externalIp: string; @@ -122,7 +102,7 @@ export interface IWalletProvision { afjVersion: string; protocol: string; tenant: boolean; - apiKey?:string; + apiKey?: string; } export interface IPlatformConfigDto { @@ -174,19 +154,19 @@ export interface IUserRequestInterface { tenantOrgId?: string; userRoleOrgPermissions?: UserRoleOrgPermsDto[]; orgName?: string; - selectedOrg: ISelectedOrgInterface; + selectedOrg: IOrgInterface; } -export interface ISelectedOrgInterface { +export interface IOrgInterface { id: string; userId: string; orgRoleId: string; orgId: string; orgRole: object; - organisation: IOrganizationInterface; + organisation: IOrganizationAgentInterface; } -export interface IOrganizationInterface { +export interface IOrganizationAgentInterface { name: string; description: string; org_agents: IOrgAgentInterface[] @@ -203,18 +183,6 @@ export interface IOrgAgentInterface { orgId: string; } -export interface ITenantCredDef { - tenantId?: string; - tag?: string; - schemaId?: string; - issuerId?: string; - payload?: ITenantCredDef; - method?: string; - agentType?: string; - apiKey?: string; - agentEndPoint?: string; -} - export interface ITenantCredDefDto { tag: string; schemaId: string; @@ -356,6 +324,9 @@ export interface IStoreAgent { id: string; } +export interface IAcceptCredentials { + credentialRecordId: string; +} export interface IAgentProofRequest { metadata: object; id: string; @@ -366,7 +337,7 @@ export interface IAgentProofRequest { threadId: string; autoAcceptProof: string; updatedAt: string; - } +} export interface IPresentation { _tags: ITags; @@ -377,6 +348,44 @@ export interface IStoreAgent { id: string; } +export interface IReceiveInvite { + alias?: string; + label?: string; + imageUrl?: string; + autoAcceptConnection?: boolean; + autoAcceptInvitation?: boolean; + reuseConnection?: boolean; + acceptInvitationTimeoutMs?: number; +} + +export interface IReceiveInvitationUrl extends IReceiveInvite { + invitationUrl: string; +} + +interface IService { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; + accept: string[]; +} + +interface IInvitation { + '@id': string; + '@type': string; + label: string; + goalCode: string; + goal: string; + accept: string[]; + handshake_protocols: string[]; + services: (IService | string)[]; + imageUrl?: string; +} + +export interface IReceiveInvitation extends IReceiveInvite { + invitation: IInvitation; +} export interface IProofPresentation { createdAt: string; protocolVersion: string; @@ -386,11 +395,11 @@ export interface IProofPresentation { autoAcceptProof: string; updatedAt: string; isVerified: boolean; - } +} interface ITags { connectionId: string; state: string; threadId: string; -} - +} + diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index dfe343149..c2a8fca92 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -1,6 +1,6 @@ import IResponseType, {IResponse} from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Controller, Logger, Post, Body, UseGuards, HttpStatus, Res, Get, Param, UseFilters, Query, Inject} from '@nestjs/common'; +import { Controller, Logger, Post, Body, UseGuards, HttpStatus, Res, Get, Param, UseFilters, Query, Inject } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { User } from '../authz/decorators/user.decorator'; @@ -8,7 +8,7 @@ import { AuthTokenResponse } from '../authz/dtos/auth-token-res.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ConnectionService } from './connection.service'; -import { ConnectionDto, CreateConnectionDto } from './dtos/connection.dto'; +import { ConnectionDto, CreateConnectionDto, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; import { IUserRequestInterface } from './interfaces'; import { Response } from 'express'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; @@ -21,7 +21,7 @@ import { IConnectionSearchinterface } from '../interfaces/ISchemaSearch.interfac import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; -import { ClientProxy, RpcException} from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; @UseFilters(CustomExceptionFilter) @Controller() @@ -33,7 +33,7 @@ export class ConnectionController { private readonly logger = new Logger('Connection'); constructor(private readonly connectionService: ConnectionService, - @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy + @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy ) { } /** @@ -82,7 +82,7 @@ export class ConnectionController { name: 'sortField', enum: SortFields, required: false - }) + }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getConnections( @Query() getAllConnectionsDto: GetAllConnectionsDto, @@ -90,7 +90,7 @@ export class ConnectionController { @Param('orgId') orgId: string, @Res() res: Response ): Promise { - + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllConnectionsDto; const connectionSearchCriteria: IConnectionSearchCriteria = { pageNumber, @@ -98,7 +98,7 @@ export class ConnectionController { pageSize, sortField, sortBy - }; + }; const connectionDetails = await this.connectionService.getConnections(connectionSearchCriteria, user, orgId); const finalResponse: IResponse = { @@ -138,42 +138,83 @@ export class ConnectionController { } + @Post('/orgs/:orgId/receive-invitation-url') + @ApiOperation({ summary: 'Receive Invitation URL', description: 'Receive Invitation URL' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async receiveInvitationUrl( + @Param('orgId') orgId: string, + @Body() receiveInvitationUrl: ReceiveInvitationUrlDto, + @User() user: IUserRequestInterface, + @Res() res: Response + ): Promise { + + const connectionData = await this.connectionService.receiveInvitationUrl(receiveInvitationUrl, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.receivenvitation, + data: connectionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Post('/orgs/:orgId/receive-invitation') + @ApiOperation({ summary: 'Receive Invitation', description: 'Receive Invitation' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async receiveInvitation( + @Param('orgId') orgId: string, + @Body() receiveInvitation: ReceiveInvitationDto, + @User() user: IUserRequestInterface, + @Res() res: Response + ): Promise { + + const connectionData = await this.connectionService.receiveInvitation(receiveInvitation, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.receivenvitation, + data: connectionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + /** * Catch connection webhook responses. * @Body connectionDto * @param orgId * @returns Callback URL for connection and created connections details */ - @Post('wh/:orgId/connections/') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: 'Catch connection webhook responses', - description: 'Callback URL for connection' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async getConnectionWebhook( - @Body() connectionDto: ConnectionDto, - @Param('orgId') orgId: string, - @Res() res: Response - ): Promise { - this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); - - const webhookUrl = await this.connectionService._getWebhookUrl(connectionDto.contextCorrelationId); - - if (webhookUrl) { - try { - await this.connectionService._postWebhookResponse(webhookUrl, { data: connectionDto }); - } catch (error) { - throw new RpcException(error.response ? error.response : error); + @Post('wh/:orgId/connections/') + @ApiExcludeEndpoint() + @ApiOperation({ + summary: 'Catch connection webhook responses', + description: 'Callback URL for connection' + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async getConnectionWebhook( + @Body() connectionDto: ConnectionDto, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); + + // const webhookUrl = await this.connectionService._getWebhookUrl(connectionDto.contextCorrelationId); + + // if (webhookUrl) { + // try { + // await this.connectionService._postWebhookResponse(webhookUrl, { data: connectionDto }); + // } catch (error) { + // throw new RpcException(error.response ? error.response : error); + // } + const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.create, + data: connectionData + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); } - const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.create, - data: connectionData - }; - - return res.status(HttpStatus.CREATED).json(finalResponse); - } } -} diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index ef6d94496..cab5d7d14 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -2,8 +2,8 @@ import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { Inject, Injectable, HttpException } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; -import { ConnectionDto, CreateConnectionDto } from './dtos/connection.dto'; -import { IUserRequestInterface } from './interfaces'; +import { ConnectionDto, CreateConnectionDto, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; +import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; @@ -71,6 +71,23 @@ export class ConnectionService extends BaseService { return this.sendNatsMessage(this.connectionServiceProxy, 'get-connection-details-by-connectionId', payload); } + receiveInvitationUrl( + receiveInvitationUrl: ReceiveInvitationUrlDto, + orgId: string, + user: IUserRequestInterface + ): Promise { + const payload = { user, receiveInvitationUrl, orgId }; + return this.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation-url', payload); + } + + receiveInvitation( + receiveInvitation: ReceiveInvitationDto, + orgId: string, + user: IUserRequestInterface + ): Promise { + const payload = { user, receiveInvitation, orgId }; + return this.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation', payload); + } async _getWebhookUrl(tenantId: string): Promise { const pattern = { cmd: 'get-webhookurl' }; diff --git a/apps/api-gateway/src/connection/dtos/connection.dto.ts b/apps/api-gateway/src/connection/dtos/connection.dto.ts index 19a876f4d..4662ea1b2 100644 --- a/apps/api-gateway/src/connection/dtos/connection.dto.ts +++ b/apps/api-gateway/src/connection/dtos/connection.dto.ts @@ -1,17 +1,18 @@ -import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; export class CreateConnectionDto { @ApiPropertyOptional() @IsOptional() - @IsString({ message: 'alias must be a string' }) + @IsString({ message: 'alias must be a string' }) @IsNotEmpty({ message: 'please provide valid alias' }) alias: string; @ApiPropertyOptional() @IsOptional() - @IsString({ message: 'label must be a string' }) + @IsString({ message: 'label must be a string' }) @IsNotEmpty({ message: 'please provide valid label' }) label: string; @@ -55,7 +56,7 @@ export class ConnectionDto { @ApiPropertyOptional() @IsOptional() theirLabel: string; - + @ApiPropertyOptional() @IsOptional() state: string; @@ -67,7 +68,7 @@ export class ConnectionDto { @ApiPropertyOptional() @IsOptional() autoAcceptConnection: boolean; - + @ApiPropertyOptional() @IsOptional() threadId: string; @@ -75,16 +76,135 @@ export class ConnectionDto { @ApiPropertyOptional() @IsOptional() protocol: string; - + @ApiPropertyOptional() @IsOptional() outOfBandId: string; @ApiPropertyOptional() @IsOptional() - updatedAt: string; + updatedAt: string; @ApiPropertyOptional() @IsOptional() contextCorrelationId: string; +} + +class ReceiveInvitationCommonDto { + @ApiPropertyOptional() + @IsOptional() + @IsString({ message: 'alias must be a string' }) + @IsNotEmpty({ message: 'please provide valid alias' }) + alias: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString({ message: 'label must be a string' }) + @IsNotEmpty({ message: 'please provide valid label' }) + label: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString({ message: 'imageUrl must be a string' }) + @IsNotEmpty({ message: 'please provide valid imageUrl' }) + imageUrl: string; + + @ApiPropertyOptional() + @IsOptional() + @IsBoolean({ message: 'autoAcceptConnection must be a boolean' }) + @IsNotEmpty({ message: 'please provide valid autoAcceptConnection' }) + autoAcceptConnection: boolean; + + @ApiPropertyOptional() + @IsOptional() + @IsBoolean({ message: 'autoAcceptInvitation must be a boolean' }) + @IsNotEmpty({ message: 'please provide valid autoAcceptInvitation' }) + autoAcceptInvitation: boolean; + + @ApiPropertyOptional() + @IsOptional() + @IsBoolean({ message: 'reuseConnection must be a boolean' }) + @IsNotEmpty({ message: 'please provide valid reuseConnection' }) + reuseConnection: boolean; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + @IsNotEmpty({ message: 'please provide valid acceptInvitationTimeoutMs' }) + acceptInvitationTimeoutMs: number; +} + +export class ReceiveInvitationUrlDto extends ReceiveInvitationCommonDto { + + @ApiProperty() + @IsOptional() + @IsString({ message: 'invitationUrl must be a string' }) + @IsNotEmpty({ message: 'please provide valid invitationUrl' }) + invitationUrl: string; +} + + +class ServiceDto { + @IsString() + id: string; + + @IsString() + serviceEndpoint: string; + + @IsString() + type: string; + + @IsString({ each: true }) + recipientKeys: string[]; + + @IsString({ each: true }) + routingKeys: string[]; + + @IsOptional() + @IsString({ each: true }) + accept: string[]; +} + +class InvitationDto { + @IsString() + '@id': string; + + @IsString() + '@type': string; + + @IsString() + label: string; + + @IsOptional() + @IsString() + goalCode: string; + + @IsOptional() + @IsString() + goal: string; + + @IsOptional() + @IsString({ each: true }) + accept: string[]; + + @IsOptional() + @IsString({ each: true }) + // eslint-disable-next-line camelcase + handshake_protocols: string[]; + + @ValidateNested({ each: true }) + @Type(() => ServiceDto) + services: ServiceDto[]; + + @IsString() + @IsOptional() + imageUrl?: string; +} + +export class ReceiveInvitationDto extends ReceiveInvitationCommonDto { + + @ApiProperty() + @ValidateNested() + @Type(() => InvitationDto) + invitation: InvitationDto; } \ No newline at end of file diff --git a/apps/api-gateway/src/connection/interfaces/index.ts b/apps/api-gateway/src/connection/interfaces/index.ts index 825a911ff..20f8f584b 100644 --- a/apps/api-gateway/src/connection/interfaces/index.ts +++ b/apps/api-gateway/src/connection/interfaces/index.ts @@ -27,7 +27,7 @@ export interface IOrganizationInterface { name: string; description: string; org_agents: IOrgAgentInterface[] - + } export interface IOrgAgentInterface { @@ -53,3 +53,65 @@ export class IConnectionInterface { outOfBandId: string; orgId: string; } + +interface Tags { + invitationId: string; + recipientKeyFingerprints: string[]; + role: string; + state: string; + threadId: string; +} + +interface IOutOfBandInvitationService { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; +} + +interface IOutOfBandInvitation { + "@type": string; + "@id": string; + label: string; + accept: string[]; + handshake_protocols: string[]; + services: IOutOfBandInvitationService[]; +} + +interface IOutOfBandRecord { + _tags: Tags; + metadata?: { [key: string]: string }; + id: string; + createdAt: string; + outOfBandInvitation: IOutOfBandInvitation; + role: string; + state: string; + autoAcceptConnection: boolean; + reusable: boolean; + updatedAt: string; +} + +interface IConnectionRecord { + _tags: { [key: string]: string }; + metadata: { [key: string]: string }; + connectionTypes: string[]; + id: string; + createdAt: string; + did: string; + invitationDid: string; + theirLabel: string; + state: string; + role: string; + alias: string; + autoAcceptConnection: boolean; + threadId: string; + protocol: string; + outOfBandId: string; + updatedAt: string; +} + +export interface IReceiveInvitationRes { + outOfBandRecord: IOutOfBandRecord; + connectionRecord: IConnectionRecord; +} \ No newline at end of file diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 9ef90aedf..1cbe07d00 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -5,7 +5,10 @@ import { IConnection, ICreateConnection, IFetchConnectionById, - IFetchConnections + IFetchConnections, + IReceiveInvitationByOrg, + IReceiveInvitationByUrlOrg, + IReceiveInvitationResponse } from './interfaces/connection.interfaces'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; @@ -61,4 +64,16 @@ export class ConnectionController { const { user, connectionId, orgId } = payload; return this.connectionService.getConnectionsById(user, connectionId, orgId); } + + @MessagePattern({ cmd: 'receive-invitation-url' }) + async receiveInvitationUrl(payload: IReceiveInvitationByUrlOrg): Promise { + const { user, receiveInvitationUrl, orgId } = payload; + return this.connectionService.receiveInvitationUrl(user, receiveInvitationUrl, orgId); + } + + @MessagePattern({ cmd: 'receive-invitation' }) + async receiveInvitation(payload: IReceiveInvitationByOrg): Promise { + const { user, receiveInvitation, orgId } = payload; + return this.connectionService.receiveInvitation(user, receiveInvitation, orgId); + } } diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index cc50ef4ee..7d4add53f 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -8,7 +8,10 @@ import { IConnection, IConnectionInvitation, IConnectionSearchCriteria, - ICreateConnection + ICreateConnection, + IReceiveInvitation, + IReceiveInvitationResponse, + IReceiveInvitationUrl } from './interfaces/connection.interfaces'; import { ConnectionRepository } from './connection.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; @@ -358,5 +361,140 @@ export class ConnectionService { }, error.status); } } + + async receiveInvitationUrl(user: IUserRequest, receiveInvitationUrl: IReceiveInvitationUrl, orgId: string): Promise { + try { + const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + + const { agentEndPoint } = agentDetails; + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + + + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION_URL}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}` + .replace('#', agentDetails.tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const createConnectionInvitation = await this._receiveInvitationUrl(url, apiKey, receiveInvitationUrl); + return createConnectionInvitation; + + + } catch (error) { + this.logger.error(`[receiveInvitationUrl] - error in rece ive invitation url : ${JSON.stringify(error)}`); + + if (error?.response?.error?.reason) { + throw new RpcException({ + message: ResponseMessages.connection.error.connectionNotFound, + statusCode: error?.response?.status, + error: error?.response?.error?.reason + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + } + + async _receiveInvitationUrl( + url: string, + apiKey: string, + receiveInvitationUrl: IReceiveInvitationUrl + ): Promise { + + const pattern = { cmd: 'agent-receive-invitation-url' }; + const payload = { url, apiKey, receiveInvitationUrl }; + return this.connectionServiceProxy + .send(pattern, payload) + .toPromise() + .catch(error => { + this.logger.error( + `[_receiveInvitationUrl] [NATS call]- error in receive invitation url : ${JSON.stringify(error)}` + ); + throw new HttpException( + { + status: error.statusCode, + error: error.error?.message?.error ? error.error?.message?.error : error.error, + message: error.message + }, error.error); + }); + } + + async receiveInvitation(user: IUserRequest, receiveInvitation: IReceiveInvitation, orgId: string): Promise { + try { + const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + + const { agentEndPoint } = agentDetails; + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}` + .replace('#', agentDetails.tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const createConnectionInvitation = await this._receiveInvitation(url, apiKey, receiveInvitation); + return createConnectionInvitation; + + + } catch (error) { + this.logger.error(`[receiveInvitation] - error in receive invitation : ${JSON.stringify(error)}`); + + if (error?.response?.error?.reason) { + throw new RpcException({ + message: ResponseMessages.connection.error.connectionNotFound, + statusCode: error?.response?.status, + error: error?.response?.error?.reason + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + } + + async _receiveInvitation( + url: string, + apiKey: string, + receiveInvitation: IReceiveInvitation + ): Promise { + + const pattern = { cmd: 'agent-receive-invitation' }; + const payload = { url, apiKey, receiveInvitation }; + return this.connectionServiceProxy + .send(pattern, payload) + .toPromise() + .catch(error => { + this.logger.error( + `[_receiveInvitation] [NATS call]- error in receive invitation : ${JSON.stringify(error)}` + ); + throw new HttpException( + { + status: error.statusCode, + error: error.error?.message?.error ? error.error?.message?.error : error.error, + message: error.message + }, error.error); + }); + } } diff --git a/apps/connection/src/interfaces/connection.interfaces.ts b/apps/connection/src/interfaces/connection.interfaces.ts index 4b2ac1420..717484f3f 100644 --- a/apps/connection/src/interfaces/connection.interfaces.ts +++ b/apps/connection/src/interfaces/connection.interfaces.ts @@ -117,4 +117,117 @@ export interface IConnectionSearchCriteria { sortBy: string; searchByText: string; user: IUserRequestInterface -} \ No newline at end of file +} + +export interface IReceiveInvitationByUrlOrg { + user: IUserRequestInterface, + receiveInvitationUrl: IReceiveInvitationUrl, + orgId: string +} + +export interface IReceiveInvitationUrl extends IReceiveInvite { + invitationUrl: string; +} + +export interface IReceiveInvitationByOrg { + user: IUserRequestInterface, + receiveInvitation: IReceiveInvitation, + orgId: string +} + +interface Service { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; + accept: string[]; +} + +interface Invitation { + '@id': string; + '@type': string; + label: string; + goalCode: string; + goal: string; + accept: string[]; + handshake_protocols: string[]; + services: (Service | string)[]; + imageUrl?: string; +} + +export interface IReceiveInvite { + alias?: string; + label?: string; + imageUrl?: string; + autoAcceptConnection?: boolean; + autoAcceptInvitation?: boolean; + reuseConnection?: boolean; + acceptInvitationTimeoutMs?: number; +} + +export interface IReceiveInvitation extends IReceiveInvite { + invitation: Invitation; +} + +interface Tags { + invitationId: string; + recipientKeyFingerprints: string[]; + role: string; + state: string; + threadId: string; +} + +interface OutOfBandInvitationService { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; +} + +interface OutOfBandInvitation { + "@type": string; + "@id": string; + label: string; + accept: string[]; + handshake_protocols: string[]; + services: OutOfBandInvitationService[]; +} + +interface OutOfBandRecord { + _tags: Tags; + metadata?: { [key: string]: string }; + id: string; + createdAt: string; + outOfBandInvitation: OutOfBandInvitation; + role: string; + state: string; + autoAcceptConnection: boolean; + reusable: boolean; + updatedAt: string; +} + +interface ConnectionRecord { + _tags: { [key: string]: string }; + metadata: { [key: string]: string }; + connectionTypes: string[]; + id: string; + createdAt: string; + did: string; + invitationDid: string; + theirLabel: string; + state: string; + role: string; + alias: string; + autoAcceptConnection: boolean; + threadId: string; + protocol: string; + outOfBandId: string; + updatedAt: string; +} + +export interface IReceiveInvitationResponse { + outOfBandRecord: OutOfBandRecord; + connectionRecord: ConnectionRecord; +} diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 6bf5e3aa1..0e99e8c3f 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -23,6 +23,8 @@ export enum CommonConstants { URL_CONN_REMOVE_CONNECTION_BY_ID = '/connections/#/remove', URL_CONN_METADATA = '/connections/#/metadata', URL_CONN_LEGACY_INVITE = '/oob/create-legacy-invitation', + URL_RECEIVE_INVITATION_URL = '/oob/receive-invitation-url', + URL_RECEIVE_INVITATION = '/oob/receive-invitation', URL_CONN_INVITATION = '/url', // WALLET SERVICES @@ -70,6 +72,8 @@ export enum CommonConstants { URL_ISSUE_GET_CREDS_AFJ = '/credentials', URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID = '/credentials', URL_OUT_OF_BAND_CREDENTIAL_OFFER = '/credentials/create-offer-oob', + URL_ACCEPT_CREDENTIALS = '/credentials/accept-offer', + // SCHEMA & CRED DEF SERVICES URL_SCHM_CREATE_SCHEMA = '/schemas', @@ -99,6 +103,10 @@ export enum CommonConstants { URL_SHAGENT_ACCEPT_PRESENTATION = '/multi-tenancy/proofs/@/accept-presentation/#', URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST = '/multi-tenancy/proofs/create-request-oob/#', URL_SHAGENT_PROOF_FORM_DATA = '/multi-tenancy/form-data/#/@', + URL_SHAGENT_ACCEPT_OFFER = '/multi-tenancy/credentials/accept-offer/#', + URL_SHAGENT_RECEIVE_INVITATION_URL = '/multi-tenancy/receive-invitation-url/#', + URL_SHAGENT_RECEIVE_INVITATION = '/multi-tenancy/receive-invitation/#', + // PROOF SERVICES URL_SEND_PROOF_REQUEST = '/proofs/request-proof', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index a2c440288..9f95f42bb 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -198,7 +198,9 @@ export const ResponseMessages = { connection: { success: { create: 'Connection created successfully', - fetch: 'Connection details fetched successfully' + receivenvitation: 'Invitation received successfully', + fetchConnection: 'Connection details fetched successfully', + fetch: 'Connections details fetched successfully' }, error: { exists: 'Connection is already exist',