From 86c7b0a2833db3d96009592e6cc8dc6af3c5cb32 Mon Sep 17 00:00:00 2001 From: Nishad Date: Wed, 18 Oct 2023 19:47:38 +0530 Subject: [PATCH 001/117] wip cli script for platform admin user Signed-off-by: Nishad --- libs/prisma-service/cli.ts | 98 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 7 +++ 3 files changed, 106 insertions(+) create mode 100644 libs/prisma-service/cli.ts diff --git a/libs/prisma-service/cli.ts b/libs/prisma-service/cli.ts new file mode 100644 index 000000000..ad126dbdb --- /dev/null +++ b/libs/prisma-service/cli.ts @@ -0,0 +1,98 @@ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-var-requires */ + +const {createInterface} = require('readline'); + +const { PrismaClient } = require('@prisma/client'); +const { createClient } = require('@supabase/supabase-js'); + +// import { Logger } from '@nestjs/common'; +// import { PrismaClient } from '@prisma/client'; + +// import {} from './data/' +const prisma = new PrismaClient(); +// const supabaseService = new SupabaseService(); + +const clientInstance = createClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_KEY, + { + auth: { + persistSession: false //or true + } + } +); +// const logger = new Logger('Init seed DB'); + +const readline = createInterface({ + input: process.stdin, + output: process.stdout +}); + +const readLineAsync = msg => new Promise(resolve => { + readline.question(msg, userRes => { + resolve(userRes); + }); + }); + +const createUser = async () => { + const name = await readLineAsync('Enter user name: '); + const email = await readLineAsync('Enter email address: '); + const password = await readLineAsync('Enter your password: '); + + try { + const supaUser = await clientInstance.auth.signUp({ + email: email.toString(), + password: password.toString() + }); + + const supaId = supaUser.data?.user?.id; + + const user = await prisma.user.create({ + data: { + 'firstName': name.toString(), + 'lastName': 'Tirang', + 'email': email.toString(), + 'username': email.toString(), + 'password': '####Please provide encrypted password using crypto-js###', + 'verificationCode': '', + 'isEmailVerified': true, + 'supabaseUserId': supaId + } + }); + console.log('User created:', user); +} catch (e) { + console.error('An error occurred in createUser:', e); +} +}; + +const createOrganization = async () => { + const organizationName = await readLineAsync('Enter organization name: '); + + // const organization = await prisma.organisation.create({ + // data: { + // name: organizationName + // } + // }); + console.log('Organization created:', organizationName); +}; + + async function main() { + + const choice = await readLineAsync('Choose an action (1. Create user, 2. Create organization): '); + + if ('1' === choice) { + await createUser(); + } else if ('2' === choice) { + await createOrganization(); + } else { + console.error('Invalid choice.'); + } + + // Close the Prisma client connection + // await prisma.$disconnect(); + readline.close(); +} + +main().catch((e) => console.error(e)); diff --git a/package.json b/package.json index 05b93ed2b..1277ec528 100755 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "json2csv": "^5.0.7", "jsonwebtoken": "^9.0.1", "jwks-rsa": "^3.0.1", + "linebyline": "^1.3.0", "moment": "^2.29.3", "nanoid": "^4.0.2", "nats": "^2.15.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bd4c2f4b..3cf80c7bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,6 +128,9 @@ dependencies: jwks-rsa: specifier: ^3.0.1 version: 3.0.1 + linebyline: + specifier: ^1.3.0 + version: 1.3.0 moment: specifier: ^2.29.3 version: 2.29.3 @@ -5948,6 +5951,10 @@ packages: unicode-trie: 2.0.0 dev: false + /linebyline@1.3.0: + resolution: {integrity: sha512-3fpIYMrSU77OCf89hjXKuCx6vGwgWEu4N5DDCGqgZ1BF0HYy9V8IbQb/3+VWIU17iBQ83qQoUokH0AhPMOTi7w==} + dev: false + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true From 438449f8d2a5b931aa2135cea825ea14e64f90a5 Mon Sep 17 00:00:00 2001 From: Nishad Date: Thu, 19 Oct 2023 14:49:42 +0530 Subject: [PATCH 002/117] refactored cli commands file Signed-off-by: Nishad --- libs/prisma-service/cli.ts | 64 ++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/libs/prisma-service/cli.ts b/libs/prisma-service/cli.ts index ad126dbdb..671b6a1fc 100644 --- a/libs/prisma-service/cli.ts +++ b/libs/prisma-service/cli.ts @@ -36,8 +36,39 @@ const readLineAsync = msg => new Promise(resolve => { }); }); + const agentSetup = async () => { + const choice = await readLineAsync('Choose an option for agent spinup (1. Dedicated, 2. Shared): '); + + if ('1' === choice) { + console.log('Dedicated Agent'); + } else if ('2' === choice) { + console.log('Shared Agent'); + } else { + console.error('Invalid choice.'); + } +}; + +const createOrganization = async () => { + const organizationName = await readLineAsync('Enter organization name: '); + const organizationDescription = await readLineAsync('Enter organization description: '); + + const organization = await prisma.organisation.create({ + data: { + 'name': organizationName, + 'description': organizationDescription, + 'logoUrl': '', + 'website': '', + 'publicProfile': true + } + }); + console.log('Organization created:', organization); + + await agentSetup(); +}; + + const createUser = async () => { - const name = await readLineAsync('Enter user name: '); + const name = await readLineAsync('Enter your name: '); const email = await readLineAsync('Enter email address: '); const password = await readLineAsync('Enter your password: '); @@ -62,33 +93,26 @@ const createUser = async () => { } }); console.log('User created:', user); + + await createOrganization(); + } catch (e) { console.error('An error occurred in createUser:', e); } }; -const createOrganization = async () => { - const organizationName = await readLineAsync('Enter organization name: '); - - // const organization = await prisma.organisation.create({ - // data: { - // name: organizationName - // } - // }); - console.log('Organization created:', organizationName); -}; - async function main() { + await createUser(); - const choice = await readLineAsync('Choose an action (1. Create user, 2. Create organization): '); + // const choice = await readLineAsync('Choose an action (1. Create user, 2. Create organization): '); - if ('1' === choice) { - await createUser(); - } else if ('2' === choice) { - await createOrganization(); - } else { - console.error('Invalid choice.'); - } + // if ('1' === choice) { + // await createUser(); + // } else if ('2' === choice) { + // await createOrganization(); + // } else { + // console.error('Invalid choice.'); + // } // Close the Prisma client connection // await prisma.$disconnect(); From 526037380b74bcabad562ebe424536951b4f43bb Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 23 Oct 2023 11:40:28 +0530 Subject: [PATCH 003/117] feat: get all ecosystem's schemas Signed-off-by: tipusinghaw --- .../src/ecosystem/ecosystem.controller.ts | 76 ++++++++++++---- .../src/ecosystem/ecosystem.service.ts | 10 +++ .../interfaces/endorsements.interface.ts | 9 ++ apps/ecosystem/src/ecosystem.controller.ts | 10 +++ apps/ecosystem/src/ecosystem.repository.ts | 85 +++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 87 ++++++++++++++++--- libs/common/src/response-messages/index.ts | 6 +- .../migration.sql | 8 ++ libs/prisma-service/prisma/schema.prisma | 1 + 9 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231018150349_endorsement_transaction_resource_id/migration.sql diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index d9a8bf1a0..2044eef0d 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -80,6 +80,44 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/:ecosystemId/schema') + @ApiOperation({ summary: 'Get all ecosystem schema', description: 'Get all ecosystem schema' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getAllEcosystemSchemas( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllEcosystemSchemaDto: GetAllEcosystemInvitationsDto, + @Res() res: Response + ): Promise { + + const schemaList = await this.ecosystemService.getAllEcosystemSchemas(ecosystemId, orgId, getAllEcosystemSchemaDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetchEndorsors, + data: schemaList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + @Get('/:orgId') @ApiOperation({ summary: 'Get all organization ecosystems', description: 'Get all existing ecosystems of an specific organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @@ -253,9 +291,9 @@ export class EcosystemController { @Post('/:orgId') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER) + // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + // @ApiBearerAuth() + // @Roles(OrgRoles.OWNER) async createNewEcosystem( @Body() createOrgDto: CreateEcosystemDto, @Param('orgId') orgId: string, @@ -272,10 +310,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId, ecosystemId); const finalResponse: IResponseType = { @@ -289,10 +327,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/cred-def') @ApiOperation({ summary: 'Request new credential-definition', description: 'Request new credential-definition' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId, ecosystemId); const finalResponse: IResponseType = { @@ -305,10 +343,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { @@ -321,10 +359,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/sumbit/:endorsementId') @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 1ad6f07af..c3c8a3a92 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -142,6 +142,16 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); } + async getAllEcosystemSchemas( + ecosystemId: string, + orgId: string, + getAllEcosystemSchemaDto: GetAllEcosystemInvitationsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllEcosystemSchemaDto; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search }; + return this.sendNats(this.serviceProxy, 'get-all-ecosystem-schemas', payload); + } + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId: string): Promise { const payload = { requestSchemaPayload, orgId, ecosystemId }; diff --git a/apps/ecosystem/interfaces/endorsements.interface.ts b/apps/ecosystem/interfaces/endorsements.interface.ts index d4c837c06..a8024a05e 100644 --- a/apps/ecosystem/interfaces/endorsements.interface.ts +++ b/apps/ecosystem/interfaces/endorsements.interface.ts @@ -6,4 +6,13 @@ export interface GetEndorsementsPayload { pageSize: number; search: string; type: string; + } + + export interface GetAllSchemaList { + ecosystemId: string; + orgId: string; + status: string; + pageNumber: number; + pageSize: number; + search: string; } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 77469fb10..50fd1b162 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -133,6 +133,16 @@ export class EcosystemController { ); } + + @MessagePattern({ cmd: 'get-all-ecosystem-schemas' }) + async getAllEcosystemSchemas( + @Body() payload: GetEndorsementsPayload + ): Promise { + return this.ecosystemService.getAllEcosystemSchemas( + payload + ); + } + @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) async deleteInvitation( @Body() payload: { invitationId: string } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 96c265473..3f6d0baff 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -8,6 +8,7 @@ import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interface import { ResponseMessages } from '@credebl/common/response-messages'; import { NotFoundException } from '@nestjs/common'; import { CommonConstants } from '@credebl/common/common.constant'; +import { GetAllSchemaList } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase @Injectable() @@ -549,6 +550,66 @@ export class EcosystemRepository { } } + + async getAllEcosystemSchemasDetails(payload: GetAllSchemaList): Promise<{ + schemasCount: number; + schemasResult: { + createDateTime: Date; + createdBy: number; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: number; + }[]; + }> { + try { + const { ecosystemId, search, pageNumber, pageSize } = payload; + + const schemaDetails = await this.prisma.endorsement_transaction.findMany({ + where: { + type: endorsementTransactionType.SCHEMA, + status: endorsementTransactionStatus.SUBMITED, + ecosystemOrgs: { + ecosystem: { + id: ecosystemId + } + } + } + }); + + const schemaArray = []; + schemaDetails.map((schemaData) => schemaArray.push(schemaData.resourceId)); + + const schemasResult = await this.prisma.schema.findMany({ + where: { + OR: [ + { version: { contains: search, mode: 'insensitive' } }, + { name: { contains: search, mode: 'insensitive' } }, + { schemaLedgerId: { contains: search, mode: 'insensitive' } } + ], + schemaLedgerId: { + in: schemaArray + } + }, + take: Number(pageSize), + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }); + const schemasCount = schemaArray.length; + + return { schemasCount, schemasResult }; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + /** * Description: Get getAgentEndPoint by orgId * @param orgId @@ -655,7 +716,8 @@ export class EcosystemRepository { ecosystemOrgId, responsePayload: '', type, - requestBody + requestBody, + resourceId: '' } }); } catch (error) { @@ -786,6 +848,27 @@ export class EcosystemRepository { } } + async updateResourse( + endorsementId: string, + resourceId: string + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + resourceId + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + async saveSchema(schemaResult: SaveSchema): Promise { try { const { name, version, attributes, schemaLedgerId, issuerId, createdBy, lastChangedBy, publisherDid, orgId, ledgerId } = schemaResult; diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c325b28dc..57383ddff 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -14,7 +14,7 @@ import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endor import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; import { CreateEcosystem, CredDefMessage, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; -import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; +import { GetAllSchemaList, GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase import { credential_definition, org_agents, platform_config, schema, user } from '@prisma/client'; @@ -38,10 +38,10 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { - const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); - if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); - }; + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); if (!createEcosystem) { throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); @@ -105,11 +105,11 @@ export class EcosystemService { ecosystem: ecosystemDetails['ecosystem'], membersCount: endorseMemberCount.membersCount, endorsementsCount: endorseMemberCount.endorsementsCount, - ecosystemLead:{ - role: ecosystemDetails['ecosystemRole']['name'], - orgName: ecosystemDetails['orgName'], - config: endorseMemberCount.ecosystemConfigData - } + ecosystemLead: { + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['orgName'], + config: endorseMemberCount.ecosystemConfigData + } }; return dashboardDetails; @@ -187,9 +187,9 @@ export class EcosystemService { async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); - + if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); }; const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); @@ -342,7 +342,7 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload:RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -412,7 +412,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload:RequestCredDeffEndorsement, orgId:number, ecosystemId:string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -779,6 +779,13 @@ export class EcosystemService { await this.updateTransactionStatus(endorsementId); if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + + const updateSchemaId = await this._updateResourceId(endorsementId, endorsementTransactionType.SCHEMA, submitTransactionRequest); + + if (!updateSchemaId) { + + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateSchemaId); + } return this.handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { @@ -797,7 +804,12 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } + const updateCredDefId = await this._updateResourceId(endorsementId, endorsementTransactionType.CREDENTIAL_DEFINITION, submitTransactionRequest); + + if (!updateCredDefId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateCredDefId); + } return this.handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } } catch (error) { @@ -830,6 +842,26 @@ export class EcosystemService { } } + async _updateResourceId(endorsementId: string, transactionType: endorsementTransactionType, transactionDetails: object): Promise { + try { + // eslint-disable-next-line prefer-destructuring + const message = transactionDetails['message']; + if (!message) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidMessage); + } + + const resourceId = message[transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId']; + + if (!resourceId) { + throw new Error(`${ResponseMessages.ecosystem.error.invalidTransactionMessage} Missing "${transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId'}" property.`); + } + + return await this.ecosystemRepository.updateResourse(endorsementId, resourceId); + } catch (error) { + this.logger.error(`Error updating resource ID: ${JSON.stringify(error)}`); + } + } + async getAgentUrl( orgAgentTypeId: number, @@ -950,6 +982,33 @@ export class EcosystemService { } } + + async getAllEcosystemSchemas(ecosystemSchemas: GetAllSchemaList): Promise { + try { + + const response = await this.ecosystemRepository.getAllEcosystemSchemasDetails(ecosystemSchemas); + + const schemasDetails = response?.schemasResult.map(schemaAttributeItem => { + const attributes = JSON.parse(schemaAttributeItem.attributes); + return { ...schemaAttributeItem, attributes }; + }); + + const schemasResponse = { + totalItems: response.schemasCount, + hasNextPage: ecosystemSchemas.pageSize * ecosystemSchemas.pageNumber < response.schemasCount, + hasPreviousPage: 1 < ecosystemSchemas.pageNumber, + nextPage: ecosystemSchemas.pageNumber + 1, + previousPage: ecosystemSchemas.pageNumber - 1, + lastPage: Math.ceil(response.schemasCount / ecosystemSchemas.pageSize), + data: schemasDetails + }; + return schemasResponse; + } catch (error) { + this.logger.error(`In error fetching all ecosystem schemas: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + /** * @returns EndorsementTransaction Status message */ diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e713dc8cc..103a95e03 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -234,7 +234,11 @@ export const ResponseMessages = { invalidAgentUrl: 'Invalid agent url', EndorsementTransactionNotFoundException: 'Endorsement transaction with status requested not found', OrgOrEcosystemNotFoundExceptionForEndorsementTransaction: 'The endorsement transaction status cant be updated', - ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.' + ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.', + updateSchemaId: 'Error while updating the schema id', + updateCredDefId: 'Error while updating the credential-definition', + invalidMessage: 'Invalid transaction details. Missing "message" property.', + invalidTransactionMessage: 'Invalid transaction details' } } }; \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20231018150349_endorsement_transaction_resource_id/migration.sql b/libs/prisma-service/prisma/migrations/20231018150349_endorsement_transaction_resource_id/migration.sql new file mode 100644 index 000000000..8b5ab06a3 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231018150349_endorsement_transaction_resource_id/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `resourceId` to the `endorsement_transaction` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "endorsement_transaction" ADD COLUMN "resourceId" TEXT NOT NULL; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index d3d57fe61..f423c1cb0 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -398,6 +398,7 @@ model endorsement_transaction { status String ecosystemOrgId String requestBody Json? + resourceId String? ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) } From c9c52c4e4e51b98140dfdb208de334193a0bb748 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 23 Oct 2023 11:46:13 +0530 Subject: [PATCH 004/117] fix: removed commented code Signed-off-by: tipusinghaw --- .../src/ecosystem/ecosystem.controller.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 6fb9499d9..1504b0074 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -291,9 +291,9 @@ export class EcosystemController { @Post('/:orgId') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - // @ApiBearerAuth() - // @Roles(OrgRoles.OWNER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER) async createNewEcosystem( @Body() createOrgDto: CreateEcosystemDto, @Param('orgId') orgId: string, @@ -310,10 +310,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId, ecosystemId); const finalResponse: IResponseType = { @@ -327,10 +327,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/cred-def') @ApiOperation({ summary: 'Request new credential-definition', description: 'Request new credential-definition' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId, ecosystemId); const finalResponse: IResponseType = { @@ -343,10 +343,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { @@ -359,10 +359,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/sumbit/:endorsementId') @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { From ee971359fd7729c3b2c6571374c1c2f1320d003b Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 13 Oct 2023 16:03:23 +0530 Subject: [PATCH 005/117] feat: refactor api for passkey module Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- .../api-gateway/src/authz/authz.controller.ts | 36 +- apps/api-gateway/src/dtos/fido-user.dto.ts | 6 +- apps/api-gateway/src/fido/fido.controller.ts | 182 ++++---- apps/api-gateway/src/fido/fido.service.ts | 22 +- .../repositories/user-device.repository.ts | 437 +++++++++--------- apps/user/src/fido/dtos/fido-user.dto.ts | 16 +- apps/user/src/fido/fido.controller.ts | 4 +- apps/user/src/fido/fido.service.ts | 51 +- apps/user/src/user.service.ts | 10 +- 9 files changed, 384 insertions(+), 380 deletions(-) diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index e1b9c3585..c2100717b 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Get, @@ -82,16 +83,35 @@ export class AuthzController { @Post('/signup') @ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform' }) async addUserDetails(@Body() userInfo: AddUserDetails, @Res() res: Response): Promise { - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + let finalResponse; + let userDetails; - userInfo.password = decryptedPassword; - const userDetails = await this.authzService.addUserDetails(userInfo); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; + if (false === userInfo.isPasskey) { + + const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { + this.commonService.passwordValidation(decryptedPassword); + userInfo.password = decryptedPassword; + userDetails = await this.authzService.addUserDetails(userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } else { + throw new BadRequestException('Password name must be between 8 to 50 Characters'); + } + } else { + + userDetails = await this.authzService.addUserDetails(userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } return res.status(HttpStatus.CREATED).json(finalResponse); + } /** diff --git a/apps/api-gateway/src/dtos/fido-user.dto.ts b/apps/api-gateway/src/dtos/fido-user.dto.ts index a0864cc04..54635b27f 100644 --- a/apps/api-gateway/src/dtos/fido-user.dto.ts +++ b/apps/api-gateway/src/dtos/fido-user.dto.ts @@ -1,11 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString, ValidateNested } from 'class-validator'; export class GenerateRegistrationDto { - @ApiProperty({ example: 'abc@vomoto.com' }) - @IsNotEmpty({ message: 'Email is required.' }) - @IsEmail() - userName: string; @IsOptional() @ApiProperty({ example: 'false' }) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index 88b4d0fdd..7768e98e9 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -1,5 +1,5 @@ -import { Body, Controller, Delete, Get, Logger, Param, Post, Put, Query, Request, Res, UseFilters } from '@nestjs/common'; -import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Param, Post, Put, Query, Request, Res, UseFilters } from '@nestjs/common'; +import { ApiBadRequestResponse, ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { BadRequestErrorDto } from '../dtos/bad-request-error.dto'; import { GenerateAuthenticationDto, GenerateRegistrationDto, UpdateFidoUserDetailsDto, VerifyRegistrationDto, VerifyAuthenticationDto } from '../dtos/fido-user.dto'; @@ -8,7 +8,6 @@ import { InternalServerErrorDto } from '../dtos/internal-server-error-res.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { FidoService } from './fido.service'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { HttpStatus } from '@nestjs/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { Roles } from '../authz/decorators/roles.decorator'; @@ -16,7 +15,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@Controller('fido') +@Controller('auth') @ApiTags('fido') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -24,48 +23,80 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler export class FidoController { private logger = new Logger('FidoController'); constructor(private readonly fidoService: FidoService) { } + /** * - * @param GenerateRegistrationDto + * @param userName * @param res - * @returns Generate registration response + * @returns User get success */ - @Post('/generate-registration-options') + @Get('/passkey/:email') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() @ApiResponse({ status: 500, description: 'Internal server error', type: InternalServerErrorDto }) - @ApiOperation({ summary: 'Generate registration option' }) + + @ApiOperation({ summary: 'Fetch fido user details' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async generateRegistrationOption(@Body() body: GenerateRegistrationDto, @Res() res: Response): Promise { + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) + async fetchFidoUserDetails(@Request() req, @Param('email') email: string, @Res() res: Response): Promise { try { - const { userName, deviceFlag } = body; - const registrationOption = await this.fidoService.generateRegistrationOption(userName, deviceFlag); - + const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.RegistrationOption, - data: registrationOption.response + message: ResponseMessages.user.success.fetchUsers, + data: fidoUserDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); + } catch (error) { this.logger.error(`Error::${error}`); throw error; } } - + /** + * + * @param GenerateRegistrationDto + * @param res + * @returns Generate registration response + */ + @Post('/passkey/generate-registration/:email') + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Generate registration option' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + async generateRegistrationOption(@Body() body: GenerateRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + try { + const { deviceFlag } = body; + const registrationOption = await this.fidoService.generateRegistrationOption(deviceFlag, email); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.fido.success.RegistrationOption, + data: registrationOption.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } catch (error) { + this.logger.error(`Error::${error}`); + } + } + + + /** * * @param VerifyRegistrationDto * @param res * @returns User create success */ - @Post('/verify-registration/:userName') - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Post('/passkey/verify-registration/:email') + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Verify registration' }) - async verifyRegistration(@Request() req, @Body() verifyRegistrationDto: VerifyRegistrationDto, @Param('userName') userName: string, @Res() res: Response): Promise { - const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, req.params.userName); + async verifyRegistration(@Request() req, @Body() verifyRegistrationDto: VerifyRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.fido.success.verifyRegistration, @@ -74,32 +105,13 @@ export class FidoController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param updateFidoUserDetailsDto - * @param res - * @returns User update success - */ - @Put('/user-update') - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Update fido user details' }) - async updateFidoUser(@Request() req, @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, @Res() res: Response): Promise { - const verifyRegistration = await this.fidoService.updateFidoUser(updateFidoUserDetailsDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateUserDetails, - data: verifyRegistration.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - /** * * @param GenerateAuthenticationDto * @param res * @returns Generate authentication object */ - @Post('/generate-authentication-options') + @Post('/passkey/authentication-options') @ApiOperation({ summary: 'Generate authentication option' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async generateAuthenticationOption(@Body() body: GenerateAuthenticationDto, @Request() req, @Res() res: Response): Promise { @@ -119,11 +131,10 @@ export class FidoController { * @param res * @returns Verify authentication object */ - @Post('/verify-authentication/:userName') + @Post('/passkey/verify-authentication/:email') @ApiOperation({ summary: 'Verify authentication' }) - async verifyAuthentication(@Request() req, @Body() verifyAuthenticationDto: VerifyAuthenticationDto, @Param('userName') userName: string, @Res() res: Response): Promise { - const verifyAuthentication = await this.fidoService.verifyAuthentication(verifyAuthenticationDto, req.params.userName); - + async verifyAuthentication(@Request() req, @Body() verifyAuthenticationDto: VerifyAuthenticationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyAuthentication = await this.fidoService.verifyAuthentication(verifyAuthenticationDto, req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.fido.success.login, @@ -132,13 +143,28 @@ export class FidoController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param userName - * @param res - * @returns User get success - */ - @Get('/user-details/:userName') +/** + * + * @param updateFidoUserDetailsDto + * @param res + * @returns User update success + */ + @Put('/passkey/user-details/:credentialId') + @ApiExcludeEndpoint() + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Update fido user details' }) + async updateFidoUser(@Request() req, @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, @Param('credentialId') credentialId: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.updateFidoUser(updateFidoUserDetailsDto, decodeURIComponent(credentialId)); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.updateUserDetails, + data: verifyRegistration.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + + @Put('/passkey/:credentialId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @ApiResponse({ @@ -146,29 +172,30 @@ export class FidoController { description: 'Internal server error', type: InternalServerErrorDto }) - - @ApiOperation({ summary: 'Fetch fido user details' }) + // @ApiQuery( + // { name: 'credentialId', required: true } + // ) + @ApiQuery( + { name: 'deviceName', required: true } + ) + @ApiOperation({ summary: 'Update fido user device name' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) - async fetchFidoUserDetails(@Request() req, @Param('userName') userName: string, @Res() res: Response): Promise { + async updateFidoUserDeviceName(@Param('credentialId') credentialId: string, @Query('deviceName') deviceName: string, @Res() res: Response): Promise { try { - const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.userName); + const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: fidoUserDetails.response + message: ResponseMessages.fido.success.updateDeviceName, + data: updateDeviceName.response }; return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { this.logger.error(`Error::${error}`); throw error; } } - @Delete('/device') + @Delete('/passkey/:credentialId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @ApiResponse({ @@ -181,7 +208,7 @@ export class FidoController { ) @ApiOperation({ summary: 'Delete fido user device' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async deleteFidoUserDevice(@Query('credentialId') credentialId: string, @Res() res: Response): Promise { + async deleteFidoUserDevice(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { try { const deleteFidoUser = await this.fidoService.deleteFidoUserDevice(credentialId); const finalResponse: IResponseType = { @@ -197,35 +224,4 @@ export class FidoController { } } - @Put('/device-name') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @ApiResponse({ - status: 500, - description: 'Internal server error', - type: InternalServerErrorDto - }) - @ApiQuery( - { name: 'credentialId', required: true } - ) - @ApiQuery( - { name: 'deviceName', required: true } - ) - @ApiOperation({ summary: 'Update fido user device name' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async updateFidoUserDeviceName(@Query('credentialId') credentialId: string, @Query('deviceName') deviceName: string, @Res() res: Response): Promise { - try { - const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateDeviceName, - data: updateDeviceName.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; - } - } - } diff --git a/apps/api-gateway/src/fido/fido.service.ts b/apps/api-gateway/src/fido/fido.service.ts index cb9e87ad0..f98659706 100644 --- a/apps/api-gateway/src/fido/fido.service.ts +++ b/apps/api-gateway/src/fido/fido.service.ts @@ -11,18 +11,18 @@ export class FidoService extends BaseService { ) { super('FidoService'); } - async generateRegistrationOption(userName: string, deviceFlag: boolean): Promise<{response: object}> { + async generateRegistrationOption(deviceFlag: boolean, email:string): Promise<{response: object}> { try { - const payload = { userName, deviceFlag }; - return this.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); + const payload = { deviceFlag, email }; + return await this.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); } catch (error) { throw new RpcException(error.response); } } - async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, userName: string): Promise<{response: object}> { - const payload = { verifyRegistrationDetails: verifyRegistrationDto, userName }; + async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, email: string): Promise<{response: object}> { + const payload = { verifyRegistrationDetails: verifyRegistrationDto, email }; return this.sendNats(this.fidoServiceProxy, 'verify-registration', payload); } @@ -31,18 +31,18 @@ export class FidoService extends BaseService { return this.sendNats(this.fidoServiceProxy, 'generate-authentication-options', payload); } - async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationDto, userName: string): Promise<{response: object}> { - const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, userName }; + async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationDto, email: string): Promise<{response: object}> { + const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, email }; return this.sendNats(this.fidoServiceProxy, 'verify-authentication', payload); } - async updateFidoUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto) : Promise<{response: object}> { - const payload = updateFidoUserDetailsDto; + async updateFidoUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, credentialId: string) : Promise<{response: object}> { + const payload = {updateFidoUserDetailsDto, credentialId}; return this.sendNats(this.fidoServiceProxy, 'update-user', payload); } - async fetchFidoUserDetails(userName: string): Promise<{response: string}> { - const payload = { userName }; + async fetchFidoUserDetails(email: string): Promise<{response: string}> { + const payload = { email }; return this.sendNats(this.fidoServiceProxy, 'fetch-fido-user-details', payload); } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 6913aa0e3..5b45fc0a2 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -38,253 +38,254 @@ export class UserDevicesRepository { } } -/** - * - * @param createFidoMultiDevice - * @returns Device details - */ -// eslint-disable-next-line camelcase -async createMultiDevice(newDevice:Prisma.JsonValue, userId:number): Promise { - try { - - const saveResponse = await this.prisma.user_devices.create({ - data: { - devices: newDevice, - userId - } - }); + /** + * + * @param createFidoMultiDevice + * @returns Device details + */ + // eslint-disable-next-line camelcase + async createMultiDevice(newDevice: Prisma.JsonValue, userId: number): Promise { + try { + + const saveResponse = await this.prisma.user_devices.create({ + data: { + devices: newDevice, + userId + } + }); - return saveResponse; + return saveResponse; - } catch (error) { - this.logger.error(`In Create User Repository: ${JSON.stringify(error)}`); - throw error; + } catch (error) { + this.logger.error(`In Create User Repository: ${JSON.stringify(error)}`); + throw error; + } } - } -/** - * - * @param userId - * @returns Device details - */ - // eslint-disable-next-line camelcase - async fidoMultiDevice(userId: number): Promise { - try { - const userDetails = await this.prisma.user_devices.findMany({ - where: { - userId, - deletedAt: null - }, - orderBy: { - createDateTime: 'desc' - } - }); + /** + * + * @param userId + * @returns Device details + */ + // eslint-disable-next-line camelcase + async fidoMultiDevice(userId: number): Promise { + try { + const userDetails = await this.prisma.user_devices.findMany({ + where: { + userId, + deletedAt: null + }, + orderBy: { + createDateTime: 'desc' + } + }); - return userDetails; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + return userDetails; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param userId - * @returns Get all device details - */ -// eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any -async getfidoMultiDevice(userId: number): Promise { - try { - - const fidoMultiDevice = await this.prisma.user_devices.findMany({ - where: { - userId - } - }); - return fidoMultiDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param userId + * @returns Get all device details + */ + // eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any + async getfidoMultiDevice(userId: number): Promise { + try { + + const fidoMultiDevice = await this.prisma.user_devices.findMany({ + where: { + userId + } + }); + return fidoMultiDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param userId - * @returns Get all active device details - */ -async getfidoMultiDeviceDetails(userId: number): Promise { - try { - const fidoMultiDevice = await this.prisma.user_devices.findMany({ - where: { - userId, - deletedAt: null - }, - select: { - id: true, - createDateTime: true, - createdBy: true, - lastChangedDateTime: true, - lastChangedBy: true, - devices: true, - credentialId: true, - deviceFriendlyName: true - } - }); - return fidoMultiDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param userId + * @returns Get all active device details + */ + async getfidoMultiDeviceDetails(userId: number): Promise { + try { + const fidoMultiDevice = await this.prisma.user_devices.findMany({ + where: { + userId, + deletedAt: null + }, + select: { + id: true, + createDateTime: true, + createdBy: true, + lastChangedDateTime: true, + lastChangedBy: true, + devices: true, + credentialId: true, + deviceFriendlyName: true + } + }); + return fidoMultiDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @returns Find device details from credentialID - */ -async getFidoUserDeviceDetails(credentialId: string): Promise { - this.logger.log(`credentialId: ${credentialId}`); - try { - const getUserDevice = await this.prisma.$queryRaw` + /** + * + * @param credentialId + * @returns Find device details from credentialID + */ + async getFidoUserDeviceDetails(credentialId: string): Promise { + this.logger.log(`credentialId: ${credentialId}`); + try { + const getUserDevice = await this.prisma.$queryRaw` SELECT * FROM user_devices WHERE credentialId LIKE '%${credentialId}%' LIMIT 1; `; - return getUserDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + return getUserDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @param loginCounter - * @returns Update Auth counter - */ -async updateFidoAuthCounter(credentialId: string, loginCounter:number): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - credentialId - }, - data: { - authCounter: loginCounter - } - }); - - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param credentialId + * @param loginCounter + * @returns Update Auth counter + */ + async updateFidoAuthCounter(credentialId: string, loginCounter: number): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + credentialId + }, + data: { + authCounter: loginCounter + } + }); + + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @returns Device detail for specific credentialId - */ -// eslint-disable-next-line camelcase -async checkUserDeviceByCredentialId(credentialId: string): Promise { - this.logger.log(`checkUserDeviceByCredentialId: ${credentialId}`); - try { - return this.prisma.user_devices.findFirst({ - where: { - credentialId - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param credentialId + * @returns Device detail for specific credentialId + */ + // eslint-disable-next-line camelcase + async checkUserDeviceByCredentialId(credentialId: string): Promise { + this.logger.log(`checkUserDeviceByCredentialId: ${credentialId}`); + try { + return await this.prisma.user_devices.findFirst({ + where: { + credentialId + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param credentialId - * @returns Delete device - */ -// eslint-disable-next-line camelcase -async deleteUserDeviceByCredentialId(credentialId: string): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - credentialId - }, - data: { - deletedAt: new Date() - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param credentialId + * @returns Delete device + */ + // eslint-disable-next-line camelcase + async deleteUserDeviceByCredentialId(credentialId: string): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + credentialId + }, + data: { + deletedAt: new Date() + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param id - * @param deviceName - * @returns Update device name - */ -async updateUserDeviceByCredentialId(id: number, deviceName:string): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - id - }, - data: { - deviceFriendlyName: deviceName - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param id + * @param deviceName + * @returns Update device name + */ + async updateUserDeviceByCredentialId(id: number, deviceName: string): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + id + }, + data: { + deviceFriendlyName: deviceName + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param credentialId - * @param deviceFriendlyName - * @returns Get device details name for specific credentialId - */ -async updateDeviceByCredentialId(credentialId:string): Promise { - try { - return await this.prisma.$queryRaw` + /** + * + * @param credentialId + * @param deviceFriendlyName + * @returns Get device details name for specific credentialId + */ + async updateDeviceByCredentialId(credentialId: string): Promise { + try { + return await this.prisma.$queryRaw` SELECT * FROM user_devices WHERE devices->>'credentialID' = ${credentialId} `; - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param id - * @param credentialId - * @param deviceFriendlyName - * @returns Update device name for specific credentialId - */ -// eslint-disable-next-line camelcase -async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyName:string): Promise { - try { - return await this.prisma.user_devices.update({ - where: { - id - }, - data: { - credentialId, - deviceFriendlyName - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param id + * @param credentialId + * @param deviceFriendlyName + * @returns Update device name for specific credentialId + */ + // eslint-disable-next-line camelcase + async addCredentialIdAndNameById(id: number, updateFidoUserDetails: string): Promise { + + try { + return await this.prisma.user_devices.update({ + where: { + id + }, + data: { + credentialId: JSON.parse(updateFidoUserDetails).credentialId, + deviceFriendlyName: JSON.parse(updateFidoUserDetails).updateFidoUserDetailsDto.deviceFriendlyName + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} } diff --git a/apps/user/src/fido/dtos/fido-user.dto.ts b/apps/user/src/fido/dtos/fido-user.dto.ts index fbf546b67..2353717ab 100644 --- a/apps/user/src/fido/dtos/fido-user.dto.ts +++ b/apps/user/src/fido/dtos/fido-user.dto.ts @@ -1,10 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString, ValidateNested } from 'class-validator'; export class GenerateRegistrationDto { - @ApiProperty({ example: 'abc@vomoto.com' }) - @IsNotEmpty({ message: 'Email is required.' }) - @IsEmail() - userName: string; + email: string; @IsOptional() @ApiProperty({ example: 'false' }) @@ -66,7 +63,7 @@ class ResponseDto { @ApiProperty() @IsString() - userName: string; + email: string; } // @@ -119,10 +116,7 @@ class VerifyAuthenticationResponseDto { export class VerifyAuthenticationPayloadDto { @ApiProperty() verifyAuthenticationDetails: VerifyAuthenticationDto; - - @ApiProperty() - @IsString() - userName: string; + email: string; } export class UpdateFidoUserDetailsDto { @@ -142,7 +136,7 @@ class VerifyAuthenticationResponseDto { export class UserNameDto { @ApiProperty() @IsString() - userName: string; + email: string; } export class credentialDto { diff --git a/apps/user/src/fido/fido.controller.ts b/apps/user/src/fido/fido.controller.ts index 6d3f9f4dd..9d43dbf0d 100644 --- a/apps/user/src/fido/fido.controller.ts +++ b/apps/user/src/fido/fido.controller.ts @@ -34,7 +34,7 @@ export class FidoController { */ @MessagePattern({ cmd: 'generate-authentication-options' }) generateAuthenticationOption(payload: GenerateRegistrationDto): Promise { - return this.fidoService.generateAuthenticationOption(payload.userName); + return this.fidoService.generateAuthenticationOption(payload.email); } /** * Description: FIDO User Verification @@ -61,7 +61,7 @@ export class FidoController { */ @MessagePattern({ cmd: 'fetch-fido-user-details' }) fetchFidoUserDetails(payload: UserNameDto):Promise { - return this.fidoService.fetchFidoUserDetails(payload.userName); + return this.fidoService.fetchFidoUserDetails(payload.email); } /** diff --git a/apps/user/src/fido/fido.service.ts b/apps/user/src/fido/fido.service.ts index 5095826b0..203f7d404 100644 --- a/apps/user/src/fido/fido.service.ts +++ b/apps/user/src/fido/fido.service.ts @@ -18,17 +18,17 @@ export class FidoService { ) { } async generateRegistration(payload: GenerateRegistrationDto): Promise { try { - const { userName, deviceFlag } = payload; - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const { email, deviceFlag } = payload; + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (!fidoUser && !fidoUser.id) { throw new NotFoundException(ResponseMessages.user.error.notFound); } if (!fidoUser || true === deviceFlag || false === deviceFlag) { - const generatedOption = await this.generateRegistrationOption(userName); + const generatedOption = await this.generateRegistrationOption(email); return generatedOption; } else if (!fidoUser.isFidoVerified) { - const generatedOption = await this.updateUserRegistrationOption(userName); + const generatedOption = await this.updateUserRegistrationOption(email); return generatedOption; } else { throw new BadRequestException(ResponseMessages.fido.error.exists); @@ -39,13 +39,13 @@ export class FidoService { } } - generateRegistrationOption(userName: string): Promise { - const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${userName}`; + generateRegistrationOption(email: string): Promise { + const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${email}`; return this.commonService .httpGet(url, { headers: { 'Content-Type': 'application/json' } }) .then(async (response) => { const { user } = response; - const updateUser = await this.fidoUserRepository.updateUserDetails(userName, [ + const updateUser = await this.fidoUserRepository.updateUserDetails(email, [ {fidoUserId:user.id}, {username:user.name} ]); @@ -57,14 +57,14 @@ export class FidoService { }); } - updateUserRegistrationOption(userName: string): Promise { - const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${userName}`; + updateUserRegistrationOption(email: string): Promise { + const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${email}`; return this.commonService .httpGet(url, { headers: { 'Content-Type': 'application/json' } }) .then(async (response) => { const { user } = response; this.logger.debug(`registration option:: already${JSON.stringify(response)}`); - await this.fidoUserRepository.updateUserDetails(userName, [ + await this.fidoUserRepository.updateUserDetails(email, [ {fidoUserId:user.id}, {isFidoVerified:false} ]); @@ -74,17 +74,17 @@ export class FidoService { async verifyRegistration(verifyRegistrationDto: VerifyRegistrationPayloadDto): Promise { try { - const { verifyRegistrationDetails, userName } = verifyRegistrationDto; + const { verifyRegistrationDetails, email } = verifyRegistrationDto; const url = `${process.env.FIDO_API_ENDPOINT}/verify-registration`; const payload = JSON.stringify(verifyRegistrationDetails); const response = await this.commonService.httpPost(url, payload, { headers: { 'Content-Type': 'application/json' } }); - if (response?.verified && userName) { - await this.fidoUserRepository.updateUserDetails(userName, [{isFidoVerified:true}]); + if (response?.verified && email) { + await this.fidoUserRepository.updateUserDetails(email, [{isFidoVerified:true}]); const credentialID = response.newDevice.credentialID.replace(/=*$/, ''); response.newDevice.credentialID = credentialID; - const getUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const getUser = await this.fidoUserRepository.checkFidoUserExist(email); await this.userDevicesRepository.createMultiDevice(response?.newDevice, getUser.id); return response; } else { @@ -96,9 +96,9 @@ export class FidoService { } } - async generateAuthenticationOption(userName: string): Promise { + async generateAuthenticationOption(email: string): Promise { try { - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (fidoUser && fidoUser.id) { const fidoMultiDevice = await this.userDevicesRepository.getfidoMultiDevice(fidoUser.id); const credentialIds = []; @@ -124,8 +124,8 @@ export class FidoService { async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationPayloadDto): Promise { try { - const { verifyAuthenticationDetails, userName } = verifyAuthenticationDto; - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const { verifyAuthenticationDetails, email } = verifyAuthenticationDto; + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); const fidoMultiDevice = await this.userDevicesRepository.getfidoMultiDeviceDetails(fidoUser.id); const url = `${process.env.FIDO_API_ENDPOINT}/verify-authentication`; const payload = { verifyAuthenticationDetails: JSON.stringify(verifyAuthenticationDetails), devices: fidoMultiDevice }; @@ -164,11 +164,13 @@ export class FidoService { async updateUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto): Promise { try { - const { deviceFriendlyName, credentialId } = updateFidoUserDetailsDto; - const updateFidoUser = await this.userDevicesRepository.updateDeviceByCredentialId(credentialId); - if (updateFidoUser[0].id) { - await this.userDevicesRepository.addCredentialIdAndNameById(updateFidoUser[0].id, credentialId, deviceFriendlyName); + const updateFidoUserDetails = JSON.stringify(updateFidoUserDetailsDto); + const updateFidoUser = await this.userDevicesRepository.updateDeviceByCredentialId(updateFidoUserDetailsDto.credentialId); + + if (updateFidoUser[0].id) { + await this.userDevicesRepository.addCredentialIdAndNameById(updateFidoUser[0].id, updateFidoUserDetails); + } if (updateFidoUser[0].id) { return 'User updated.'; @@ -182,9 +184,9 @@ export class FidoService { } } - async fetchFidoUserDetails(userName: string): Promise { + async fetchFidoUserDetails(email: string): Promise { try { - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (!fidoUser) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -219,7 +221,6 @@ export class FidoService { async updateFidoUserDeviceName(payload: updateDeviceDto): Promise { try { const { credentialId, deviceName } = payload; - const getUserDevice = await this.userDevicesRepository.checkUserDeviceByCredentialId(credentialId); const updateUserDevice = await this.userDevicesRepository.updateUserDeviceByCredentialId(getUserDevice.id, deviceName); if (1 === updateUserDevice.count) { diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 3c82595c7..b85d8df94 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -186,6 +186,7 @@ export class UserService { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); } const checkUserDetails = await this.userRepository.getUserDetails(userInfo.email); + if (!checkUserDetails) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } @@ -200,7 +201,6 @@ export class UserService { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } const userDetails = await this.userRepository.getUserDetails(userInfo.email); - if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } @@ -282,10 +282,8 @@ export class UserService { */ async login(loginUserDto: LoginUserDto): Promise { const { email, password, isPasskey } = loginUserDto; - try { const userData = await this.userRepository.checkUserExist(email); - if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -321,7 +319,6 @@ export class UserService { email, password }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); if (error) { @@ -329,7 +326,6 @@ export class UserService { } const token = data?.session; - return token; } catch (error) { throw new RpcException(error.response ? error.response : error); @@ -396,7 +392,7 @@ export class UserService { async findSupabaseUser(payload: { id }): Promise { try { - return this.userRepository.getUserBySupabaseId(payload.id); + return await this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`Error in findSupabaseUser: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -405,7 +401,7 @@ export class UserService { async findUserByEmail(payload: { email }): Promise { try { - return this.userRepository.findUserByEmail(payload.email); + return await this.userRepository.findUserByEmail(payload.email); } catch (error) { this.logger.error(`findUserByEmail: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); From 94505e5802b9d8e85bdc8e175f3391b9e0844085 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:31:51 +0530 Subject: [PATCH 006/117] fix: Added validation for schema and cred-def and updated payload for create request cred-def (#149) * fix: Added validation for the schema and cred-def request creation Signed-off-by: KulkarniShashank * Remove commented code Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../interfaces/ecosystem.interfaces.ts | 3 + apps/ecosystem/src/ecosystem.repository.ts | 114 ++++++++++-------- apps/ecosystem/src/ecosystem.service.ts | 61 ++++++---- libs/common/src/response-messages/index.ts | 2 + 4 files changed, 111 insertions(+), 69 deletions(-) diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 50040f120..0fc061f0a 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -66,6 +66,7 @@ export interface CredDefMessage { schemaId: string; schema: Record; credentialDefinitionRequest: string; + credentialDefinition: Record; }; registrationMetadata: Record; schemaMetadata: Record; @@ -116,6 +117,8 @@ interface CredentialDefinitionPayload { tag: string; issuerId: string; schemaId: string; + type: string; + value: Record; } export interface submitTransactionPayload { diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 19ba3355a..5269530d1 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -109,19 +109,19 @@ export class EcosystemRepository { const ecosystemDetails = await this.prisma.ecosystem.findMany({ where: { ecosystemOrgs: { - some: { - orgId - } + some: { + orgId + } } }, - include:{ + include: { ecosystemOrgs: { - where: { - orgId - }, - include:{ - ecosystemRole: true - } + where: { + orgId + }, + include: { + ecosystemRole: true + } } } }); @@ -166,11 +166,11 @@ export class EcosystemRepository { } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); - } + } } - async getEcosystemMembersCount (ecosystemId: string): Promise { + async getEcosystemMembersCount(ecosystemId: string): Promise { try { const membersCount = await this.prisma.ecosystem_orgs.count( { @@ -179,24 +179,24 @@ export class EcosystemRepository { } } ); - return membersCount; + return membersCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); } } - async getEcosystemEndorsementsCount (ecosystemId: string): Promise { + async getEcosystemEndorsementsCount(ecosystemId: string): Promise { try { const endorsementsCount = await this.prisma.endorsement_transaction.count({ where: { ecosystemOrgs: { ecosystemId - + } } }); - return endorsementsCount; + return endorsementsCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -385,7 +385,7 @@ export class EcosystemRepository { }, include: { ecosystem: true, - ecosystemRole:true + ecosystemRole: true }, take: pageSize, skip: (pageNumber - 1) * pageSize, @@ -665,6 +665,26 @@ export class EcosystemRepository { } } + // eslint-disable-next-line camelcase + async findRecordsByNameAndVersion(name: string, version: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'name' = ${name} AND "requestBody"->>'version' = ${version}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem schema: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async findRecordsByCredDefTag(tag: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'tag' = ${tag}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem credential-definition: ${error.message} `); + throw error; + } + } + async updateTransactionDetails( endorsementId: string, schemaTransactionRequest: string @@ -732,7 +752,7 @@ export class EcosystemRepository { throw error; } } - + // eslint-disable-next-line camelcase async saveCredDef(credDefResult: saveCredDef): Promise { try { @@ -771,35 +791,35 @@ export class EcosystemRepository { async updateEndorsementRequestStatus(ecosystemId: string, endorsementId: string): Promise { try { - - const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ - where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } - }); - - if (!endorsementTransaction) { - throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); - } - const { ecosystemOrgId } = endorsementTransaction; - - const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ - where: { id: ecosystemOrgId } - }); - - if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { - const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ - where: { id: endorsementId }, - data: { - status: endorsementTransactionStatus.DECLINED - } - }); - - return updatedEndorsementTransaction; - } else { - throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); - } + + const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ + where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } + }); + + if (!endorsementTransaction) { + throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); + } + const { ecosystemOrgId } = endorsementTransaction; + + const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ + where: { id: ecosystemOrgId } + }); + + if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { + const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status: endorsementTransactionStatus.DECLINED + } + }); + + return updatedEndorsementTransaction; + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); + } } catch (error) { - this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); - throw error; - } + this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); + throw error; } + } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 3d6b2a26e..2a31a045f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -69,8 +69,8 @@ export class EcosystemService { // eslint-disable-next-line camelcase async getAllEcosystem(payload: { orgId: string }): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); - + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); + if (!getAllEcosystemDetails) { throw new NotFoundException(ResponseMessages.ecosystem.error.update); } @@ -82,15 +82,15 @@ export class EcosystemService { * * @returns ecosystem dashboard details */ - async getEcosystemDashboardDetails(ecosystemId: string): Promise { - try { - return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); - } catch (error) { - this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } + async getEcosystemDashboardDetails(ecosystemId: string): Promise { + try { + return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + } catch (error) { + this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); } - + } + /** * Description: get an ecosystem invitation * @returns Get sent ecosystem invitation details @@ -157,7 +157,7 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - + const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); @@ -286,6 +286,11 @@ export class EcosystemService { */ async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { + const schemaRequestExist = await this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version); + + if (0 !== schemaRequestExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); + } const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { @@ -340,6 +345,13 @@ export class EcosystemService { async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { + + const credDefRequestExist = await this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag); + + if (0 !== credDefRequestExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.credDefAlreadyExist); + } + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); @@ -383,7 +395,8 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestCredDefPayload, endorsementTransactionType.CREDENTIAL_DEFINITION); + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); @@ -562,7 +575,9 @@ export class EcosystemService { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'] + schemaId: endorsementTransactionPayload.requestBody['schemaId'], + type: endorsementTransactionPayload.requestBody['type'], + value: endorsementTransactionPayload.requestBody['value'] }; } @@ -602,6 +617,7 @@ export class EcosystemService { } return saveSchemaDetails; } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); const saveCredentialDefinition: saveCredDef = { schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], @@ -612,6 +628,7 @@ export class EcosystemService { orgId: ecosystemMemberDetails.orgId, schemaId: schemaDetails.id }; + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); if (!saveCredDefDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); @@ -752,12 +769,12 @@ export class EcosystemService { ] }; - const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); + const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); if (ecosystemOrgData['ecosystemRole']['name'] !== EcosystemRoles.ECOSYSTEM_LEAD) { query.ecosystemOrgs['orgId'] = orgId; } - + if (type) { query['type'] = type; } @@ -770,13 +787,13 @@ export class EcosystemService { } - /** - * - * @param ecosystemId - * @param endorsementId - * @param orgId - * @returns EndorsementTransactionRequest Status message - */ + /** + * + * @param ecosystemId + * @param endorsementId + * @param orgId + * @returns EndorsementTransactionRequest Status message + */ async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string): Promise { try { diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 18564fbaa..001d51bdf 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -214,6 +214,8 @@ export const ResponseMessages = { requestCredDefTransaction: 'Error while submitting transaction', notFound: 'Organization not found', leadNotFound: 'Lead details not found', + schemaAlreadyExist: 'Schema name and schema version already exist', + credDefAlreadyExist: 'Credential definition already exist', saveSchema: 'Error while storing the schema details', saveCredDef: 'Error while storing the credential-definition details', invalidOrgId: 'Invalid organization Id', From 6dd1927ab4e20a0f46d24f782848f72cd9c16748 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Fri, 13 Oct 2023 16:00:14 +0530 Subject: [PATCH 007/117] fix: removed unused orgId Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index c207ffbb1..524d499c0 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -206,9 +206,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'decline-endorsement-transaction' }) async declineEndorsementRequestByLead(payload: { - ecosystemId:string, endorsementId:string, orgId:string + ecosystemId:string, endorsementId:string }): Promise { - return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.orgId, payload.endorsementId); + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } From 8dd6678f1fe3ba1a64c918774ba1e0bafcd3d18d Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 16 Oct 2023 10:23:54 +0530 Subject: [PATCH 008/117] resolved sonar lint checks Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- apps/api-gateway/src/user/user.controller.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index e88752f18..b14d9e3c6 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Post, Put, Body, Param, UseFilters } from '@nestjs/common'; +import { Controller, Post, Put, Body, Param, UseFilters, Res, HttpStatus, BadRequestException, Get, Query, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { ApiBearerAuth, @@ -13,17 +13,11 @@ import { import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { Res } from '@nestjs/common'; import { Response } from 'express'; -import { HttpStatus } from '@nestjs/common'; import { CommonService } from '@credebl/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; -import { BadRequestException } from '@nestjs/common'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Get } from '@nestjs/common'; -import { Query } from '@nestjs/common'; import { user } from '@prisma/client'; -import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; From 4e60d5684118cbcbb40e5bbc812c551cf7223b2b Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:51:54 +0530 Subject: [PATCH 009/117] feat: Schema and cred-def auto sign and submit endorsement transaction (#153) * Added the auto submit flag for automatic submit the transaction Signed-off-by: KulkarniShashank * Removed unnecessary params Signed-off-by: KulkarniShashank * Error handling in the schema endorsement Signed-off-by: KulkarniShashank * feat: Schema and credDef auto sign and submit transaction Signed-off-by: KulkarniShashank * Solved issue when schema sign Signed-off-by: KulkarniShashank * Added the validation for the cedDef create and write Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.repository.ts | 54 ++- apps/ecosystem/src/ecosystem.service.ts | 389 +++++++++++++-------- libs/common/src/common.constant.ts | 2 + libs/common/src/response-messages/index.ts | 8 + libs/prisma-service/prisma/schema.prisma | 22 +- 5 files changed, 308 insertions(+), 167 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 5269530d1..6ffa674e9 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { credential_definition, ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; +import { credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; import { DeploymentModeType, EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; @@ -146,7 +146,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -165,7 +165,7 @@ export class EcosystemRepository { }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -182,7 +182,7 @@ export class EcosystemRepository { return membersCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -199,7 +199,7 @@ export class EcosystemRepository { return endorsementsCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -223,7 +223,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -246,7 +246,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -268,7 +268,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException('Unable to update ecosystem invitation'); + throw error; } } @@ -304,7 +304,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException('Unable to update ecosystem orgs'); + throw error; } } @@ -333,7 +333,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -350,7 +350,7 @@ export class EcosystemRepository { return await this.getEcosystemInvitationsPagination(query, pageNumber, pageSize); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -409,7 +409,7 @@ export class EcosystemRepository { return { totalPages, members }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -446,7 +446,7 @@ export class EcosystemRepository { return { totalPages, invitations }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -508,7 +508,7 @@ export class EcosystemRepository { return { totalPages, transactions }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -578,7 +578,27 @@ export class EcosystemRepository { } catch (error) { this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; + } + } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getEcosystemConfigDetails(key: string): Promise { + try { + + return this.prisma.ecosystem_config.findFirst({ + where: { + key + } + }); + + } catch (error) { + this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); + throw error; } } @@ -603,7 +623,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -619,7 +639,7 @@ export class EcosystemRepository { return deletedInvitation; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2a31a045f..4d7121ed3 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,11 +13,11 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CredDefMessage, CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; -// eslint-disable-next-line camelcase -import { platform_config } from '@prisma/client'; import { CommonConstants } from '@credebl/common/common.constant'; +// eslint-disable-next-line camelcase +import { credential_definition, org_agents, platform_config, schema } from '@prisma/client'; @Injectable() @@ -284,37 +284,47 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { + async requestSchemaEndorsement(requestSchemaPayload, orgId, ecosystemId): Promise { try { - const schemaRequestExist = await this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [schemaRequestExist, ecosystemMemberDetails, platformConfig, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = await Promise.all([ + this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getPlatformConfigDetails(), + this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)) + ]); if (0 !== schemaRequestExist.length) { throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); + throw new NotFoundException(ResponseMessages.ecosystem.error.notFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails?.tenantId); - - const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); - - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } if (!ecosystemLeadAgentDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + throw new NotFoundException(ResponseMessages.ecosystem.error.leadNotFound); + } + + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); } - const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + const url = await this.getAgentUrl(ecosystemMemberDetails.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails.tenantId); - const schemaTransactionPayload: SchemaTransactionPayload = { + const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + + const schemaTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestSchemaPayload.endorse, attributes: attributeArray, @@ -324,7 +334,8 @@ export class EcosystemService { }; const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); - const schemaTransactionResponse: SchemaTransactionResponse = { + + const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, @@ -335,53 +346,73 @@ export class EcosystemService { if ('failed' === schemaTransactionRequest.message.schemaState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); } + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestSchemaPayload, endorsementTransactionType.SCHEMA); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); - - throw new RpcException(error.response ? error.response : error); + throw new RpcException(error.response || error); } } - async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload, orgId, ecosystemId): Promise { try { - const credDefRequestExist = await this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [credDefRequestExist, ecosystemMemberDetails, platformConfig, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = await Promise.all([ + this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getPlatformConfigDetails(), + this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)) + ]); if (0 !== credDefRequestExist.length) { throw new ConflictException(ResponseMessages.ecosystem.error.credDefAlreadyExist); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails?.tenantId); - - // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } if (!ecosystemLeadAgentDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } - const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); + } + + const url = await this.getAgentUrl(ecosystemMemberDetails.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails.tenantId); - const credDefTransactionPayload: CredDefTransactionPayload = { + const credDefTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestCredDefPayload.endorse, tag: requestCredDefPayload.tag, schemaId: requestCredDefPayload.schemaId, issuerId: ecosystemMemberDetails.orgDid }; - // Need to add logic and type for credential-definition + const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + + if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); + } + + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + + if (!requestBody) { + throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); + } + const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -390,20 +421,14 @@ export class EcosystemService { ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - - if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); - } - - const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { - this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); - - throw new RpcException(error.response ? error.response : error); + this.logger.error(`In request cred-def endorsement: ${JSON.stringify(error)}`); + throw new RpcException(error.response || error); } } + async getInvitationsByEcosystemId( payload: FetchInvitationsPayload ): Promise { @@ -454,25 +479,32 @@ export class EcosystemService { async signTransaction(endorsementId: string, ecosystemId: string): Promise { try { - const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); + const [endorsementTransactionPayload, ecosystemLeadDetails, platformConfig] = await Promise.all([ + this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED), + this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId), + this.ecosystemRepository.getPlatformConfigDetails() + ]); + if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - - if (!getEcosystemLeadDetails) { + if (!ecosystemLeadDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - const url = await this.getAgentUrl(ecosystemLeadAgentDetails?.orgAgentTypeId, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(ecosystemLeadDetails.orgId)); if (!ecosystemLeadAgentDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } + + const url = await this.getAgentUrl(ecosystemLeadAgentDetails?.orgAgentTypeId, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); + const jsonString = endorsementTransactionPayload.requestPayload.toString(); const payload = { transaction: jsonString, @@ -480,11 +512,38 @@ export class EcosystemService { }; const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); - return this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + if (!schemaTransactionRequest) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.signRequestError); + } + + const autoEndorsement = `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}`; + const ecosystemConfigDetails = await this.ecosystemRepository.getEcosystemConfigDetails(autoEndorsement); + + if (!ecosystemConfigDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemConfigNotFound); + } + + const updateSignedTransaction = await this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + + if (!updateSignedTransaction) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateTransactionError); + } + + if (updateSignedTransaction && 'true' === ecosystemConfigDetails.value) { + + const submitTxn = await this.submitTransaction(endorsementId, ecosystemId); + if (!submitTxn) { + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + return submitTxn; + } + + return updateSignedTransaction; } catch (error) { - this.logger.error(`In sign transaction : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`In sign transaction: ${JSON.stringify(error)}`); + throw new RpcException(error.response || error); } } @@ -536,113 +595,165 @@ export class EcosystemService { } } - async submitTransaction(endorsementId: string, ecosystemId: string): Promise { + // eslint-disable-next-line camelcase + async getEcosystemMemberDetails(endorsementTransactionPayload): Promise { + const orgId = Number(endorsementTransactionPayload.ecosystemOrgs.orgId); + return this.ecosystemRepository.getAgentDetails(orgId); + } + + // eslint-disable-next-line camelcase + async getEcosystemLeadAgentDetails(ecosystemId): Promise { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + return this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + } + + // eslint-disable-next-line camelcase + async getPlatformConfig(): Promise { + return this.ecosystemRepository.getPlatformConfigDetails(); + } + + async submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails): Promise { + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); + const jsonString = endorsementTransactionPayload.responsePayload.toString(); + const payload: submitTransactionPayload = { + endorsedTransaction: jsonString, + endorserDid: ecosystemLeadAgentDetails.orgDid + }; + + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + payload.schema = { + attributes: parsedRequestPayload.operation.data.attr_names, + version: parsedRequestPayload.operation.data.version, + name: parsedRequestPayload.operation.data.name, + issuerId: ecosystemMemberDetails.orgDid + }; + } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + + payload.credentialDefinition = { + tag: parsedRequestPayload.operation.tag, + issuerId: ecosystemMemberDetails.orgDid, + schemaId: endorsementTransactionPayload.requestBody['schemaId'], + type: endorsementTransactionPayload.requestBody['type'], + value: endorsementTransactionPayload.requestBody['value'] + }; + } + + return payload; + } + + async handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { + const regex = /[^:]+$/; + const match = ecosystemMemberDetails.orgDid.match(regex); + let extractedDidValue; + + if (match) { + // eslint-disable-next-line prefer-destructuring + extractedDidValue = match[0]; + + } + const saveSchemaPayload: SaveSchema = { + name: endorsementTransactionPayload.requestBody['name'], + version: endorsementTransactionPayload.requestBody['version'], + attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), + schemaLedgerId: submitTransactionRequest['message'].schemaId, + issuerId: ecosystemMemberDetails.orgDid, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + publisherDid: extractedDidValue, + orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, + ledgerId: ecosystemMemberDetails.ledgerId + }; + const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); + if (!saveSchemaDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); + } + return saveSchemaDetails; + } + + // eslint-disable-next-line camelcase + async handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); + + if (!schemaDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.schemaNotFound); + } + + const saveCredentialDefinition: saveCredDef = { + schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], + tag: endorsementTransactionPayload.requestBody['tag'], + credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, + revocable: false, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + orgId: ecosystemMemberDetails.orgId, + schemaId: schemaDetails.id + }; + + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); + if (!saveCredDefDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); + } + return saveCredDefDetails; + } + + async updateTransactionStatus(endorsementId): Promise { + return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + } + + async submitTransaction(endorsementId, ecosystemId): Promise { try { - const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } + if ('submitted' === endorsementTransactionPayload.status) { throw new ConflictException(ResponseMessages.ecosystem.error.transactionSubmitted); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); - - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - + const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); + const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); + const platformConfig = await this.getPlatformConfig(); const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); + const payload = await this.submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails); - const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); - const jsonString = endorsementTransactionPayload.responsePayload.toString(); + const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); - const payload: submitTransactionPayload = { - endorsedTransaction: jsonString, - endorserDid: ecosystemLeadAgentDetails.orgDid - }; + if ('failed' === submitTransactionRequest["message"].state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + + await this.updateTransactionStatus(endorsementId); if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { - payload.schema = { - attributes: parsedRequestPayload.operation.data.attr_names, - version: parsedRequestPayload.operation.data.version, - name: parsedRequestPayload.operation.data.name, - issuerId: ecosystemMemberDetails.orgDid - }; + return this.handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - payload.credentialDefinition = { - tag: parsedRequestPayload.operation.tag, - issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'], - type: endorsementTransactionPayload.requestBody['type'], - value: endorsementTransactionPayload.requestBody['value'] - }; - } - const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); + if ('undefined' === submitTransactionRequest["message"].credentialDefinitionId.split(":")[3]) { - if ('failed' === submitTransactionRequest['message'].state) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); - } + const autoEndorsement = `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}`; + const ecosystemConfigDetails = await this.ecosystemRepository.getEcosystemConfigDetails(autoEndorsement); - await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); - if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + if ('true' === ecosystemConfigDetails.value) { - const regex = /[^:]+$/; - const match = ecosystemMemberDetails.orgDid.match(regex); - let extractedDidValue; + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + } else { - if (match) { - // eslint-disable-next-line prefer-destructuring - extractedDidValue = match[0]; + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SIGNED); + } + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } - const saveSchemaPayload: SaveSchema = { - name: endorsementTransactionPayload.requestBody['name'], - version: endorsementTransactionPayload.requestBody['version'], - attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), - schemaLedgerId: submitTransactionRequest['message'].schemaId, - issuerId: ecosystemMemberDetails.orgDid, - createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - publisherDid: extractedDidValue, - orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, - ledgerId: ecosystemMemberDetails.ledgerId - }; - const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); - if (!saveSchemaDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); - } - return saveSchemaDetails; - } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); - const saveCredentialDefinition: saveCredDef = { - schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], - tag: endorsementTransactionPayload.requestBody['tag'], - credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, - revocable: false, - createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - orgId: ecosystemMemberDetails.orgId, - schemaId: schemaDetails.id - }; - - const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); - if (!saveCredDefDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); - } - return saveCredDefDetails; + return this.handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } - - } catch (error) { - this.logger.error(`In sumit transaction : ${JSON.stringify(error)}`); + this.logger.error(`In submit transaction: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } + /** * Description: Store shortening URL * @param signEndorsementPayload @@ -719,7 +830,7 @@ export class EcosystemService { ): Promise { const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); - + if (!isEcosystemEnabled) { throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); } @@ -737,7 +848,7 @@ export class EcosystemService { ): Promise { const ecosystemDetails = await this.prisma.ecosystem_config.findFirst( { - where:{ + where: { key: 'enableEcosystem' } } @@ -795,11 +906,11 @@ export class EcosystemService { * @returns EndorsementTransactionRequest Status message */ - async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string): Promise { + async declineEndorsementRequestByLead(ecosystemId: string, endorsementId: string): Promise { try { return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, endorsementId); - } catch (error) { + } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); throw new InternalServerErrorException(error); } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 1bd48d76d..2e9fe6e88 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -261,6 +261,8 @@ export enum CommonConstants { ONBOARDING_TYPE_EXTERNAL = 1, ONBOARDING_TYPE_INVITATION = 2, + // ecosystem config auto endorsement + ECOSYSTEM_AUTO_ENDOSEMENT = 'autoEndorsement', // Network TESTNET = 'testnet', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 001d51bdf..679e9590c 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -213,7 +213,15 @@ export const ResponseMessages = { requestSchemaTransaction: 'Error while request schema transaction', requestCredDefTransaction: 'Error while submitting transaction', notFound: 'Organization not found', + platformConfigNotFound: 'Platform configurations not found', + schemaNotFound: 'Schema not found', + ecosystemNotFound: 'Ecosystem not found', + ecosystemOrgNotFound: 'Ecosystem org not found', + ecosystemConfigNotFound: 'Ecosystem config not found', + credentialDefinitionNotFound: 'Credential definition found', leadNotFound: 'Lead details not found', + signRequestError: 'Error while signing the transaction', + updateTransactionError: 'Error while update the transaction', schemaAlreadyExist: 'Schema name and schema version already exist', credDefAlreadyExist: 'Credential definition already exist', saveSchema: 'Error while storing the schema details', diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 1104e0fc9..d3d57fe61 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -368,15 +368,15 @@ model ecosystem_orgs { id String @id @default(uuid()) orgId String orgName String? - orgDid String? @db.VarChar + orgDid String? @db.VarChar status String deploymentMode String? ecosystemId String ecosystemRoleId String createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy String @default("1") + createdBy String @default("1") lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy String @default("1") + lastChangedBy String @default("1") deletedAt DateTime? @db.Timestamp(6) ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) @@ -402,12 +402,12 @@ model endorsement_transaction { } model ecosystem_config { - id String @id @default(uuid()) - key String? - value String? - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy String @default("1") - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy String @default("1") - deletedAt DateTime? @db.Timestamp(6) + id String @id @default(uuid()) + key String? + value String? + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy String @default("1") + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy String @default("1") + deletedAt DateTime? @db.Timestamp(6) } From 1d31e6e47869609c65685b29b1dbdea88443f615 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 16 Oct 2023 14:59:04 +0530 Subject: [PATCH 010/117] refactor: api for passkey module Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- apps/api-gateway/src/fido/fido.controller.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index 7768e98e9..ee3f066d8 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -172,9 +172,6 @@ export class FidoController { description: 'Internal server error', type: InternalServerErrorDto }) - // @ApiQuery( - // { name: 'credentialId', required: true } - // ) @ApiQuery( { name: 'deviceName', required: true } ) From 8008c5ebe88c9dcbc09718b06989b2603b7064c6 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 16 Oct 2023 10:55:38 +0530 Subject: [PATCH 011/117] fix: decline endorsement request Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 2ef578338..36fbf9b6a 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -437,11 +437,11 @@ export class EcosystemController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async declineEndorsementRequestByLead( @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, @Param('endorsementId') endorsementId: string, + @Param('orgId') orgId: string, @Res() res: Response ): Promise { - await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, orgId, endorsementId); + await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, endorsementId, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.DeclineEndorsementTransaction From c0a063622fb0ceb402347c02b61b8c3702c52182 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 16 Oct 2023 13:02:49 +0530 Subject: [PATCH 012/117] refactor: Implemented org validation for create and invite ecosystem Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank --- .../src/ecosystem/ecosystem.service.ts | 6 ++-- .../interfaces/ecosystem.interfaces.ts | 18 ++++++++++++ apps/ecosystem/src/ecosystem.repository.ts | 28 +++++++++++++++++-- apps/ecosystem/src/ecosystem.service.ts | 16 ++++++++--- libs/common/src/response-messages/index.ts | 3 +- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 39e4c10e0..2fdd48a47 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -10,6 +10,8 @@ import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { RequestSchemaDto, RequestCredDefDto } from './dtos/request-schema.dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; @Injectable() export class EcosystemService extends BaseService { @@ -22,7 +24,7 @@ export class EcosystemService extends BaseService { * @param createEcosystemDto * @returns Ecosystem creation success */ - async createEcosystem(createEcosystemDto): Promise { + async createEcosystem(createEcosystemDto: CreateEcosystemDto): Promise { const payload = { createEcosystemDto }; return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); } @@ -32,7 +34,7 @@ export class EcosystemService extends BaseService { * @param editEcosystemDto * @returns Ecosystem creation success */ - async editEcosystem(editEcosystemDto, ecosystemId): Promise { + async editEcosystem(editEcosystemDto: EditEcosystemDto, ecosystemId:string): Promise { const payload = { editEcosystemDto, ecosystemId }; return this.sendNats(this.serviceProxy, 'edit-ecosystem', payload); } diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 0fc061f0a..1e7aa379b 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -171,3 +171,21 @@ export interface EndorsementTransactionPayloadDetails { orgId: string; }; } + +export interface CreateEcosystem { + name: string; + + description?: string; + + tags?: string; + + userId: number; + + logo?: string; + + orgName: string; + + orgDid: string; + + orgId?: string; +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 6ffa674e9..87f1a0dbd 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,4 +1,4 @@ -import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; @@ -18,9 +18,9 @@ export class EcosystemRepository { ) { } /** - * Description: Get getAgentEndPoint by orgId + * Description: create ecosystem * @param createEcosystemDto - * @returns Get getAgentEndPoint details + * @returns ecosystem */ // eslint-disable-next-line camelcase async createNewEcosystem(createEcosystemDto): Promise { @@ -150,6 +150,28 @@ export class EcosystemRepository { } } + /** + * + * @param orgId + * @returns Get specific organization details from ecosystem + */ + // eslint-disable-next-line camelcase + async checkEcosystemOrgs(orgId:string): Promise { + try { + if (!orgId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); + } + return this.prisma.ecosystem_orgs.findFirst({ + where: { + orgId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + /** * * @returns Get ecosystem dashboard card count diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4d7121ed3..730fca401 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,7 +13,7 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @@ -37,10 +37,14 @@ export class EcosystemService { */ // eslint-disable-next-line camelcase - async createEcosystem(createEcosystemDto): Promise { + async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); if (!createEcosystem) { - throw new NotFoundException(ResponseMessages.ecosystem.error.update); + throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); } return createEcosystem; } @@ -157,7 +161,11 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); + + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 679e9590c..52fb01b9d 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -232,7 +232,8 @@ export const ResponseMessages = { transactionSubmitted: 'Transaction already submitted', invalidAgentUrl: 'Invalid agent url', EndorsementTransactionNotFoundException:'Endorsement transaction with status requested not found', - OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg' + OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg', + ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.' } } }; \ No newline at end of file From 49897b629ef5a4b9e01e0997a9aadd7f862a0351 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:21:55 +0530 Subject: [PATCH 013/117] refactor: ecosystem lead details included ecosystem dashboard (#156) * worked on the ecosystem lead details Signed-off-by: Nishad * removed the unnecessary code and comments Signed-off-by: Nishad --------- Signed-off-by: Nishad Signed-off-by: KulkarniShashank --- .../src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.repository.ts | 8 +++--- apps/ecosystem/src/ecosystem.service.ts | 25 ++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 36fbf9b6a..79fb14575 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -104,7 +104,7 @@ export class EcosystemController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 87f1a0dbd..7c4e40a2c 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -177,7 +177,7 @@ export class EcosystemRepository { * @returns Get ecosystem dashboard card count */ - async getEcosystemDashboardDetails(ecosystemId: string): Promise { + async getEcosystemDashboardDetails(ecosystemId: string): Promise<{membersCount: number; endorsementsCount: number}> { try { const membersCount = await this.getEcosystemMembersCount(ecosystemId); const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); @@ -474,7 +474,7 @@ export class EcosystemRepository { async fetchEcosystemOrg( - payload: { ecosystemId: string, orgId: string } + payload: object ): Promise { return this.prisma.ecosystem_orgs.findFirst({ @@ -482,7 +482,9 @@ export class EcosystemRepository { ...payload }, select: { - ecosystemRole: true + ecosystem: true, + ecosystemRole: true, + orgName: true } }); diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 730fca401..5b18da802 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -88,7 +88,30 @@ export class EcosystemService { */ async getEcosystemDashboardDetails(ecosystemId: string): Promise { try { - return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + const endorseMemberCount = await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + + const query = { + ecosystemId, + ecosystemRole: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }; + + const ecosystemDetails = await this.ecosystemRepository.fetchEcosystemOrg( + query + ); + + const dashboardDetails = { + ecosystem: ecosystemDetails['ecosystem'], + membersCount: endorseMemberCount.membersCount, + endorsementsCount: endorseMemberCount.endorsementsCount, + ecosystemLead:{ + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['orgName'] + } + }; + + return dashboardDetails; } catch (error) { this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); From bbea3656612e5edc022e3d0fd47a88b6a3b06a48 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 17 Oct 2023 08:25:29 +0530 Subject: [PATCH 014/117] remove unnecessary code Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- apps/user/src/fido/dtos/fido-user.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user/src/fido/dtos/fido-user.dto.ts b/apps/user/src/fido/dtos/fido-user.dto.ts index 2353717ab..9336e4a9c 100644 --- a/apps/user/src/fido/dtos/fido-user.dto.ts +++ b/apps/user/src/fido/dtos/fido-user.dto.ts @@ -66,7 +66,7 @@ class ResponseDto { email: string; } -// + class VerifyAuthenticationResponseDto { @ApiProperty() @IsString() From dd908a8b0e409c67ca1af1f151b2dd0411621263 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 17 Oct 2023 15:28:39 +0530 Subject: [PATCH 015/117] refactor return type Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- apps/user/repositories/user-device.repository.ts | 2 +- apps/user/src/fido/fido.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 5b45fc0a2..f5a78e691 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -93,7 +93,7 @@ export class UserDevicesRepository { * @returns Get all device details */ // eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any - async getfidoMultiDevice(userId: number): Promise { + async getfidoMultiDevice(userId: number): Promise { try { const fidoMultiDevice = await this.prisma.user_devices.findMany({ diff --git a/apps/user/src/fido/fido.service.ts b/apps/user/src/fido/fido.service.ts index 203f7d404..f21a8a164 100644 --- a/apps/user/src/fido/fido.service.ts +++ b/apps/user/src/fido/fido.service.ts @@ -104,7 +104,7 @@ export class FidoService { const credentialIds = []; if (fidoMultiDevice) { for (const iterator of fidoMultiDevice) { - credentialIds.push(iterator.devices.credentialID); + credentialIds.push(iterator.devices['credentialID']); } } else { throw new BadRequestException(ResponseMessages.fido.error.deviceNotFound); From 1c1c5ea46c5ef62da1f6463f687726aec1fbc36f Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:08:36 +0530 Subject: [PATCH 016/117] refactor: Implemented auto flag handling within the ecosystem and introduced validation. (#158) * Added the auto submit flag for automatic submit the transaction Signed-off-by: KulkarniShashank * Removed unnecessary params Signed-off-by: KulkarniShashank * Error handling in the schema endorsement Signed-off-by: KulkarniShashank * feat: Schema and credDef auto sign and submit transaction Signed-off-by: KulkarniShashank * Solved issue when schema sign Signed-off-by: KulkarniShashank * Added the validation for the cedDef create and write Signed-off-by: KulkarniShashank * feat: auto endorsement flag handle and refactor schema and cred-def Signed-off-by: KulkarniShashank * Solved conflicts in endorsement service Signed-off-by: KulkarniShashank * Change the API URL for auto sign and submit flag Signed-off-by: KulkarniShashank * Modify the error messages in ecosystem Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../src/agent-service.service.ts | 13 +- .../src/interface/agent-service.interface.ts | 1 + .../repositories/agent-service.repository.ts | 2 +- .../src/ecosystem/ecosystem.controller.ts | 30 ++- .../src/ecosystem/ecosystem.service.ts | 149 +++++++------- apps/ecosystem/src/ecosystem.controller.ts | 183 +++++++++--------- apps/ecosystem/src/ecosystem.repository.ts | 58 +++++- apps/ecosystem/src/ecosystem.service.ts | 24 ++- libs/common/src/response-messages/index.ts | 9 +- 9 files changed, 289 insertions(+), 180 deletions(-) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 0d3f67403..557960456 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -229,7 +229,7 @@ export class AgentServiceService { socket.emit('agent-spinup-process-initiated', { clientId: agentSpinupDto.clientSocketId }); } - await this._agentSpinup(walletProvisionPayload, agentSpinupDto, orgApiKey, orgData, user, socket); + await this._agentSpinup(walletProvisionPayload, agentSpinupDto, orgApiKey, orgData, user, socket, agentSpinupDto.ledgerId); const agentStatusResponse = { agentSpinupStatus: 1 }; @@ -251,7 +251,7 @@ export class AgentServiceService { } } - async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket): Promise { + async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket, ledgerId: number[]): Promise { try { const agentSpinUpResponse = new Promise(async (resolve, _reject) => { @@ -288,7 +288,8 @@ export class AgentServiceService { agentsTypeId: AgentType.AFJ, orgId: orgData.id, walletName: agentSpinupDto.walletName, - clientSocketId: agentSpinupDto.clientSocketId + clientSocketId: agentSpinupDto.clientSocketId, + ledgerId }; if (agentEndPoint && agentSpinupDto.clientSocketId) { @@ -358,7 +359,8 @@ export class AgentServiceService { orgId: payload.orgId, agentEndPoint: payload.agentEndPoint, agentId: payload.agentId, - orgAgentTypeId: OrgAgentType.DEDICATED + orgAgentTypeId: OrgAgentType.DEDICATED, + ledgerId: payload.ledgerId }; @@ -543,7 +545,8 @@ export class AgentServiceService { agentEndPoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint, orgAgentTypeId: OrgAgentType.SHARED, tenantId: tenantDetails.tenantRecord.id, - walletName: label + walletName: label, + ledgerId: payload.ledgerId }; if (payload.clientSocketId) { diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index d6a7d4b99..3386889bb 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -137,6 +137,7 @@ export interface IStoreOrgAgentDetails { agentId?: number; orgAgentTypeId?: OrgAgentType; tenantId?: string; + ledgerId?: number[]; } diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index 15d333e92..90d050471 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -88,7 +88,7 @@ export class AgentServiceRepository { agentId: storeOrgAgentDetails.agentId ? storeOrgAgentDetails.agentId : null, orgAgentTypeId: storeOrgAgentDetails.orgAgentTypeId ? storeOrgAgentDetails.orgAgentTypeId : null, tenantId: storeOrgAgentDetails.tenantId ? storeOrgAgentDetails.tenantId : null, - ledgerId: 1 + ledgerId: storeOrgAgentDetails.ledgerId[0] } }); } catch (error) { diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 79fb14575..c6e037a0d 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -106,7 +106,6 @@ export class EcosystemController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() - async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { const getEcosystemDetails = await this.ecosystemService.getEcosystemDashboardDetails(ecosystemId, orgId); @@ -244,7 +243,7 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - + /** * * @param createOrgDto @@ -310,7 +309,7 @@ export class EcosystemController { @ApiBearerAuth() @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { + async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -369,6 +368,31 @@ export class EcosystemController { } + /** + * + * @param res + * @returns + */ + @Put('transaction/endorsement/auto') + @ApiOperation({ + summary: 'Auto sign and submit transactions', + description: 'Auto sign and submit transactions' + }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async autoSignAndSubmitTransaction( + @Res() res: Response + ): Promise { + await this.ecosystemService.autoSignAndSubmitTransaction(); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.AutoEndorsementTransaction + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * * @param acceptRejectEcosystemInvitation diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 2fdd48a47..1ad6f07af 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -48,7 +48,7 @@ export class EcosystemService extends BaseService { const payload = { orgId }; return this.sendNats(this.serviceProxy, 'get-all-ecosystem', payload); } - + /** * * @@ -67,8 +67,8 @@ export class EcosystemService extends BaseService { * @returns */ async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string): Promise { - const payload = { bulkInvitationDto, userId }; - return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } async getInvitationsByEcosystemId( @@ -80,7 +80,7 @@ export class EcosystemService extends BaseService { const payload = { ecosystemId, pageNumber, pageSize, search, userId }; return this.sendNats(this.serviceProxy, 'get-sent-invitations-ecosystemId', payload); } - + /** * * @returns Ecosystem members @@ -92,84 +92,89 @@ export class EcosystemService extends BaseService { const { pageNumber, pageSize, search } = getEcosystemMembers; const payload = { ecosystemId, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'fetch-ecosystem-members', payload); - } + } /** * * @returns Ecosystem Invitations details */ - async getEcosystemInvitations( - getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, - userEmail: string, - status: string - ): Promise<{ response: object }> { - const { pageNumber, pageSize, search } = getAllInvitationsDto; - const payload = { userEmail, status, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); - } + async getEcosystemInvitations( + getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, + userEmail: string, + status: string + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllInvitationsDto; + const payload = { userEmail, status, pageNumber, pageSize, search }; + return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); + } async deleteEcosystemInvitations( invitationId: string - ): Promise { + ): Promise { const payload = { invitationId }; return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); } - async acceptRejectEcosystemInvitaion( - acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, - userEmail: string - ): Promise<{ response: string }> { - const payload = { acceptRejectInvitation, userEmail }; - return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); - } - - - async fetchEcosystemOrg( - ecosystemId: string, - orgId: string - ): Promise<{ response: object }> { - const payload = { ecosystemId, orgId }; - return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); - } - - async getEndorsementTranasactions( - ecosystemId: string, - orgId: string, - getAllEndorsements: GetAllEndorsementsDto - ): Promise<{ response: object }> { - const { pageNumber, pageSize, search, type } = getAllEndorsements; - const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; - return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); - } - - - async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId:string): Promise { - const payload = { requestSchemaPayload, orgId, ecosystemId}; - return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); - } - - async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number, ecosystemId:string): Promise { - const payload = { requestCredDefPayload, orgId, ecosystemId}; - return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); - } - - async signTransaction(endorsementId:string, ecosystemId:string): Promise { - const payload = { endorsementId, ecosystemId }; - return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); - } - - async submitTransaction(endorsementId:string, ecosystemId:string): Promise { - const payload = { endorsementId, ecosystemId }; - return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); - } - - async declineEndorsementRequestByLead( - ecosystemId: string, - endorsementId: string, - orgId: string - ): Promise<{ response: object }> { - const payload = { ecosystemId, endorsementId, orgId }; - return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); - } - + async acceptRejectEcosystemInvitaion( + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + userEmail: string + ): Promise<{ response: string }> { + const payload = { acceptRejectInvitation, userEmail }; + return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); + } + + + async fetchEcosystemOrg( + ecosystemId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, orgId }; + return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); + } + + async getEndorsementTranasactions( + ecosystemId: string, + orgId: string, + getAllEndorsements: GetAllEndorsementsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search, type } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; + return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); + } + + + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId: string): Promise { + const payload = { requestSchemaPayload, orgId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); + } + + async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number, ecosystemId: string): Promise { + const payload = { requestCredDefPayload, orgId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); + } + + async signTransaction(endorsementId: string, ecosystemId: string): Promise { + const payload = { endorsementId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); + } + + async submitTransaction(endorsementId: string, ecosystemId: string): Promise { + const payload = { endorsementId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); + } + + async autoSignAndSubmitTransaction(): Promise<{ response: object }> { + const payload = {}; + return this.sendNats(this.serviceProxy, 'auto-endorsement-transaction', payload); + } + + async declineEndorsementRequestByLead( + ecosystemId: string, + endorsementId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, endorsementId, orgId }; + return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); + } + } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 524d499c0..f17c28d1b 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -12,7 +12,7 @@ import { RequestCredDeffEndorsement, RequestSchemaEndorsement } from '../interfa @Controller() export class EcosystemController { - constructor(private readonly ecosystemService: EcosystemService) {} + constructor(private readonly ecosystemService: EcosystemService) { } private readonly logger = new Logger('EcosystemController'); /** @@ -31,7 +31,7 @@ export class EcosystemController { * @param payload updation Details * @returns Get updated ecosystem details */ - @MessagePattern({ cmd: 'edit-ecosystem' }) + @MessagePattern({ cmd: 'edit-ecosystem' }) async editEcosystem(@Body() payload: { editEcosystemDto, ecosystemId }): Promise { return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); } @@ -43,7 +43,7 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'get-all-ecosystem' }) async getAllEcosystems( - @Body() payload: {orgId: string} + @Body() payload: { orgId: string } ): Promise { return this.ecosystemService.getAllEcosystem(payload); } @@ -63,18 +63,18 @@ export class EcosystemController { * Description: get ecosystem invitations * @returns Get sent invitation details */ - @MessagePattern({ cmd: 'get-ecosystem-invitations' }) - async getEcosystemInvitations( - @Body() payload: {userEmail: string, status: string; pageNumber: number; pageSize: number; search: string } - ): Promise { - return this.ecosystemService.getEcosystemInvitations( - payload.userEmail, - payload.status, - payload.pageNumber, - payload.pageSize, - payload.search - ); - } + @MessagePattern({ cmd: 'get-ecosystem-invitations' }) + async getEcosystemInvitations( + @Body() payload: { userEmail: string, status: string; pageNumber: number; pageSize: number; search: string } + ): Promise { + return this.ecosystemService.getEcosystemInvitations( + payload.userEmail, + payload.status, + payload.pageNumber, + payload.pageSize, + payload.search + ); + } /** * @@ -98,7 +98,7 @@ export class EcosystemController { @MessagePattern({ cmd: 'send-ecosystem-invitation' }) async createInvitation( @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } - ): Promise { + ): Promise { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } @@ -127,89 +127,98 @@ export class EcosystemController { @MessagePattern({ cmd: 'get-endorsement-transactions' }) async getEndorsementTransactions( @Body() payload: GetEndorsementsPayload - ): Promise { + ): Promise { return this.ecosystemService.getEndorsementTransactions( payload ); - } + } @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) async deleteInvitation( - @Body() payload: {invitationId: string} - ): Promise { + @Body() payload: { invitationId: string } + ): Promise { return this.ecosystemService.deleteEcosystemInvitations( payload.invitationId - ); - } + ); + } @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) async fetchEcosystemOrg( - @Body() payload: { ecosystemId: string, orgId: string} + @Body() payload: { ecosystemId: string, orgId: string } ): Promise { - return this.ecosystemService.fetchEcosystemOrg( - payload - ); + return this.ecosystemService.fetchEcosystemOrg( + payload + ); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'schema-endorsement-request' }) + async schemaEndorsementRequest( + @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } + ): Promise { + return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'credDef-endorsement-request' }) + async credDefEndorsementRequest( + @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } + ): Promise { + return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns sign endorsement request + */ + @MessagePattern({ cmd: 'sign-endorsement-transaction' }) + async signTransaction( + @Body() payload: { endorsementId: string, ecosystemId: string } + ): Promise { + return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns submit endorsement request + */ + @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) + async submitTransaction( + @Body() payload: { endorsementId: string, ecosystemId: string } + ): Promise { + return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns auto sign and submit endorsement request + */ + @MessagePattern({ cmd: 'auto-endorsement-transaction' }) + async autoSignAndSubmitTransaction(): Promise { + return this.ecosystemService.autoSignAndSubmitTransaction(); + } + + /** + * + * @param payload + * @returns Declien Endorsement Transaction status + */ + @MessagePattern({ cmd: 'decline-endorsement-transaction' }) + async declineEndorsementRequestByLead(payload: { + ecosystemId: string, endorsementId: string + }): Promise { + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } - - /** - * - * @param payload - * @returns Schema endorsement request - */ - @MessagePattern({ cmd: 'schema-endorsement-request' }) - async schemaEndorsementRequest( - @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } - ): Promise { - return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns Schema endorsement request - */ - @MessagePattern({ cmd: 'credDef-endorsement-request' }) - async credDefEndorsementRequest( - @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId:string} - ): Promise { - return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns sign endorsement request - */ - @MessagePattern({ cmd: 'sign-endorsement-transaction' }) - async signTransaction( - @Body() payload: { endorsementId: string, ecosystemId:string } - ): Promise { - return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns submit endorsement request - */ - @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) - async submitTransaction( - @Body() payload: { endorsementId: string, ecosystemId:string } - ): Promise { - return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); - } - - - /** - * - * @param payload - * @returns Declien Endorsement Transaction status - */ - @MessagePattern({ cmd: 'decline-endorsement-transaction' }) - async declineEndorsementRequestByLead(payload: { - ecosystemId:string, endorsementId:string - }): Promise { - return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); - } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 7c4e40a2c..96c265473 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -7,6 +7,7 @@ import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; import { NotFoundException } from '@nestjs/common'; +import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @Injectable() @@ -156,7 +157,7 @@ export class EcosystemRepository { * @returns Get specific organization details from ecosystem */ // eslint-disable-next-line camelcase - async checkEcosystemOrgs(orgId:string): Promise { + async checkEcosystemOrgs(orgId: string): Promise { try { if (!orgId) { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); @@ -176,14 +177,16 @@ export class EcosystemRepository { * * @returns Get ecosystem dashboard card count */ - - async getEcosystemDashboardDetails(ecosystemId: string): Promise<{membersCount: number; endorsementsCount: number}> { + // eslint-disable-next-line camelcase + async getEcosystemDashboardDetails(ecosystemId: string): Promise<{ membersCount: number; endorsementsCount: number; ecosystemConfigData: ecosystem_config[] }> { try { const membersCount = await this.getEcosystemMembersCount(ecosystemId); const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); + const ecosystemConfigData = await this.getEcosystemConfig(); return { membersCount, - endorsementsCount + endorsementsCount, + ecosystemConfigData }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); @@ -191,6 +194,16 @@ export class EcosystemRepository { } } + // eslint-disable-next-line camelcase + async getEcosystemConfig(): Promise { + try { + const getEcosystemConfigDetails = await this.prisma.ecosystem_config.findMany(); + return getEcosystemConfigDetails; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } async getEcosystemMembersCount(ecosystemId: string): Promise { try { @@ -866,4 +879,41 @@ export class EcosystemRepository { throw error; } } + + async updateAutoSignAndSubmitTransaction(): Promise<{ + id: string; + key: string; + value: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + }> { + try { + + const { id, value } = await this.prisma.ecosystem_config.findFirst({ + where: { + key: `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}` + } + }); + + const updatedValue = 'false' === value ? 'true' : 'false'; + + const updateEcosystemConfig = await this.prisma.ecosystem_config.update({ + where: { + id + }, + data: { + value: updatedValue + } + }); + + return updateEcosystemConfig; + + } catch (error) { + this.logger.error(`Error in update auto sign and submit flag: ${error.message}`); + throw error; + } + } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 5b18da802..503685d61 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -107,7 +107,8 @@ export class EcosystemService { endorsementsCount: endorseMemberCount.endorsementsCount, ecosystemLead:{ role: ecosystemDetails['ecosystemRole']['name'], - orgName: ecosystemDetails['orgName'] + orgName: ecosystemDetails['orgName'], + config: endorseMemberCount.ecosystemConfigData } }; @@ -563,7 +564,7 @@ export class EcosystemService { if (updateSignedTransaction && 'true' === ecosystemConfigDetails.value) { - const submitTxn = await this.submitTransaction(endorsementId, ecosystemId); + const submitTxn = await this.submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentDetails.agentEndPoint); if (!submitTxn) { await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); @@ -730,7 +731,7 @@ export class EcosystemService { return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); } - async submitTransaction(endorsementId, ecosystemId): Promise { + async submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentEndPoint?): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); @@ -745,7 +746,9 @@ export class EcosystemService { const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); const platformConfig = await this.getPlatformConfig(); - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); + + const agentEndPoint = ecosystemLeadAgentEndPoint ? ecosystemLeadAgentEndPoint : ecosystemMemberDetails.agentEndPoint; + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); const payload = await this.submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails); const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); @@ -928,6 +931,19 @@ export class EcosystemService { } } + /** + * @returns EndorsementTransaction Status message + */ + + async autoSignAndSubmitTransaction(): Promise { + try { + + return await this.ecosystemRepository.updateAutoSignAndSubmitTransaction(); + } catch (error) { + this.logger.error(`error in decline endorsement request: ${error}`); + throw new InternalServerErrorException(error); + } + } /** * diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 52fb01b9d..e713dc8cc 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -198,7 +198,8 @@ export const ResponseMessages = { invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', fetchEndorsors: 'Endorser transactions fetched successfully', - DeclineEndorsementTransaction:'Decline endorsement request successfully', + DeclineEndorsementTransaction: 'Decline endorsement request successfully', + AutoEndorsementTransaction: 'The flag for transactions has been successfully set', fetchMembers: 'Ecosystem members fetched successfully' }, error: { @@ -231,9 +232,9 @@ export const ResponseMessages = { invalidTransaction: 'Transaction does not exist', transactionSubmitted: 'Transaction already submitted', invalidAgentUrl: 'Invalid agent url', - EndorsementTransactionNotFoundException:'Endorsement transaction with status requested not found', - OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg', + EndorsementTransactionNotFoundException: 'Endorsement transaction with status requested not found', + OrgOrEcosystemNotFoundExceptionForEndorsementTransaction: 'The endorsement transaction status cant be updated', ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.' } - } + } }; \ No newline at end of file From f49d7445c2d2105a87f76f85e4ed241a5ebac655 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Thu, 19 Oct 2023 11:43:05 +0530 Subject: [PATCH 017/117] feat: Out-Of-Band issuance Signed-off-by: KulkarniShashank --- .../src/agent-service.controller.ts | 7 +- .../src/agent-service.service.ts | 13 +- .../src/interface/agent-service.interface.ts | 9 ++ .../src/issuance/dtos/issuance.dto.ts | 35 ++++- .../src/issuance/issuance.controller.ts | 39 ++++- .../src/issuance/issuance.service.ts | 8 +- .../interfaces/issuance.interfaces.ts | 23 ++- apps/issuance/src/issuance.controller.ts | 8 +- apps/issuance/src/issuance.module.ts | 4 +- apps/issuance/src/issuance.repository.ts | 17 ++- apps/issuance/src/issuance.service.ts | 140 +++++++++++++++++- .../out-of-band-issuance.template.ts | 61 ++++++++ libs/common/src/common.constant.ts | 2 + libs/common/src/response-messages/index.ts | 6 +- 14 files changed, 353 insertions(+), 19 deletions(-) create mode 100644 apps/issuance/templates/out-of-band-issuance.template.ts diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 1d4a99192..9b33fe4c8 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -1,7 +1,7 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { AgentServiceService } from './agent-service.service'; -import { GetCredDefAgentRedirection, GetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema } from './interface/agent-service.interface'; +import { GetCredDefAgentRedirection, GetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, OutOfBandCredentialOffer } from './interface/agent-service.interface'; import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface'; import { ISendProofRequestPayload } from './interface/agent-service.interface'; import { user } from '@prisma/client'; @@ -124,4 +124,9 @@ export class AgentServiceController { async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload:object }): Promise { return this.agentServiceService.sumbitTransaction(payload.url, payload.apiKey, payload.submitEndorsementPayload); } + + @MessagePattern({ cmd: 'agent-out-of-band-credential-offer' }) + async outOfBandCredentialOffer(payload: { outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string }): Promise { + return this.agentServiceService.outOfBandCredentialOffer(payload.outOfBandIssuancePayload, 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 557960456..842d4cada 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -16,7 +16,7 @@ import * as dotenv from 'dotenv'; import * as fs from 'fs'; import { catchError, map } from 'rxjs/operators'; dotenv.config(); -import { GetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer } from './interface/agent-service.interface'; +import { GetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, OutOfBandCredentialOffer } from './interface/agent-service.interface'; import { AgentType, OrgAgentType } from '@credebl/enum/enum'; import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface'; import { AgentServiceRepository } from './repositories/agent-service.repository'; @@ -943,5 +943,16 @@ export class AgentServiceService { } } + async outOfBandCredentialOffer(outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string): Promise { + try { + const sendOutOfbandCredentialOffer = await this.commonService + .httpPost(url, outOfBandIssuancePayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + return sendOutOfbandCredentialOffer; + } catch (error) { + this.logger.error(`Error in out-of-band credential in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } } diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 3386889bb..834afdf64 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -14,6 +14,15 @@ export interface IAgentSpinupDto { tenant?: boolean; } +export interface OutOfBandCredentialOffer { + emailId: string; + attributes: IAttributes[]; + credentialDefinitionId: string; + comment: string; + protocolVersion?: string; + orgId: number; +} + export interface ITenantDto { label: string; seed: string; diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index e2f6a4c37..51b6ce07c 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,7 +1,12 @@ import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -interface attribute { +interface CredentialOffer { + emailId: string; + attribute: Attribute[]; +} + +interface Attribute { name: string; value: string; } @@ -11,7 +16,7 @@ export class IssueCredentialDto { @ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] }) @IsNotEmpty({ message: 'Please provide valid attributes' }) @IsArray({ message: 'attributes should be array' }) - attributes: attribute[]; + attributes: Attribute[]; @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid credentialDefinitionId' }) @@ -98,3 +103,29 @@ export class CredentialAttributes { value: string; } +export class OutOfBandCredentialDto { + + @ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attribute': { 'value': 'string', 'name': 'string' } }] }) + @IsNotEmpty({ message: 'Please provide valid attributes' }) + @IsArray({ message: 'attributes should be array' }) + credentialOffer: CredentialOffer[]; + + @ApiProperty({ example: 'string' }) + @IsNotEmpty({ message: 'Please provide valid credential definition id' }) + @IsString({ message: 'credential definition id should be string' }) + credentialDefinitionId: string; + + @ApiProperty({ example: 'string' }) + @IsNotEmpty({ message: 'Please provide valid comment' }) + @IsString({ message: 'comment should be string' }) + @IsOptional() + comment: string; + + @ApiProperty({ example: 'v1' }) + @IsOptional() + @IsNotEmpty({ message: 'Please provide valid protocol version' }) + @IsString({ message: 'protocol version should be string' }) + protocolVersion?: string; + + orgId: number; +} \ No newline at end of file diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index ba130ad24..beb58488b 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -33,7 +33,7 @@ import { CommonService } from '@credebl/common/common.service'; import { Response } from 'express'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { IssuanceService } from './issuance.service'; -import { IssuanceDto, IssueCredentialDto } from './dtos/issuance.dto'; +import { IssuanceDto, IssueCredentialDto, OutOfBandCredentialDto } from './dtos/issuance.dto'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { User } from '../authz/decorators/user.decorator'; import { ResponseMessages } from '@credebl/common/response-messages'; @@ -183,6 +183,42 @@ export class IssuanceController { return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * Description: credential issuance out-of-band + * @param user + * @param outOfBandCredentialDto + * @param orgId + * @param res + * @returns + */ + @Post('/orgs/:orgId/credentials/oob') + // @UseGuards(AuthGuard('jwt')) + @ApiOperation({ + summary: `Create out-of-band credential offer`, + description: `Create out-of-band credential offer` + }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + // @ApiBearerAuth() + // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async outOfBandCredentialOffer( + @User() user: IUserRequest, + @Body() outOfBandCredentialDto: OutOfBandCredentialDto, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + outOfBandCredentialDto.orgId = orgId; + const getCredentialDetails = await this.issueCredentialService.outOfBandCredentialOffer(user, outOfBandCredentialDto); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Description: webhook Save issued credential details * @param user @@ -209,5 +245,4 @@ export class IssuanceController { return res.status(HttpStatus.CREATED).json(finalResponse); } - } diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index 38bb28cff..e609c1d90 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { IssuanceDto, IssueCredentialDto } from './dtos/issuance.dto'; +import { IssuanceDto, IssueCredentialDto, OutOfBandCredentialDto } from './dtos/issuance.dto'; @Injectable() export class IssuanceService extends BaseService { @@ -50,4 +50,10 @@ export class IssuanceService extends BaseService { return this.sendNats(this.issuanceProxy, 'webhook-get-issue-credential', payload); } + outOfBandCredentialOffer(user: IUserRequest, outOfBandCredentialDto: OutOfBandCredentialDto): Promise<{ + response: object; + }> { + const payload = { user, outOfBandCredentialDto }; + return this.sendNats(this.issuanceProxy, 'out-of-band-credential-offer', payload); + } } diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index 16aeb6e6c..fd255e8d3 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -2,7 +2,7 @@ import { IUserRequest } from '@credebl/user-request/user-request.interface'; -export interface IAttributes { +export interface Attributes { name: string; value: string; } @@ -11,7 +11,7 @@ export interface IIssuance { credentialDefinitionId: string; comment: string; connectionId: string; - attributes: IAttributes[]; + attributes: Attributes[]; orgId: number; protocolVersion: string; } @@ -38,9 +38,26 @@ export interface IIssuanceWebhookInterface { credentialAttributes: ICredentialAttributesInterface[]; orgId: number; } - + export interface ICredentialAttributesInterface { 'mime-type': string; name: string; value: string; } + +export interface CredentialOffer { + emailId: string; + attribute: Attributes[]; +} +export interface OutOfBandCredentialOfferPayload { + credentialOffer: CredentialOffer[]; + credentialDefinitionId: string; + comment: string; + protocolVersion?: string; + orgId: number; +} + +export interface OutOfBandCredentialOffer { + user: IUserRequest; + outOfBandCredentialDto: OutOfBandCredentialOfferPayload; +} \ No newline at end of file diff --git a/apps/issuance/src/issuance.controller.ts b/apps/issuance/src/issuance.controller.ts index 698ef908b..c2083d1c9 100644 --- a/apps/issuance/src/issuance.controller.ts +++ b/apps/issuance/src/issuance.controller.ts @@ -1,6 +1,6 @@ import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; -import { IIssuance, IIssuanceWebhookInterface, IIssueCredentials, IIssueCredentialsDefinitions } from '../interfaces/issuance.interfaces'; +import { IIssuance, IIssuanceWebhookInterface, IIssueCredentials, IIssueCredentialsDefinitions, OutOfBandCredentialOffer } from '../interfaces/issuance.interfaces'; import { IssuanceService } from './issuance.service'; @Controller() @@ -37,4 +37,10 @@ export class IssuanceController { const { createDateTime, connectionId, threadId, protocolVersion, credentialAttributes, orgId } = payload; return this.issuanceService.getIssueCredentialWebhook(createDateTime, connectionId, threadId, protocolVersion, credentialAttributes, orgId); } + + @MessagePattern({ cmd: 'out-of-band-credential-offer' }) + async outOfBandCredentialOffer(payload: OutOfBandCredentialOffer): Promise { + const { user, outOfBandCredentialDto } = payload; + return this.issuanceService.outOfBandCredentialOffer(user, outOfBandCredentialDto); + } } diff --git a/apps/issuance/src/issuance.module.ts b/apps/issuance/src/issuance.module.ts index 732e40f35..a6ec1a452 100644 --- a/apps/issuance/src/issuance.module.ts +++ b/apps/issuance/src/issuance.module.ts @@ -6,6 +6,8 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; import { IssuanceController } from './issuance.controller'; import { IssuanceRepository } from './issuance.repository'; import { IssuanceService } from './issuance.service'; +import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; @Module({ imports: [ @@ -22,6 +24,6 @@ import { IssuanceService } from './issuance.service'; CommonModule ], controllers: [IssuanceController], - providers: [IssuanceService, IssuanceRepository, PrismaService, Logger] + providers: [IssuanceService, IssuanceRepository, PrismaService, Logger, OutOfBandIssuance, EmailDto] }) export class IssuanceModule { } diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 0ac80a9e9..681eb1539 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { agent_invitations, credentials, org_agents, platform_config, shortening_url } from '@prisma/client'; +import { agent_invitations, credentials, org_agents, organisation, platform_config, shortening_url } from '@prisma/client'; import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() export class IssuanceRepository { @@ -144,4 +144,19 @@ export class IssuanceRepository { throw new InternalServerErrorException(error); } } + + /** + * Get organization details + * @returns + */ + async getOrganization(orgId: number): Promise { + try { + + return this.prisma.organisation.findFirst({ where: { id: orgId } }); + + } catch (error) { + this.logger.error(`[getOrganization] - error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } \ No newline at end of file diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 1760c97fc..82a4643e4 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -1,15 +1,19 @@ /* eslint-disable camelcase */ import { CommonService } from '@credebl/common'; -import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { IssuanceRepository } from './issuance.repository'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CommonConstants } from '@credebl/common/common.constant'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; -import { ICredentialAttributesInterface } from '../interfaces/issuance.interfaces'; +import { ICredentialAttributesInterface, OutOfBandCredentialOfferPayload } from '../interfaces/issuance.interfaces'; import { OrgAgentType } from '@credebl/enum/enum'; import { platform_config } from '@prisma/client'; +import * as QRCode from 'qrcode'; +import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; @Injectable() @@ -18,8 +22,9 @@ export class IssuanceService { constructor( @Inject('NATS_CLIENT') private readonly issuanceServiceProxy: ClientProxy, private readonly commonService: CommonService, - private readonly issuanceRepository: IssuanceRepository - + private readonly issuanceRepository: IssuanceRepository, + private readonly outOfBandIssuance: OutOfBandIssuance, + private readonly emailData: EmailDto ) { } @@ -245,6 +250,121 @@ export class IssuanceService { } } + async outOfBandCredentialOffer(user: IUserRequest, outOfBandCredential: OutOfBandCredentialOfferPayload): Promise { + try { + const { + credentialOffer, + comment, + credentialDefinitionId, + orgId, + protocolVersion + } = outOfBandCredential; + + const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + + const issuanceMethodLabel = 'create-offer-oob'; + const url = await this.getAgentUrl(issuanceMethodLabel, agentDetails.orgAgentTypeId, agentDetails.agentEndPoint, agentDetails.tenantId); + const organizationDetails = await this.issuanceRepository.getOrganization(orgId); + const { apiKey } = agentDetails; + + const emailPromises = credentialOffer.map(async (iterator) => { + const outOfBandIssuancePayload = { + protocolVersion: protocolVersion || 'v1', + credentialFormats: { + indy: { + attributes: [iterator.attribute], + credentialDefinitionId + } + }, + autoAcceptCredential: 'always', + comment + }; + + const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); + + if (!credentialCreateOfferDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.credentialOfferNotFound); + } + + const invitationId = credentialCreateOfferDetails.response.invitation['@id']; + if (!invitationId) { + throw new NotFoundException(ResponseMessages.issuance.error.invitationNotFound); + } + + const agentEndPoint = agentDetails.tenantId + ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` + : `${agentDetails.agentEndPoint}/url/${invitationId}`; + + const qrCodeOptions = { type: 'image/png' }; + const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); + const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); + + if (!platformConfigData) { + throw new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound); + } + + this.emailData.emailFrom = platformConfigData.emailFrom; + this.emailData.emailTo = iterator.emailId; + this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credentials Required`; + this.emailData.emailHtml = await this.outOfBandIssuance.outOfBandIssuance(iterator.emailId, organizationDetails.name, outOfBandIssuanceQrCode); + this.emailData.emailAttachments = [ + { + filename: 'qrcode.png', + content: outOfBandIssuanceQrCode.split(';base64,')[1], + contentType: 'image/png', + disposition: 'attachment' + } + ]; + + const isEmailSent = await sendEmail(this.emailData); + if (!isEmailSent) { + throw new InternalServerErrorException(ResponseMessages.issuance.error.emailSend); + } + return isEmailSent; + }); + + const results = await Promise.all(emailPromises); + + // Check if all emails were successfully sent + return results.every((result) => true === result); + } catch (error) { + this.logger.error(`[outOfBoundCredentialOffer] - error in create out-of-band credentials: ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } + + + async _outOfBandCredentialOffer(outOfBandIssuancePayload: object, url: string, apiKey: string): Promise<{ + response; + }> { + try { + const pattern = { cmd: 'agent-out-of-band-credential-offer' }; + const payload = { outOfBandIssuancePayload, url, apiKey }; + return this.issuanceServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.statusCode, + error: error.message + }, error.error); + }); + } catch (error) { + this.logger.error(`[_outOfBandCredentialOffer] [NATS call]- error in out of band : ${JSON.stringify(error)}`); + throw error; + } + } + /** * Description: Fetch agent url * @param referenceId @@ -272,7 +392,7 @@ export class IssuanceService { case 'create-offer-oob': { url = orgAgentTypeId === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_ISSUE_CREATE_CRED_OFFER_AFJ}` + ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` : orgAgentTypeId === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER_OUT_OF_BAND}`.replace('#', tenantId) : null; @@ -298,6 +418,16 @@ export class IssuanceService { break; } + case 'create-offer-oob': { + + url = orgAgentTypeId === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` + : orgAgentTypeId === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_OUT_OF_BAND_CREDENTIAL}`.replace('#', tenantId) + : null; + break; + } + default: { break; } diff --git a/apps/issuance/templates/out-of-band-issuance.template.ts b/apps/issuance/templates/out-of-band-issuance.template.ts new file mode 100644 index 000000000..6031fb0b2 --- /dev/null +++ b/apps/issuance/templates/out-of-band-issuance.template.ts @@ -0,0 +1,61 @@ +export class OutOfBandIssuance { + + public outOfBandIssuance(email: string, orgName: string, issuanceQrCode: string): string { + try { + return ` + + + + + + + + + +
+
+ Credebl Logo +
+
+ verification Image +
+
+

+ Hello ${email} , +

+

+ The organization ${orgName} has requested your assistance in issuing your credentials. + We are delighted to notify you that a credential document has been successfully issued to you. To acknowledge and access the document, kindly proceed with the instructions outlined below: +

    +
  • Download the ADHAYA Wallet from the Play Store.
  • +
  • Create an Account.
  • +
  • Scan the QR code provided below.
  • +
  • Accept the Credential Document request.
  • +
  • Check your wallet to access the issued Credential Document.
  • +
+ Should you require any assistance or have questions, feel free to contact our dedicated support team. +

+ QR Code +
+
+
+ + f + t +
+

+ Best Regards,The CREDEBL Team +

+
+
+
+ + `; + + } catch (error) { + } + } +} \ No newline at end of file diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 2e9fe6e88..306d8bc78 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -71,6 +71,7 @@ export enum CommonConstants { // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values 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', // SCHEMA & CRED DEF SERVICES URL_SCHM_CREATE_SCHEMA = '/schemas', @@ -100,6 +101,7 @@ 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_OUT_OF_BAND_CREDENTIAL = '/multi-tenancy/credentials/create-offer-oob/#', // 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 e713dc8cc..67d350a1d 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -161,7 +161,11 @@ export const ResponseMessages = { credentialsNotFound: 'Credentials not found', agentEndPointNotFound: 'agentEndPoint Not Found', agentUrlNotFound: 'agent url not found', - notFound: 'Organization agent not found' + notFound: 'Organization agent not found', + credentialOfferNotFound: "Credential offer not found", + invitationNotFound: "Invitation not found", + platformConfigNotFound: "Platform config details not found", + emailSend: 'Unable to send email to the user', } }, verification: { From afc8bcc91a08f8cfd71cd41341dc6117a312c622 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 18 Oct 2023 16:00:54 +0530 Subject: [PATCH 018/117] fix: implemented request body in credential definition request Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank --- .../src/ecosystem/dtos/request-schema.dto.ts | 49 +++++++++++++++++-- .../interfaces/ecosystem.interfaces.ts | 1 + apps/ecosystem/src/ecosystem.controller.ts | 12 ++--- apps/ecosystem/src/ecosystem.service.ts | 15 ++---- 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts index 0d0598f1f..bbd0aad6a 100644 --- a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts @@ -1,5 +1,6 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; @ApiExtraModels() class AttributeValue { @@ -17,6 +18,17 @@ class AttributeValue { displayName: string; } +class CredDefSchemaDetails { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) + attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) + schemaDataType: string; +} + export class RequestSchemaDto { @ApiProperty() @IsString({ message: 'name must be in string format.' }) @@ -46,6 +58,32 @@ export class RequestSchemaDto { } +export class SchemaDetails { + @ApiProperty() + @IsString({ message: 'name must be a string.' }) + name: string; + + @ApiProperty() + @IsString({ message: 'version must be a string.' }) + version: string; + + @ApiProperty({ + example: [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) + @IsArray({ message: 'attributes must be an array.' }) + @IsNotEmpty({ message: 'please provide valid attributes.' }) + @ValidateNested({ each: true }) + @Type(() => CredDefSchemaDetails) + attributes: CredDefSchemaDetails[]; + +} + export class RequestCredDefDto { @ApiProperty() @IsBoolean({ message: 'endorse must be a boolean.' }) @@ -53,10 +91,15 @@ export class RequestCredDefDto { endorse?: boolean; @ApiProperty() - @IsString({ message: 'tag must be in string format.' }) + @IsString({ message: 'tag must be a string.' }) tag: string; @ApiProperty() - @IsString({ message: 'schemaId must be in string format.' }) + @IsString({ message: 'schemaId must be a string.' }) schemaId: string; + + @ApiProperty() + @ValidateNested() + @Type(() => SchemaDetails) + schemaDetails: SchemaDetails; } \ No newline at end of file diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 1e7aa379b..e77392db7 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -17,6 +17,7 @@ export interface RequestCredDeffEndorsement { schemaId: string tag: string; endorse?: boolean; + schemaDetails?: object; } export interface IAttributeValue { diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index f17c28d1b..77469fb10 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -156,8 +156,7 @@ export class EcosystemController { * @returns Schema endorsement request */ @MessagePattern({ cmd: 'schema-endorsement-request' }) - async schemaEndorsementRequest( - @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } + async schemaEndorsementRequest(payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } ): Promise { return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); } @@ -168,8 +167,7 @@ export class EcosystemController { * @returns Schema endorsement request */ @MessagePattern({ cmd: 'credDef-endorsement-request' }) - async credDefEndorsementRequest( - @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } + async credDefEndorsementRequest(payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } ): Promise { return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); } @@ -180,8 +178,7 @@ export class EcosystemController { * @returns sign endorsement request */ @MessagePattern({ cmd: 'sign-endorsement-transaction' }) - async signTransaction( - @Body() payload: { endorsementId: string, ecosystemId: string } + async signTransaction(payload: { endorsementId: string, ecosystemId: string } ): Promise { return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); } @@ -192,8 +189,7 @@ export class EcosystemController { * @returns submit endorsement request */ @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) - async submitTransaction( - @Body() payload: { endorsementId: string, ecosystemId: string } + async submitTransaction(payload: { endorsementId: string, ecosystemId: string } ): Promise { return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 503685d61..dbd068b07 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,7 +13,7 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CreateEcosystem, CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, CredDefMessage, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @@ -316,7 +316,7 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload, orgId, ecosystemId): Promise { + async requestSchemaEndorsement(requestSchemaPayload:RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -386,7 +386,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload, orgId, ecosystemId): Promise { + async requestCredDeffEndorsement(requestCredDefPayload:RequestCredDeffEndorsement, orgId:number, ecosystemId:string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -439,12 +439,6 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } - const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; - - if (!requestBody) { - throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); - } - const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -453,7 +447,7 @@ export class EcosystemService { ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, endorsementTransactionType.CREDENTIAL_DEFINITION); + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestCredDefPayload, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { this.logger.error(`In request cred-def endorsement: ${JSON.stringify(error)}`); throw new RpcException(error.response || error); @@ -734,7 +728,6 @@ export class EcosystemService { async submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentEndPoint?): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); - if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } From 9a4bd2856726867cfb808083cc637bb40fb2ea04 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 18 Oct 2023 17:53:12 +0530 Subject: [PATCH 019/117] fix: added attributes as array of string Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank --- .../src/ecosystem/dtos/request-schema.dto.ts | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts index bbd0aad6a..202857f57 100644 --- a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts @@ -18,16 +18,6 @@ class AttributeValue { displayName: string; } -class CredDefSchemaDetails { - - @IsString() - @IsNotEmpty({ message: 'attributeName is required.' }) - attributeName: string; - - @IsString() - @IsNotEmpty({ message: 'schemaDataType is required.' }) - schemaDataType: string; -} export class RequestSchemaDto { @ApiProperty() @@ -68,19 +58,11 @@ export class SchemaDetails { version: string; @ApiProperty({ - example: [ - { - attributeName: 'name', - schemaDataType: 'string', - displayName: 'Name' - } - ] + example: ['name', 'id'] }) @IsArray({ message: 'attributes must be an array.' }) @IsNotEmpty({ message: 'please provide valid attributes.' }) - @ValidateNested({ each: true }) - @Type(() => CredDefSchemaDetails) - attributes: CredDefSchemaDetails[]; + attributes: string[]; } @@ -100,6 +82,7 @@ export class RequestCredDefDto { @ApiProperty() @ValidateNested() + @IsOptional() @Type(() => SchemaDetails) - schemaDetails: SchemaDetails; + schemaDetails?: SchemaDetails; } \ No newline at end of file From e0f181dd2efe2f2dca09c4327a27ebab2cd29a11 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 18 Oct 2023 15:46:32 +0530 Subject: [PATCH 020/117] refactor: send ecosystem invitations link Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- .../src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.service.ts | 36 ++++++++++++++++--- .../templates/EcosystemInviteTemplate.ts | 11 ++++-- libs/common/src/send-grid-helper-file.ts | 2 +- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c6e037a0d..d9a8bf1a0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -209,7 +209,7 @@ export class EcosystemController { */ @Get('/:ecosystemId/:orgId/members') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index dbd068b07..c325b28dc 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -17,7 +17,7 @@ import { CreateEcosystem, CredDefMessage, RequestCredDeffEndorsement, RequestSch import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase -import { credential_definition, org_agents, platform_config, schema } from '@prisma/client'; +import { credential_definition, org_agents, platform_config, schema, user } from '@prisma/client'; @Injectable() @@ -158,13 +158,14 @@ export class EcosystemService { for (const invitation of invitations) { const { email } = invitation; + const isUserExist = await this.checkUserExistInPlatform(email); + const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); if (!isInvitationExist) { await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); - try { - await this.sendInviteEmailTemplate(email, ecosystemDetails.name); + await this.sendInviteEmailTemplate(email, ecosystemDetails.name, isUserExist); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -293,7 +294,8 @@ export class EcosystemService { */ async sendInviteEmailTemplate( email: string, - ecosystemName: string + ecosystemName: string, + isUserExist: boolean ): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -303,7 +305,7 @@ export class EcosystemService { emailData.emailTo = email; emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Invitation`; - emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, ecosystemName); + emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, ecosystemName, isUserExist); //Email is sent to user for the verification through emailData const isEmailSent = await sendEmail(emailData); @@ -311,6 +313,30 @@ export class EcosystemService { return isEmailSent; } + async checkUserExistInPlatform(email: string): Promise { + const pattern = { cmd: 'get-user-by-mail' }; + const payload = { email }; + + const userData: user = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + + if (userData && userData.isEmailVerified) { + return true; + } + return false; + } + /** * * @param RequestSchemaEndorsement diff --git a/apps/ecosystem/templates/EcosystemInviteTemplate.ts b/apps/ecosystem/templates/EcosystemInviteTemplate.ts index 2f872654a..1486b1db4 100644 --- a/apps/ecosystem/templates/EcosystemInviteTemplate.ts +++ b/apps/ecosystem/templates/EcosystemInviteTemplate.ts @@ -2,12 +2,17 @@ export class EcosystemInviteTemplate { public sendInviteEmailTemplate( email: string, - ecosystemName: string + ecosystemName: string, + isUserExist = false ): string { - const validUrl = `${process.env.FRONT_END_URL}/authentication/sign-in`; + const validUrl = isUserExist ? `${process.env.FRONT_END_URL}/authentication/sign-in` : `${process.env.FRONT_END_URL}/authentication/sign-up`; + + const message = isUserExist + ? `You have already registered on platform, you can access the application. + Please log in and accept the ecosystem “INVITATION” and participate in the ecosystem` + : `You have to register on the platform and then you can access the application. Accept the ecosystem “INVITATION” and participate in the ecosystem`; - const message = `You have been invited to join the ecosystem so please log in and accept the ecosystem “INVITATION” and participate in the ecosystem`; const year: number = new Date().getFullYear(); return ` diff --git a/libs/common/src/send-grid-helper-file.ts b/libs/common/src/send-grid-helper-file.ts index a98bd518b..623d118e1 100644 --- a/libs/common/src/send-grid-helper-file.ts +++ b/libs/common/src/send-grid-helper-file.ts @@ -18,7 +18,7 @@ export const sendEmail = async (EmailDto: EmailDto): Promise => { html: EmailDto.emailHtml, attachments: EmailDto.emailAttachments }; - return await sendgrid.send(msg).then(() => true).catch(() => false) + return await sendgrid.send(msg).then(() => true).catch(() => false); } catch (error) { return false; From b8147577b79a017165b0436c2fda03b1c39fdc13 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:03:33 +0530 Subject: [PATCH 021/117] fix: send invitation flow for organization & ecosystem (#179) * fix: invitation flow Signed-off-by: Nishad * resolved the comments on PR Signed-off-by: Nishad --------- Signed-off-by: Nishad Signed-off-by: KulkarniShashank --- .../src/ecosystem/ecosystem.controller.ts | 2 +- .../src/ecosystem/ecosystem.service.ts | 4 ++-- .../organization/organization.controller.ts | 2 +- .../src/organization/organization.service.ts | 4 ++-- apps/ecosystem/src/ecosystem.controller.ts | 4 ++-- apps/ecosystem/src/ecosystem.service.ts | 19 +++++++++++++--- .../src/organization.controller.ts | 4 ++-- apps/organization/src/organization.service.ts | 22 +++++++++++++++---- libs/common/src/response-messages/index.ts | 4 ++-- 9 files changed, 46 insertions(+), 19 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index d9a8bf1a0..f8212a7fe 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -357,7 +357,7 @@ export class EcosystemController { @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.ecosystemId = ecosystemId; - await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id)); + await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id), user.email); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 1ad6f07af..3a7e6b2e7 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -66,8 +66,8 @@ export class EcosystemService extends BaseService { * @param userId * @returns */ - async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string): Promise { - const payload = { bulkInvitationDto, userId }; + async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string, userEmail: string): Promise { + const payload = { bulkInvitationDto, userId, userEmail }; return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 667c7bb26..a4a796895 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -298,7 +298,7 @@ export class OrganizationController { async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: number, @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.orgId = orgId; - await this.organizationService.createInvitation(bulkInvitationDto, user.id); + await this.organizationService.createInvitation(bulkInvitationDto, user.id, user.email); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index c7579dcf5..2af36e772 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -109,8 +109,8 @@ export class OrganizationService extends BaseService { * @param sendInvitationDto * @returns Organization invitation creation Success */ - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { - const payload = { bulkInvitationDto, userId }; + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number, userEmail: string): Promise { + const payload = { bulkInvitationDto, userId, userEmail }; return this.sendNats(this.serviceProxy, 'send-invitation', payload); } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 77469fb10..e996dc550 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -97,9 +97,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'send-ecosystem-invitation' }) async createInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } + payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string, userEmail: string } ): Promise { - return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); + return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c325b28dc..4a297d902 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -149,7 +149,7 @@ export class EcosystemService { * @param userId * @returns */ - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string): Promise { + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const { invitations, ecosystemId } = bulkInvitationDto; try { @@ -162,7 +162,7 @@ export class EcosystemService { const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); - if (!isInvitationExist) { + if (!isInvitationExist && userEmail === invitation.email) { await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); try { await this.sendInviteEmailTemplate(email, ecosystemDetails.name, isUserExist); @@ -277,9 +277,22 @@ export class EcosystemService { const invitations = await this.ecosystemRepository.getEcosystemInvitations(query); - if (0 < invitations.length) { + let isPendingInvitation = false; + let isAcceptedInvitation = false; + + for (const invitation of invitations) { + if (invitation.status === Invitation.PENDING) { + isPendingInvitation = true; + } + if (invitation.status === Invitation.ACCEPTED) { + isAcceptedInvitation = true; + } + } + + if (isPendingInvitation || isAcceptedInvitation) { return true; } + return false; } catch (error) { throw new RpcException(error.response ? error.response : error); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index bbe5bfd8b..6128c1592 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -110,9 +110,9 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'send-invitation' }) async createInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: number } + @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: number, userEmail: string } ): Promise { - return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId); + return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } @MessagePattern({ cmd: 'fetch-user-invitations' }) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 3dae32045..55ec0e538 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -273,9 +273,22 @@ export class OrganizationService { const invitations = await this.organizationRepository.getOrgInvitations(query); - if (0 < invitations.length) { + let isPendingInvitation = false; + let isAcceptedInvitation = false; + + for (const invitation of invitations) { + if (invitation.status === Invitation.PENDING) { + isPendingInvitation = true; + } + if (invitation.status === Invitation.ACCEPTED) { + isAcceptedInvitation = true; + } + } + + if (isPendingInvitation || isAcceptedInvitation) { return true; } + return false; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); @@ -290,7 +303,7 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number, userEmail: string): Promise { const { invitations, orgId } = bulkInvitationDto; try { @@ -301,9 +314,10 @@ export class OrganizationService { const isUserExist = await this.checkUserExistInPlatform(email); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + const isInvitationExist = await this.checkInvitationExist(email, orgId); + + if (!isInvitationExist && userEmail !== invitation.email) { - if (!isInvitationExist) { await this.organizationRepository.createSendInvitation(email, orgId, userId, orgRoleId); const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 67d350a1d..2f4e7e6e6 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -43,7 +43,7 @@ export const ResponseMessages = { update: 'Organization updated successfully', fetchProfile: 'Organization profile fetched successfully', fetchOrgRoles: 'Organization roles fetched successfully', - createInvitation: 'Organization invitations sent successfully', + createInvitation: 'Organization invitations sent', getInvitation: 'Organization invitations fetched successfully', getOrganization: 'Organization details fetched successfully', getOrgDashboard: 'Organization dashboard details fetched', @@ -194,7 +194,7 @@ export const ResponseMessages = { fetch: 'Ecosystem fetched successfully', getEcosystemDashboard: 'Ecosystem dashboard details fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', - createInvitation: 'Ecosystem invitations sent successfully', + createInvitation: 'Ecosystem invitations sent', schemaRequest: 'Schema transaction request created successfully', credDefRequest: 'credential-definition transaction request created successfully', sign: 'Transaction request signed successfully', From d0548a0d162556fcd318a8090904b167be8f8114 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:26:55 +0530 Subject: [PATCH 022/117] feat: cred-def list by schemaId for verification (#180) * fix: cred-def list by schemaId for verification Signed-off-by: KulkarniShashank * Added await by returning the ced defs Signed-off-by: KulkarniShashank * Discription changes on get cred-def list by schema Id Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../credential-definition.controller.ts | 28 ++++++++++++++++--- .../credential-definition.service.ts | 5 ++++ .../credential-definition.controller.ts | 7 ++++- .../credential-definition.service.ts | 12 +++++++- .../create-credential-definition.interface.ts | 4 +++ .../credential-definition.repository.ts | 14 ++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 6ce6c3d57..cbc1f1f4f 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -20,16 +20,16 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @ApiBearerAuth() @ApiTags('credential-definitions') +@Controller() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) -@Controller('orgs') @UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - @Get('/:orgId/cred-defs/:credDefId') + @Get('/orgs/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get an existing credential definition by Id', description: 'Get an existing credential definition by Id' @@ -51,7 +51,27 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Get('/:orgId/cred-defs') + @Get('/verifiation/cred-defs/:schemaId') + @ApiOperation({ + summary: 'Get an existing credential definitions by schema Id', + description: 'Get an existing credential definitions by schema Id' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + async getCredentialDefinitionBySchemaId( + @Param('schemaId') schemaId: string, + @Res() res: Response + ): Promise { + const credentialsDefinitions = await this.credentialDefinitionService.getCredentialDefinitionBySchemaId(schemaId); + const credDefResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.credentialDefinition.success.fetch, + data: credentialsDefinitions.response + }; + return res.status(HttpStatus.OK).json(credDefResponse); + } + + @Get('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions of provided organization id with pagination', description: 'Fetch all credential definitions from metadata saved in database of provided organization id.' @@ -95,7 +115,7 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Post('/:orgId/cred-defs') + @Post('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Sends a credential definition to ledger', description: 'Sends a credential definition to ledger' diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index b66b607f0..799bbed7f 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -28,4 +28,9 @@ export class CredentialDefinitionService extends BaseService { const payload = { credDefSearchCriteria, user, orgId }; return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } + + getCredentialDefinitionBySchemaId(schemaId: string): Promise<{ response: object }> { + const payload = { schemaId }; + return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions-by-schema-id', payload); + } } diff --git a/apps/ledger/src/credential-definition/credential-definition.controller.ts b/apps/ledger/src/credential-definition/credential-definition.controller.ts index 7ef0d638c..1f9aab2f3 100644 --- a/apps/ledger/src/credential-definition/credential-definition.controller.ts +++ b/apps/ledger/src/credential-definition/credential-definition.controller.ts @@ -4,7 +4,7 @@ import { Controller, Logger } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { MessagePattern } from '@nestjs/microservices'; -import { GetAllCredDefsPayload } from './interfaces/create-credential-definition.interface'; +import { GetAllCredDefsPayload, GetCredDefBySchemaId } from './interfaces/create-credential-definition.interface'; import { CreateCredDefPayload, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; @@ -45,4 +45,9 @@ export class CredentialDefinitionController { }> { return this.credDefService.getAllCredDefs(payload); } + + @MessagePattern({ cmd: 'get-all-credential-definitions-by-schema-id' }) + async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { + return this.credDefService.getCredentialDefinitionBySchemaId(payload); + } } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index 66341747f..201bda760 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -9,7 +9,7 @@ import { import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CredentialDefinitionRepository } from './repositories/credential-definition.repository'; -import { CreateCredDefPayload, CredDefPayload, GetAllCredDefsPayload, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; +import { CreateCredDefPayload, CredDefPayload, GetAllCredDefsPayload, GetCredDefBySchemaId, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CreateCredDefAgentRedirection, GetCredDefAgentRedirection } from './interfaces/credential-definition.interface'; @@ -256,4 +256,14 @@ export class CredentialDefinitionService extends BaseService { } } + async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { + try { + const { schemaId } = payload; + const credDefListBySchemaId = await this.credentialDefinitionRepository.getCredentialDefinitionBySchemaId(schemaId); + return credDefListBySchemaId; + } catch (error) { + this.logger.error(`Error in retrieving credential definitions: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts index ccefdcf37..0f574fe19 100644 --- a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts +++ b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts @@ -50,4 +50,8 @@ export interface GetAllCredDefsPayload { credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: number +} + +export interface GetCredDefBySchemaId { + schemaId: string } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts index 03690982e..12597749b 100644 --- a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts +++ b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts @@ -152,4 +152,18 @@ export class CredentialDefinitionRepository { throw error; } } + + async getCredentialDefinitionBySchemaId(schemaId: string): Promise { + try { + return this.prisma.credential_definition.findMany({ + where: { + schemaLedgerId: schemaId + } + + }); + } catch (error) { + this.logger.error(`Error in getting credential definitions: ${error}`); + throw error; + } + } } \ No newline at end of file From f9ba0220efd3a7c9113c3301851844131401c82d Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Thu, 19 Oct 2023 13:10:54 +0530 Subject: [PATCH 023/117] Request payload changes in the issuance Signed-off-by: KulkarniShashank --- apps/api-gateway/src/issuance/dtos/issuance.dto.ts | 2 +- apps/issuance/src/issuance.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index 51b6ce07c..d0acc517e 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -105,7 +105,7 @@ export class CredentialAttributes { export class OutOfBandCredentialDto { - @ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attribute': { 'value': 'string', 'name': 'string' } }] }) + @ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attribute': [{ 'value': 'string', 'name': 'string' }] }] }) @IsNotEmpty({ message: 'Please provide valid attributes' }) @IsArray({ message: 'attributes should be array' }) credentialOffer: CredentialOffer[]; diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 82a4643e4..34d7225dc 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -275,7 +275,7 @@ export class IssuanceService { protocolVersion: protocolVersion || 'v1', credentialFormats: { indy: { - attributes: [iterator.attribute], + attributes: iterator.attribute, credentialDefinitionId } }, From 0949e94fbdcf13de41ec483f4e0e1f4c66793991 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:46:28 +0530 Subject: [PATCH 024/117] Solved the credential-def bug in endorsement (#181) Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.service.ts | 47 ++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4a297d902..874ae6f4b 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -38,10 +38,10 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { - const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); - if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); - }; + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); if (!createEcosystem) { throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); @@ -105,11 +105,11 @@ export class EcosystemService { ecosystem: ecosystemDetails['ecosystem'], membersCount: endorseMemberCount.membersCount, endorsementsCount: endorseMemberCount.endorsementsCount, - ecosystemLead:{ - role: ecosystemDetails['ecosystemRole']['name'], - orgName: ecosystemDetails['orgName'], - config: endorseMemberCount.ecosystemConfigData - } + ecosystemLead: { + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['orgName'], + config: endorseMemberCount.ecosystemConfigData + } }; return dashboardDetails; @@ -187,9 +187,9 @@ export class EcosystemService { async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); - + if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); }; const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); @@ -281,12 +281,12 @@ export class EcosystemService { let isAcceptedInvitation = false; for (const invitation of invitations) { - if (invitation.status === Invitation.PENDING) { + if (invitation.status === Invitation.PENDING) { isPendingInvitation = true; - } - if (invitation.status === Invitation.ACCEPTED) { + } + if (invitation.status === Invitation.ACCEPTED) { isAcceptedInvitation = true; - } + } } if (isPendingInvitation || isAcceptedInvitation) { @@ -355,7 +355,7 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload:RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -425,7 +425,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload:RequestCredDeffEndorsement, orgId:number, ecosystemId:string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -478,6 +478,13 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + + if (!requestBody) { + throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); + } + + requestCredDefPayload["credentialDefinition"] = requestBody; const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -697,9 +704,9 @@ export class EcosystemService { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'], - type: endorsementTransactionPayload.requestBody['type'], - value: endorsementTransactionPayload.requestBody['value'] + schemaId: endorsementTransactionPayload.requestBody.credentialDefinition['schemaId'], + type: endorsementTransactionPayload.requestBody.credentialDefinition['type'], + value: endorsementTransactionPayload.requestBody.credentialDefinition['value'] }; } From 878af7b2c6276ccb67cff331f578900a4ee76727 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Sat, 21 Oct 2023 13:17:31 +0530 Subject: [PATCH 025/117] Bulk out of band issuance function Signed-off-by: KulkarniShashank --- .../src/verification/dto/request-proof.dto.ts | 6 +- apps/issuance/src/issuance.service.ts | 136 +++++++++------ .../src/interfaces/verification.interface.ts | 2 +- apps/verification/src/verification.module.ts | 4 +- apps/verification/src/verification.service.ts | 164 +++++++++--------- libs/common/src/response-messages/index.ts | 1 + 6 files changed, 177 insertions(+), 136 deletions(-) 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 7081a3f58..9260fe659 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -85,15 +85,15 @@ export class OutOfBandRequestProof { @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: ProofRequestAttribute[]; - @ApiProperty({ example: 'string' }) + @ApiProperty({ example: ['exmaple@example.com'] }) @IsNotEmpty({ message: 'Please provide valid emailId' }) @Transform(({ value }) => trim(value)) @Transform(({ value }) => toLowerCase(value)) @IsNotEmpty({ message: 'Email is required.' }) @MaxLength(256, { message: 'Email must be at most 256 character.' }) @IsEmail() - emailId: string; - + emailId: string[]; + @ApiProperty() @IsOptional() comment: string; diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 34d7225dc..da61c21d6 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ import { CommonService } from '@credebl/common'; -import { HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; import { IssuanceRepository } from './issuance.repository'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -250,6 +250,7 @@ export class IssuanceService { } } + async outOfBandCredentialOffer(user: IUserRequest, outOfBandCredential: OutOfBandCredentialOfferPayload): Promise { try { const { @@ -260,6 +261,9 @@ export class IssuanceService { protocolVersion } = outOfBandCredential; + // Define a batch size + const batchSize = 100; // Adjust this based on your needs + const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); @@ -268,75 +272,109 @@ export class IssuanceService { const issuanceMethodLabel = 'create-offer-oob'; const url = await this.getAgentUrl(issuanceMethodLabel, agentDetails.orgAgentTypeId, agentDetails.agentEndPoint, agentDetails.tenantId); const organizationDetails = await this.issuanceRepository.getOrganization(orgId); + + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.organizationNotFound); + } + const { apiKey } = agentDetails; - const emailPromises = credentialOffer.map(async (iterator) => { - const outOfBandIssuancePayload = { - protocolVersion: protocolVersion || 'v1', - credentialFormats: { - indy: { - attributes: iterator.attribute, - credentialDefinitionId + const errors = []; + const emailPromises = []; + + // Split the credentialOffer array into batches + for (let i = 0; i < credentialOffer.length; i += batchSize) { + const batch = credentialOffer.slice(i, i + batchSize); + + // Process each batch in parallel + const batchPromises = batch.map(async (iterator) => { + try { + const outOfBandIssuancePayload = { + protocolVersion: protocolVersion || 'v1', + credentialFormats: { + indy: { + attributes: iterator.attribute, + credentialDefinitionId + } + }, + autoAcceptCredential: 'always', + comment + }; + + const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); + + if (!credentialCreateOfferDetails) { + errors.push(ResponseMessages.issuance.error.credentialOfferNotFound); + return false; } - }, - autoAcceptCredential: 'always', - comment - }; - - const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); - if (!credentialCreateOfferDetails) { - throw new NotFoundException(ResponseMessages.issuance.error.credentialOfferNotFound); - } + const invitationId = credentialCreateOfferDetails.response.invitation['@id']; + if (!invitationId) { + errors.push(ResponseMessages.issuance.error.invitationNotFound); + return false; + } - const invitationId = credentialCreateOfferDetails.response.invitation['@id']; - if (!invitationId) { - throw new NotFoundException(ResponseMessages.issuance.error.invitationNotFound); - } + const agentEndPoint = agentDetails.tenantId + ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` + : `${agentDetails.agentEndPoint}/url/${invitationId}`; - const agentEndPoint = agentDetails.tenantId - ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` - : `${agentDetails.agentEndPoint}/url/${invitationId}`; + const qrCodeOptions = { type: 'image/png' }; + const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); + const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); - const qrCodeOptions = { type: 'image/png' }; - const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); - const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); + if (!platformConfigData) { + errors.push(ResponseMessages.issuance.error.platformConfigNotFound); + return false; + } - if (!platformConfigData) { - throw new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound); - } + this.emailData.emailFrom = platformConfigData.emailFrom; + this.emailData.emailTo = iterator.emailId; + this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credentials Required`; + this.emailData.emailHtml = await this.outOfBandIssuance.outOfBandIssuance(iterator.emailId, organizationDetails.name, outOfBandIssuanceQrCode); + this.emailData.emailAttachments = [ + { + filename: 'qrcode.png', + content: outOfBandIssuanceQrCode.split(';base64,')[1], + contentType: 'image/png', + disposition: 'attachment' + } + ]; + + const isEmailSent = await sendEmail(this.emailData); + if (!isEmailSent) { + errors.push(ResponseMessages.issuance.error.emailSend); + return false; + } - this.emailData.emailFrom = platformConfigData.emailFrom; - this.emailData.emailTo = iterator.emailId; - this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credentials Required`; - this.emailData.emailHtml = await this.outOfBandIssuance.outOfBandIssuance(iterator.emailId, organizationDetails.name, outOfBandIssuanceQrCode); - this.emailData.emailAttachments = [ - { - filename: 'qrcode.png', - content: outOfBandIssuanceQrCode.split(';base64,')[1], - contentType: 'image/png', - disposition: 'attachment' + return isEmailSent; + } catch (error) { + errors.push(error.message); + return false; } - ]; + }); - const isEmailSent = await sendEmail(this.emailData); - if (!isEmailSent) { - throw new InternalServerErrorException(ResponseMessages.issuance.error.emailSend); - } - return isEmailSent; - }); + emailPromises.push(Promise.all(batchPromises)); + } const results = await Promise.all(emailPromises); + // Flatten the results array + const flattenedResults = [].concat(...results); + // Check if all emails were successfully sent - return results.every((result) => true === result); + const allSuccessful = flattenedResults.every((result) => true === result); + + if (0 < errors.length) { + this.logger.error(errors); + } + + return allSuccessful; } catch (error) { this.logger.error(`[outOfBoundCredentialOffer] - error in create out-of-band credentials: ${JSON.stringify(error)}`); throw new RpcException(error); } } - async _outOfBandCredentialOffer(outOfBandIssuancePayload: object, url: string, apiKey: string): Promise<{ response; }> { diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 4f25db0f4..99ad821f7 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -16,7 +16,7 @@ export interface IRequestProof { comment: string; autoAcceptProof: string; protocolVersion: string; - emailId?: string + emailId?: string[] } export interface IGetAllProofPresentations { diff --git a/apps/verification/src/verification.module.ts b/apps/verification/src/verification.module.ts index 4b6ea652f..d42ce109b 100644 --- a/apps/verification/src/verification.module.ts +++ b/apps/verification/src/verification.module.ts @@ -5,6 +5,8 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonModule } from '@credebl/common'; import { VerificationRepository } from './repositories/verification.repository'; import { PrismaService } from '@credebl/prisma-service'; +import { OutOfBandVerification } from '../templates/out-of-band-verification.template'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; @Module({ imports: [ @@ -21,6 +23,6 @@ import { PrismaService } from '@credebl/prisma-service'; CommonModule ], controllers: [VerificationController], - providers: [VerificationService, VerificationRepository, PrismaService, Logger] + providers: [VerificationService, VerificationRepository, PrismaService, Logger, OutOfBandVerification, EmailDto] }) export class VerificationModule { } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index b7fbad1e0..b977eae72 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -21,7 +21,9 @@ export class VerificationService { constructor( @Inject('NATS_CLIENT') private readonly verificationServiceProxy: ClientProxy, - private readonly verificationRepository: VerificationRepository + private readonly verificationRepository: VerificationRepository, + private readonly outOfBandVerification: OutOfBandVerification, + private readonly emailData: EmailDto ) { } @@ -315,114 +317,112 @@ export class VerificationService { } /** - * Request out-of-band proof presentation - * @param outOfBandRequestProof - * @returns Get requested proof presentation details - */ + * Request out-of-band proof presentation + * @param outOfBandRequestProof + * @returns Get requested proof presentation details + */ async sendOutOfBandPresentationRequest(outOfBandRequestProof: IRequestProof): Promise { try { - const comment = outOfBandRequestProof.comment ? outOfBandRequestProof.comment : ''; - - let proofRequestPayload: ISendProofRequestPayload = { - protocolVersion: '', - comment: '', - proofFormats: { - indy: { - name: '', - requested_attributes: {}, - requested_predicates: {}, - version: '' - } - }, - autoAcceptProof: '' - }; + const comment = outOfBandRequestProof.comment || ''; + const protocolVersion = outOfBandRequestProof.protocolVersion || 'v1'; + const autoAcceptProof = outOfBandRequestProof.autoAcceptProof || 'never'; const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(outOfBandRequestProof); - proofRequestPayload = { - protocolVersion: outOfBandRequestProof.protocolVersion ? outOfBandRequestProof.protocolVersion : 'v1', - comment, - 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: outOfBandRequestProof.autoAcceptProof ? outOfBandRequestProof.autoAcceptProof : 'never' - }; - const getAgentDetails = await this.verificationRepository.getAgentEndPoint(outOfBandRequestProof.orgId); - const organizationDetails = await this.verificationRepository.getOrganization(outOfBandRequestProof.orgId); + const [getAgentDetails, organizationDetails] = await Promise.all([ + this.verificationRepository.getAgentEndPoint(outOfBandRequestProof.orgId), + this.verificationRepository.getOrganization(outOfBandRequestProof.orgId) + ]); const verificationMethodLabel = 'create-request-out-of-band'; const url = await this.getAgentUrl(verificationMethodLabel, getAgentDetails?.orgAgentTypeId, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); - const payload = { apiKey: '', url, proofRequestPayload }; + const payload = { + apiKey: '', + url, + proofRequestPayload: { + protocolVersion, + comment, + proofFormats: { + indy: { + name: 'Proof Request', + version: '1.0', + requested_attributes: requestedAttributes, + requested_predicates: requestedPredicates + } + }, + autoAcceptProof + } + }; - const getProofPresentation = await this._sendOutOfBandProofRequest(payload); + const emailPromises = outOfBandRequestProof.emailId.map(async email => { + return this.sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails); + }); - if (!getProofPresentation) { - throw new NotFoundException(ResponseMessages.verification.error.proofPresentationNotFound); + const results = await Promise.all(emailPromises); + + // Check if any email sending failed + if (results.some(result => !result)) { + throw new Error(ResponseMessages.verification.error.emailSend); } - const invitationId = getProofPresentation?.response?.invitation['@id']; + return true; + } catch (error) { + this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${error.message}`); + throw new RpcException(error.message); + } + } - if (!invitationId) { - throw new NotFoundException(ResponseMessages.verification.error.invitationNotFound); - } + async sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails): Promise { + const getProofPresentation = await this._sendOutOfBandProofRequest(payload); - let shortenedUrl; - if (getAgentDetails?.tenantId) { - shortenedUrl = `${getAgentDetails?.agentEndPoint}/multi-tenancy/url/${getAgentDetails?.tenantId}/${invitationId}`; - } else { - shortenedUrl = `${getAgentDetails?.agentEndPoint}/url/${invitationId}`; - } + if (!getProofPresentation) { + throw new Error(ResponseMessages.verification.error.proofPresentationNotFound); + } - const uniqueCID = uuid.v4(); + const invitationId = getProofPresentation?.response?.invitation['@id']; - const qrCodeOptions: QRCode.QRCodeToDataURLOptions = { - type: 'image/png' - }; + if (!invitationId) { + throw new Error(ResponseMessages.verification.error.invitationNotFound); + } - const outOfBandIssuanceQrCode = await QRCode.toDataURL(shortenedUrl, qrCodeOptions); - const platformConfigData = await this.verificationRepository.getPlatformConfigDetails(); + const shortenedUrl = getAgentDetails?.tenantId + ? `${getAgentDetails?.agentEndPoint}/multi-tenancy/url/${getAgentDetails?.tenantId}/${invitationId}` + : `${getAgentDetails?.agentEndPoint}/url/${invitationId}`; - if (!platformConfigData) { - throw new NotFoundException(ResponseMessages.verification.error.platformConfigNotFound); - } + const uniqueCID = uuid.v4(); + const qrCodeOptions: QRCode.QRCodeToDataURLOptions = { type: 'image/png' }; + const outOfBandIssuanceQrCode = await QRCode.toDataURL(shortenedUrl, qrCodeOptions); - const outOfBandVerification = new OutOfBandVerification(); - const emailData = new EmailDto(); - emailData.emailFrom = platformConfigData.emailFrom; - emailData.emailTo = outOfBandRequestProof.emailId; - emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Verification of Your Credentials Required`; - emailData.emailHtml = await outOfBandVerification.outOfBandVerification(outOfBandRequestProof.emailId, uniqueCID, organizationDetails.name); - emailData.emailAttachments = [ - { - filename: 'qrcode.png', - content: outOfBandIssuanceQrCode.split(';base64,')[1], - contentType: 'image/png', - disposition: 'attachment' - } - ]; - const isEmailSent = await sendEmail(emailData); + const platformConfigData = await this.verificationRepository.getPlatformConfigDetails(); - if (isEmailSent) { - return isEmailSent; - } else { - throw new InternalServerErrorException(ResponseMessages.verification.error.emailSend); + if (!platformConfigData) { + throw new Error(ResponseMessages.verification.error.platformConfigNotFound); + } + + this.emailData.emailFrom = platformConfigData.emailFrom; + this.emailData.emailTo = email; + this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Verification of Your Credentials Required`; + this.emailData.emailHtml = await this.outOfBandVerification.outOfBandVerification(email, uniqueCID, organizationDetails.name); + this.emailData.emailAttachments = [ + { + filename: 'qrcode.png', + content: outOfBandIssuanceQrCode.split(';base64,')[1], + contentType: 'image/png', + disposition: 'attachment' } + ]; + const isEmailSent = await sendEmail(this.emailData); - } catch (error) { - this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); + if (!isEmailSent) { + throw new Error(ResponseMessages.verification.error.emailSend); } + + return isEmailSent; } + /** * Consume agent API for request out-of-band proof presentation * @param payload diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 2f4e7e6e6..cc858c5b0 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -160,6 +160,7 @@ export const ResponseMessages = { exists: 'Credentials is already exist', credentialsNotFound: 'Credentials not found', agentEndPointNotFound: 'agentEndPoint Not Found', + organizationNotFound: 'organization Not Found', agentUrlNotFound: 'agent url not found', notFound: 'Organization agent not found', credentialOfferNotFound: "Credential offer not found", From 64b81118b1e7f8e3433a9a68ecdf9c05fec94836 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Sat, 21 Oct 2023 14:47:09 +0530 Subject: [PATCH 026/117] Out of band verification with bulk emailIds Signed-off-by: KulkarniShashank --- .../src/verification/dto/request-proof.dto.ts | 9 +---- apps/verification/src/verification.service.ts | 37 +++++++++++++------ 2 files changed, 28 insertions(+), 18 deletions(-) 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 9260fe659..8330f7a9e 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; +import { IsArray, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; @@ -86,12 +86,7 @@ export class OutOfBandRequestProof { attributes: ProofRequestAttribute[]; @ApiProperty({ example: ['exmaple@example.com'] }) - @IsNotEmpty({ message: 'Please provide valid emailId' }) - @Transform(({ value }) => trim(value)) - @Transform(({ value }) => toLowerCase(value)) - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) - @IsEmail() + @IsArray({ message: 'emailId should be array' }) emailId: string[]; @ApiProperty() diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index b977eae72..4dead2219 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -356,17 +356,9 @@ export class VerificationService { } }; - const emailPromises = outOfBandRequestProof.emailId.map(async email => { - return this.sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails); - }); - - const results = await Promise.all(emailPromises); - - // Check if any email sending failed - if (results.some(result => !result)) { - throw new Error(ResponseMessages.verification.error.emailSend); - } - + const batchSize = 100; // Define the batch size according to your needs + const { emailId } = outOfBandRequestProof; // Assuming it's an array + await this.sendEmailInBatches(payload, emailId, getAgentDetails, organizationDetails, batchSize); return true; } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${error.message}`); @@ -374,6 +366,29 @@ export class VerificationService { } } + async sendEmailInBatches(payload, emailIds, getAgentDetails, organizationDetails, batchSize): Promise { + const accumulatedErrors = []; + + for (let i = 0; i < emailIds.length; i += batchSize) { + const batch = emailIds.slice(i, i + batchSize); + const emailPromises = batch.map(async email => { + try { + await this.sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails); + } catch (error) { + accumulatedErrors.push(error); + } + }); + + await Promise.all(emailPromises); + } + + if (0 < accumulatedErrors.length) { + this.logger.error(accumulatedErrors); + throw new Error(ResponseMessages.verification.error.emailSend); + } + } + + async sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails): Promise { const getProofPresentation = await this._sendOutOfBandProofRequest(payload); From 0affdf13a51473412e04354f7e2d33fd32a66e49 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 19 Oct 2023 20:41:04 +0530 Subject: [PATCH 027/117] fixed ecosystem invitations (#182) Signed-off-by: Nishad Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 874ae6f4b..57e4368e8 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -161,8 +161,8 @@ export class EcosystemService { const isUserExist = await this.checkUserExistInPlatform(email); const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); - - if (!isInvitationExist && userEmail === invitation.email) { + + if (!isInvitationExist && userEmail !== invitation.email) { await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); try { await this.sendInviteEmailTemplate(email, ecosystemDetails.name, isUserExist); From 52fcdc55d259bab7aef04292556801340a28f3f3 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Sat, 21 Oct 2023 13:37:47 +0530 Subject: [PATCH 028/117] Support indicio:demonet and indicio:testnet Signed-off-by: KulkarniShashank --- .../src/agent-service.service.ts | 9 +++++---- apps/agent-service/src/main.ts | 2 +- .../prisma/data/credebl-master-table.json | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 842d4cada..a8679c2a1 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -338,10 +338,11 @@ export class AgentServiceService { const agentDidWriteUrl = `${payload.agentEndPoint}${CommonConstants.URL_AGENT_WRITE_DID}`; - const { seed } = payload; + const { seed, ledgerId } = payload; const { apiKey } = payload; const writeDid = 'write-did'; - const agentDid = await this._retryAgentSpinup(agentDidWriteUrl, apiKey, writeDid, seed); + const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(ledgerId); + const agentDid = await this._retryAgentSpinup(agentDidWriteUrl, apiKey, writeDid, seed, ledgerDetails[0].indyNamespace); if (agentDid) { const getDidMethodUrl = `${payload.agentEndPoint}${CommonConstants.URL_AGENT_GET_DIDS}`; @@ -388,14 +389,14 @@ export class AgentServiceService { } } - async _retryAgentSpinup(agentUrl: string, apiKey: string, agentApiState: string, seed?: string): Promise { + async _retryAgentSpinup(agentUrl: string, apiKey: string, agentApiState: string, seed?: string, indyNamespace?: string): Promise { return retry( async () => { if ('write-did' === agentApiState) { const agentDid = await this.commonService - .httpPost(agentUrl, { seed }, { headers: { 'x-api-key': apiKey } }) + .httpPost(agentUrl, { seed, method: indyNamespace }, { headers: { 'x-api-key': apiKey } }) .then(async response => response); return agentDid; } else if ('get-did-doc' === agentApiState) { diff --git a/apps/agent-service/src/main.ts b/apps/agent-service/src/main.ts index 53d5fbba0..434d62bf4 100644 --- a/apps/agent-service/src/main.ts +++ b/apps/agent-service/src/main.ts @@ -28,7 +28,7 @@ async function bootstrap(): Promise { seed: process.env.PLATFORM_SEED, orgId: parseInt(process.env.PLATFORM_ID), tenant: true, - ledgerId: [1, 2] + ledgerId: [1, 2, 3, 4] }; const agentService = app.get(AgentServiceService); diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 23bbb73b2..a498c5929 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -106,6 +106,26 @@ "registerDIDEndpoint": "https://selfserve.indiciotech.io/nym", "registerDIDPayload": "", "indyNamespace": "indicio:testnet" + }, + { + "name": "Indicio Demonet", + "networkType": "demonet", + "poolConfig": "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_demonet_genesis", + "isActive": true, + "networkString": "demonet", + "registerDIDEndpoint": "https://selfserve.indiciotech.io/nym", + "registerDIDPayload": "", + "indyNamespace": "indicio:demonet" + }, + { + "name": "Indicio Mainnet", + "networkType": "mainnet", + "poolConfig": "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_mainnet_genesis", + "isActive": true, + "networkString": "mainnet", + "registerDIDEndpoint": "https://selfserve.indiciotech.io/nym", + "registerDIDPayload": "", + "indyNamespace": "indicio:mainnet" } ], "endorseData": [ From 3ee0eecfe874190196d5322804e2ae19a8d36c69 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Mon, 23 Oct 2023 09:12:00 +0530 Subject: [PATCH 029/117] Add API for Organization profile Signed-off-by: KulkarniShashank --- .../organization/organization.controller.ts | 13 +++++++++++ .../src/organization/organization.service.ts | 8 +++++++ apps/connection/src/connection.repository.ts | 23 +++++++++++++++++-- apps/connection/src/connection.service.ts | 4 ++-- .../repositories/organization.repository.ts | 14 +++++++++++ .../src/organization.controller.ts | 5 ++++ apps/organization/src/organization.service.ts | 21 ++++++++++++----- 7 files changed, 78 insertions(+), 10 deletions(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index a4a796895..9ac60d335 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -41,6 +41,19 @@ export class OrganizationController { private readonly commonService: CommonService ) { } + @Get('/profile/:orgId') + @ApiOperation({ summary: 'Organization Profile', description: 'Update an organization' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + async getOgPofile(@Param('orgId') orgId: number, @Res() res: Response): Promise { + const orgProfile = await this.organizationService.getOgPofile(orgId); + + const base64Data = orgProfile.response["logoUrl"].replace(/^data:image\/\w+;base64,/, ''); + + const imageBuffer = Buffer.from(base64Data, 'base64'); + res.setHeader('Content-Type', 'image/png'); + return res.send(imageBuffer); + } + /** * * @param user diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index 2af36e772..4dcfce609 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -134,4 +134,12 @@ export class OrganizationService extends BaseService { return this.sendNats(this.serviceProxy, 'fetch-organization-user', payload); } + + async getOgPofile( + orgId: number + ): Promise<{ response: object }> { + const payload = { orgId }; + + return this.sendNats(this.serviceProxy, 'fetch-organization-profile', payload); + } } diff --git a/apps/connection/src/connection.repository.ts b/apps/connection/src/connection.repository.ts index d8f7e8ee9..014510354 100644 --- a/apps/connection/src/connection.repository.ts +++ b/apps/connection/src/connection.repository.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { agent_invitations, connections, org_agents, platform_config, shortening_url } from '@prisma/client'; +import { agent_invitations, connections, organisation, platform_config, shortening_url } from '@prisma/client'; @Injectable() export class ConnectionRepository { @@ -16,12 +16,31 @@ export class ConnectionRepository { * @returns Get getAgentEndPoint details */ // eslint-disable-next-line camelcase - async getAgentEndPoint(orgId: number): Promise { + async getAgentEndPoint(orgId: number): Promise<{ + organisation: organisation; + } & { + id: number; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; + orgDid: string; + verkey: string; + agentEndPoint: string; + agentId: number; + isDidPublic: boolean; + ledgerId: number; + orgAgentTypeId: number; + tenantId: string; + }> { try { const agentDetails = await this.prisma.org_agents.findFirst({ where: { orgId + }, + include: { + organisation: true } }); return agentDetails; diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 78b149be1..9ca907bac 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -46,7 +46,7 @@ export class ConnectionService { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const platformConfig: platform_config = await this.connectionRepository.getPlatformConfigDetails(); - const { agentEndPoint, id } = agentDetails; + const { agentEndPoint, id, organisation } = agentDetails; const agentId = id; if (!agentDetails) { throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); @@ -56,7 +56,7 @@ export class ConnectionService { multiUseInvitation: multiUseInvitation || true, autoAcceptConnection: autoAcceptConnection || true, alias: alias || undefined, - imageUrl: imageUrl || undefined, + imageUrl: organisation.logoUrl || undefined, label: label || undefined }; diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 4ad0bd967..a847a6527 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -433,4 +433,18 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } + + async getOrgProfile(id: number): Promise { + try { + return this.prisma.organisation.findUnique({ + where: { + id + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + } diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 6128c1592..240a8ccd7 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -153,4 +153,9 @@ export class OrganizationController { async getOrgDashboard(payload: { orgId: number; userId: number }): Promise { return this.organizationService.getOrgDashboard(payload.orgId); } + + @MessagePattern({ cmd: 'fetch-organization-profile' }) + async getOgPofile(payload: { orgId: number }): Promise { + return this.organizationService.getOgPofile(payload.orgId); + } } diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 55ec0e538..2badf27cb 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -277,13 +277,13 @@ export class OrganizationService { let isAcceptedInvitation = false; for (const invitation of invitations) { - if (invitation.status === Invitation.PENDING) { + if (invitation.status === Invitation.PENDING) { isPendingInvitation = true; - } - if (invitation.status === Invitation.ACCEPTED) { + } + if (invitation.status === Invitation.ACCEPTED) { isAcceptedInvitation = true; - } - } + } + } if (isPendingInvitation || isAcceptedInvitation) { return true; @@ -314,7 +314,7 @@ export class OrganizationService { const isUserExist = await this.checkUserExistInPlatform(email); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + const isInvitationExist = await this.checkInvitationExist(email, orgId); if (!isInvitationExist && userEmail !== invitation.email) { @@ -480,4 +480,13 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } + + async getOgPofile(orgId: number): Promise { + try { + return this.organizationRepository.getOrgProfile(orgId); + } catch (error) { + this.logger.error(`get organization profile : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } } \ No newline at end of file From e6403c38c53d2e86ea3c654ec0a22e6862de146e Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Mon, 23 Oct 2023 09:29:10 +0530 Subject: [PATCH 030/117] Error handling in the org profile Signed-off-by: KulkarniShashank --- apps/organization/src/organization.service.ts | 6 +++++- libs/common/src/response-messages/index.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 2badf27cb..b1f887f18 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -483,7 +483,11 @@ export class OrganizationService { async getOgPofile(orgId: number): Promise { try { - return this.organizationRepository.getOrgProfile(orgId); + const orgProfile = await this.organizationRepository.getOrgProfile(orgId); + if (!orgProfile.logoUrl || '' === orgProfile.logoUrl) { + throw new NotFoundException(ResponseMessages.organisation.error.orgProfile); + } + return orgProfile; } catch (error) { this.logger.error(`get organization profile : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index cc858c5b0..be5dd0ce9 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -54,6 +54,7 @@ export const ResponseMessages = { exists: 'An organization name is already exist', profileNotFound: 'Organization public profile not found', rolesNotExist: 'Provided roles not exists in the platform', + orgProfile: 'Organization profile not found', userNotFound: 'User not found for the given organization', updateUserRoles: 'Unable to update user roles' } From 62e775cd2faf3e5698708848f952971ae2234c2b Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Mon, 23 Oct 2023 11:38:29 +0530 Subject: [PATCH 031/117] Connection image url add Signed-off-by: KulkarniShashank --- apps/connection/src/connection.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 9ca907bac..a35c55cb1 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -52,11 +52,16 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); } + let logoImageUrl; + if (organisation.logoUrl) { + logoImageUrl = `${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_ENDPOINT}/orgs/profile/${organisation.id}`; + } + const connectionPayload = { multiUseInvitation: multiUseInvitation || true, autoAcceptConnection: autoAcceptConnection || true, alias: alias || undefined, - imageUrl: organisation.logoUrl || undefined, + imageUrl: logoImageUrl ? logoImageUrl : '', label: label || undefined }; From 919234b15e6f46c294b10246368ed395f84dadeb Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Mon, 23 Oct 2023 12:12:58 +0530 Subject: [PATCH 032/117] Orgnization logo setup in the connection service Signed-off-by: KulkarniShashank --- apps/agent-service/src/agent-service.service.ts | 4 ++-- apps/connection/src/connection.service.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index a8679c2a1..5486b4f20 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -133,7 +133,7 @@ export class AgentServiceService { agentSpinupDto.agentType = agentSpinupDto.agentType ? agentSpinupDto.agentType : 1; agentSpinupDto.tenant = agentSpinupDto.tenant ? agentSpinupDto.tenant : false; - agentSpinupDto.ledgerId = !agentSpinupDto.ledgerId || 0 === agentSpinupDto.ledgerId.length ? [1] : agentSpinupDto.ledgerId; + agentSpinupDto.ledgerId = !agentSpinupDto.ledgerId || 0 === agentSpinupDto.ledgerId.length ? [3] : agentSpinupDto.ledgerId; const platformConfig: platform_config = await this.agentServiceRepository.getPlatformConfigDetails(); @@ -487,7 +487,7 @@ export class AgentServiceService { async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise { try { - payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId.length ? [1] : payload.ledgerId; + payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId.length ? [3] : payload.ledgerId; const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(payload.ledgerId); const sharedAgentSpinUpResponse = new Promise(async (resolve, _reject) => { diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index a35c55cb1..ce5d0d8e0 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -57,6 +57,7 @@ export class ConnectionService { logoImageUrl = `${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_ENDPOINT}/orgs/profile/${organisation.id}`; } + this.logger.log(`logoImageUrl ::: ${logoImageUrl}`); const connectionPayload = { multiUseInvitation: multiUseInvitation || true, autoAcceptConnection: autoAcceptConnection || true, From 0f29ee820c99d58aa2153dc3a43ea33bcb8bdbd1 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Mon, 23 Oct 2023 15:51:30 +0530 Subject: [PATCH 033/117] Added the emailId and attribute in the issuance oob payload Signed-off-by: KulkarniShashank --- .../src/issuance/dtos/issuance.dto.ts | 18 ++- .../interfaces/issuance.interfaces.ts | 8 +- apps/issuance/src/issuance.service.ts | 132 ++++++++++-------- 3 files changed, 91 insertions(+), 67 deletions(-) diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index d0acc517e..26defee28 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,9 +1,9 @@ -import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString, IsEmail } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; interface CredentialOffer { emailId: string; - attribute: Attribute[]; + attributes: Attribute[]; } interface Attribute { @@ -108,8 +108,22 @@ export class OutOfBandCredentialDto { @ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attribute': [{ 'value': 'string', 'name': 'string' }] }] }) @IsNotEmpty({ message: 'Please provide valid attributes' }) @IsArray({ message: 'attributes should be array' }) + @IsOptional() credentialOffer: CredentialOffer[]; + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail() + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + @IsOptional() + emailId: string; + + @ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] }) + @IsNotEmpty({ message: 'Please provide valid attributes' }) + @IsArray({ message: 'attributes should be array' }) + @IsOptional() + attributes: Attribute[]; + @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid credential definition id' }) @IsString({ message: 'credential definition id should be string' }) diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index fd255e8d3..ee72abbb7 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -47,14 +47,16 @@ export interface ICredentialAttributesInterface { export interface CredentialOffer { emailId: string; - attribute: Attributes[]; + attributes: Attributes[]; } export interface OutOfBandCredentialOfferPayload { - credentialOffer: CredentialOffer[]; credentialDefinitionId: string; comment: string; - protocolVersion?: string; orgId: number; + credentialOffer?: CredentialOffer[]; + emailId?: string; + attributes?: Attributes[]; + protocolVersion?: string; } export interface OutOfBandCredentialOffer { diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index da61c21d6..463b4dbb0 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -258,7 +258,9 @@ export class IssuanceService { comment, credentialDefinitionId, orgId, - protocolVersion + protocolVersion, + attributes, + emailId } = outOfBandCredential; // Define a batch size @@ -282,78 +284,83 @@ export class IssuanceService { const errors = []; const emailPromises = []; - // Split the credentialOffer array into batches - for (let i = 0; i < credentialOffer.length; i += batchSize) { - const batch = credentialOffer.slice(i, i + batchSize); - - // Process each batch in parallel - const batchPromises = batch.map(async (iterator) => { - try { - const outOfBandIssuancePayload = { - protocolVersion: protocolVersion || 'v1', - credentialFormats: { - indy: { - attributes: iterator.attribute, - credentialDefinitionId - } - }, - autoAcceptCredential: 'always', - comment - }; - - const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); - - if (!credentialCreateOfferDetails) { - errors.push(ResponseMessages.issuance.error.credentialOfferNotFound); - return false; - } + const sendEmailForCredentialOffer = async (iterator, emailId): Promise => { + try { + const outOfBandIssuancePayload = { + protocolVersion: protocolVersion || 'v1', + credentialFormats: { + indy: { + attributes: iterator.attributes || attributes, + credentialDefinitionId + } + }, + autoAcceptCredential: 'always', + comment + }; - const invitationId = credentialCreateOfferDetails.response.invitation['@id']; - if (!invitationId) { - errors.push(ResponseMessages.issuance.error.invitationNotFound); - return false; - } - const agentEndPoint = agentDetails.tenantId - ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` - : `${agentDetails.agentEndPoint}/url/${invitationId}`; + const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); + if (!credentialCreateOfferDetails) { + errors.push(ResponseMessages.issuance.error.credentialOfferNotFound); + return false; + } - const qrCodeOptions = { type: 'image/png' }; - const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); - const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); + const invitationId = credentialCreateOfferDetails.response.invitation['@id']; + if (!invitationId) { + errors.push(ResponseMessages.issuance.error.invitationNotFound); + return false; + } - if (!platformConfigData) { - errors.push(ResponseMessages.issuance.error.platformConfigNotFound); - return false; - } + const agentEndPoint = agentDetails.tenantId + ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` + : `${agentDetails.agentEndPoint}/url/${invitationId}`; - this.emailData.emailFrom = platformConfigData.emailFrom; - this.emailData.emailTo = iterator.emailId; - this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credentials Required`; - this.emailData.emailHtml = await this.outOfBandIssuance.outOfBandIssuance(iterator.emailId, organizationDetails.name, outOfBandIssuanceQrCode); - this.emailData.emailAttachments = [ - { - filename: 'qrcode.png', - content: outOfBandIssuanceQrCode.split(';base64,')[1], - contentType: 'image/png', - disposition: 'attachment' - } - ]; + const qrCodeOptions = { type: 'image/png' }; + const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); + const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); - const isEmailSent = await sendEmail(this.emailData); - if (!isEmailSent) { - errors.push(ResponseMessages.issuance.error.emailSend); - return false; + if (!platformConfigData) { + errors.push(ResponseMessages.issuance.error.platformConfigNotFound); + return false; + } + + this.emailData.emailFrom = platformConfigData.emailFrom; + this.emailData.emailTo = emailId; + this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credentials Required`; + this.emailData.emailHtml = await this.outOfBandIssuance.outOfBandIssuance(emailId, organizationDetails.name, outOfBandIssuanceQrCode); + this.emailData.emailAttachments = [ + { + filename: 'qrcode.png', + content: outOfBandIssuanceQrCode.split(';base64,')[1], + contentType: 'image/png', + disposition: 'attachment' } + ]; - return isEmailSent; - } catch (error) { - errors.push(error.message); + const isEmailSent = await sendEmail(this.emailData); + if (!isEmailSent) { + errors.push(ResponseMessages.issuance.error.emailSend); return false; } - }); - emailPromises.push(Promise.all(batchPromises)); + return isEmailSent; + } catch (error) { + errors.push(error.message); + return false; + } + }; + + if (credentialOffer) { + for (let i = 0; i < credentialOffer.length; i += batchSize) { + const batch = credentialOffer.slice(i, i + batchSize); + + // Process each batch in parallel + const batchPromises = batch.map((iterator) => sendEmailForCredentialOffer(iterator, iterator.emailId)); + + emailPromises.push(Promise.all(batchPromises)); + } + } else { + emailPromises.push(sendEmailForCredentialOffer({}, emailId)); } const results = await Promise.all(emailPromises); @@ -375,6 +382,7 @@ export class IssuanceService { } } + async _outOfBandCredentialOffer(outOfBandIssuancePayload: object, url: string, apiKey: string): Promise<{ response; }> { From 2249606a398a06a4d916e57d184f0d4c0c7d7f0a Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Wed, 25 Oct 2023 14:53:30 +0530 Subject: [PATCH 034/117] Added the API for get oob verification qr code Signed-off-by: KulkarniShashank --- .../src/issuance/issuance.controller.ts | 13 ++++++ .../src/verification/dto/request-proof.dto.ts | 6 +-- .../verification/verification.controller.ts | 20 ++++++++- .../out-of-band-issuance.template.ts | 2 +- apps/verification/src/verification.service.ts | 42 ++++++++++--------- .../out-of-band-verification.template.ts | 3 +- 6 files changed, 61 insertions(+), 25 deletions(-) diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index beb58488b..28430fa1f 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -57,6 +57,19 @@ export class IssuanceController { ) { } private readonly logger = new Logger('IssuanceController'); + @Get('/issuance/oob/qr/:base64Image') + @ApiOperation({ summary: 'Out-Of-Band issuance QR', description: 'Out-Of-Band issuance QR' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiExcludeEndpoint() + async getOgPofile(@Param('base64Image') base64Image: string, @Res() res: Response): Promise { + + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ''); + + const imageBuffer = Buffer.from(base64Data, 'base64'); + res.setHeader('Content-Type', 'image/png'); + return res.send(imageBuffer); + } + /** * Description: Get all issued credentials * @param user 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 8330f7a9e..c991ba434 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -85,9 +85,9 @@ export class OutOfBandRequestProof { @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: ProofRequestAttribute[]; - @ApiProperty({ example: ['exmaple@example.com'] }) - @IsArray({ message: 'emailId should be array' }) - emailId: string[]; + @ApiProperty() + @IsString({ each: true, message: 'Each emailId in the array should be a string' }) + emailId: string | string[]; @ApiProperty() @IsOptional() diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index f9f555c52..c256db92c 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -31,7 +31,6 @@ import { WebhookPresentationProof } from './dto/webhook-proof.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@ApiBearerAuth() @Controller() @ApiTags('verifications') export class VerificationController { @@ -39,6 +38,19 @@ export class VerificationController { private readonly logger = new Logger('VerificationController'); + @Get('/verification/oob/qr/:base64Image') + @ApiOperation({ summary: 'Out-Of-Band issuance QR', description: 'Out-Of-Band issuance QR' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiExcludeEndpoint() + async getOgPofile(@Param('base64Image') base64Image: string, @Res() res: Response): Promise { + + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ''); + + const imageBuffer = Buffer.from(base64Data, 'base64'); + res.setHeader('Content-Type', 'image/png'); + return res.send(imageBuffer); + } + @Get('/orgs/:orgId/proofs/:proofId/form') @ApiOperation({ summary: `Get a proof form data`, @@ -46,6 +58,7 @@ export class VerificationController { }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -81,6 +94,7 @@ export class VerificationController { @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() async getProofPresentationById( @Res() res: Response, @GetUser() user: IUserRequest, @@ -113,6 +127,7 @@ export class VerificationController { @ApiQuery( { name: 'threadId', required: false } ) + @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getProofPresentations( @@ -145,6 +160,7 @@ export class VerificationController { @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBody({ type: RequestProof }) + @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) async sendPresentationRequest( @@ -184,6 +200,7 @@ export class VerificationController { @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async verifyPresentation( @Res() res: Response, @@ -216,6 +233,7 @@ export class VerificationController { @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBody({ type: OutOfBandRequestProof }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async sendOutOfBandPresentationRequest( @Res() res: Response, diff --git a/apps/issuance/templates/out-of-band-issuance.template.ts b/apps/issuance/templates/out-of-band-issuance.template.ts index 6031fb0b2..d877cdb6f 100644 --- a/apps/issuance/templates/out-of-band-issuance.template.ts +++ b/apps/issuance/templates/out-of-band-issuance.template.ts @@ -36,7 +36,7 @@ export class OutOfBandIssuance { Should you require any assistance or have questions, feel free to contact our dedicated support team.

- QR Code + QR Code