diff --git a/services/credentials-service/src/credentials/credentials.module.ts b/services/credentials-service/src/credentials/credentials.module.ts index bd62d4435..c83417126 100644 --- a/services/credentials-service/src/credentials/credentials.module.ts +++ b/services/credentials-service/src/credentials/credentials.module.ts @@ -5,13 +5,14 @@ import { HttpModule, HttpService } from '@nestjs/axios'; import { IdentityUtilsService } from './utils/identity.utils.service'; import { RenderingUtilsService } from './utils/rendering.utils.service'; import { SchemaUtilsSerivce } from './utils/schema.utils.service'; -import {AnchorCordUtilsServices} from './utils/cord.utils.service' +import { BlockchainAnchorFactory } from './factories/blockchain-anchor.factory'; +import { AnchorCordService } from './implementations/anchor-cord.service'; import { PrismaClient } from '@prisma/client'; @Module({ imports: [HttpModule], - providers: [ CredentialsService, PrismaClient, IdentityUtilsService, RenderingUtilsService, SchemaUtilsSerivce,AnchorCordUtilsServices], + providers: [CredentialsService, PrismaClient, IdentityUtilsService, RenderingUtilsService, SchemaUtilsSerivce, BlockchainAnchorFactory, AnchorCordService], controllers: [CredentialsController], exports: [IdentityUtilsService] }) -export class CredentialsModule {} +export class CredentialsModule { } diff --git a/services/credentials-service/src/credentials/credentials.service.ts b/services/credentials-service/src/credentials/credentials.service.ts index d84498402..257323023 100644 --- a/services/credentials-service/src/credentials/credentials.service.ts +++ b/services/credentials-service/src/credentials/credentials.service.ts @@ -13,7 +13,8 @@ import { JwtCredentialSubject } from 'src/app.interface'; import { SchemaUtilsSerivce } from './utils/schema.utils.service'; import { IdentityUtilsService } from './utils/identity.utils.service'; import { RenderingUtilsService } from './utils/rendering.utils.service'; -import { AnchorCordUtilsServices } from './utils/cord.utils.service'; +import { BlockchainAnchorFactory } from './factories/blockchain-anchor.factory'; + import * as jsigs from 'jsonld-signatures'; import * as jsonld from 'jsonld'; import { DOCUMENTS } from './documents'; @@ -43,7 +44,7 @@ export class CredentialsService { private readonly identityUtilsService: IdentityUtilsService, private readonly renderingUtilsService: RenderingUtilsService, private readonly schemaUtilsService: SchemaUtilsSerivce, - private readonly anchorCordUtilsServices: AnchorCordUtilsServices + private readonly blockchainFactory: BlockchainAnchorFactory ) { this.init(); } @@ -150,9 +151,14 @@ export class CredentialsService { async verifyCredential(credToVerify: Verifiable, status?: VCStatus) { try { - // If ANCHOR_TO_CORD is true, delegate verification to Cord Verification MiddleWare service - if (this.shouldAnchorToCord()) { - return await this.anchorCordUtilsServices.verifyCredentialOnCord(credToVerify); + // Check if anchoring to blockchain is enabled and get the method + const method = this.shouldAnchorToBlockchain(); + + if (method) { + // Get the appropriate service from the factory + const anchorService = this.blockchainFactory.getAnchorService(method); + // delegate verification to appropriate service + return await anchorService.verifyCredential(credToVerify); } // calling identity service to verify the issuer DID const issuerId = (credToVerify.issuer?.id || credToVerify.issuer) as string; @@ -278,9 +284,15 @@ export class CredentialsService { let response: any = null; + // Check if anchoring to blockchain is enabled and get the method + const method = this.shouldAnchorToBlockchain(); + // Check if ANCHOR_TO_CORD is true - if (this.shouldAnchorToCord()) { - response = await this.anchorCredentialToCord(credInReq, issueRequest); + if (method) { + // Get the appropriate service from the factory + const anchorService = this.blockchainFactory.getAnchorService(method); + const anchoredCredentialData = await anchorService.anchorCredential(issueRequest); + response = this.saveCredentialToDatabase(anchoredCredentialData) } else { // Check for issuance date if (!credInReq.issuanceDate) { @@ -322,60 +334,36 @@ export class CredentialsService { return response; } - /** - * Determines if ANCHOR_TO_CORD environment variable is true - */ - private shouldAnchorToCord(): boolean { - return process.env.ANCHOR_TO_CORD && process.env.ANCHOR_TO_CORD.toLowerCase().trim() === 'true'; - } - /** - * Anchors the credential to Cord blockchain and saves to the DB - */ - private async anchorCredentialToCord(credInReq: any, issueRequest: IssueCredentialDTO) { - if (!issueRequest.credentialSchemaId) { - this.logger.error('Credential SchemaId Schema ID is required for anchoring but is missing'); - throw new BadRequestException('Cord Schema ID is missing'); + /** + * Determines if anchoring to a blockchain is enabled based on environment variables. + * Checks for specific blockchain configurations and returns the appropriate method. + * @returns The blockchain method (e.g., 'cord', 'solana') if anchoring is enabled; otherwise, null. + */ + private shouldAnchorToBlockchain(): string | null { + // Check if the environment variable ANCHOR_TO_CORD is set to 'true' for the CORD blockchain + if ( + process.env.ANCHOR_TO_CORD && + process.env.ANCHOR_TO_CORD.toLowerCase().trim() === 'true' + ) { + return 'cord'; // Return 'cord' as the service method if CORD anchoring is enabled } - try { - this.logger.debug('Anchoring unsigned credential to Cord blockchain with schema ID:', issueRequest.credentialSchemaId); - - const anchorResponse = await this.anchorCordUtilsServices.anchorCredential({ - ...credInReq, - schemaId: issueRequest.credentialSchemaId, - }); - - this.logger.debug('Credential successfully anchored to Cord:', anchorResponse); - - const { - id, issuer, issuanceDate, validUntil: expirationDate, credentialSubject, proof, - } = anchorResponse.vc; - - const anchoredCredentialData = { - id, - type: issueRequest.credential.type, - issuer, - issuanceDate, - expirationDate, - subject: credentialSubject, - subjectId: (credentialSubject as JwtCredentialSubject).id, - proof, - credential_schema: issueRequest.credentialSchemaId, - signed: anchorResponse.vc as object, - tags: issueRequest.tags, - blockchainStatus: "ANCHORED" - - }; - - return this.saveCredentialToDatabase(anchoredCredentialData); - } catch (err) { - this.logger.error('Error anchoring credential to Cord blockchain:', err); - throw new InternalServerErrorException('Error anchoring credential to Cord blockchain'); + // Add additional checks here for other blockchains, e.g.,Solana, Ethereum, Polkadot + /* + if ( + process.env.ANCHOR_TO_SOLANA && + process.env.ANCHOR_TO_SOLANA.toLowerCase().trim() === 'true' + ) { + return 'solana'; // Return 'solana' if solana anchoring is enabled } + */ + + return null; // Return null if no blockchain anchoring is required } + /** * Signs the credential locally and saves it to the database */ @@ -415,7 +403,6 @@ export class CredentialsService { * Saves the credential to the database and returns the response */ private async saveCredentialToDatabase(credentialData: any) { - const newCred = await this.prisma.verifiableCredentials.create({ data: credentialData, }); diff --git a/services/credentials-service/src/credentials/factories/blockchain-anchor.factory.ts b/services/credentials-service/src/credentials/factories/blockchain-anchor.factory.ts new file mode 100644 index 000000000..0a7bfd5c3 --- /dev/null +++ b/services/credentials-service/src/credentials/factories/blockchain-anchor.factory.ts @@ -0,0 +1,38 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { AnchorCordService } from '../implementations/anchor-cord.service'; +import { BlockchainAnchor } from '../interfaces/blockchain_anchor.interface'; + +/** + * Factory class to dynamically resolve the appropriate BlockchainAnchor service. + * It uses the specified method to determine which implementation to return. + */ +@Injectable() +export class BlockchainAnchorFactory { + /** + * Constructor for the BlockchainAnchorFactory. + * @param cordService - An instance of AnchorCordService, which handles CORD-specific anchoring logic. + */ + constructor(private readonly cordService: AnchorCordService) { } + + /** + * Resolves the appropriate BlockchainAnchor service based on the provided method. + * @param method - The blockchain method (e.g., 'cord'). + * @returns The service instance corresponding to the specified method or null if no method is provided. + * @throws + */ + getAnchorService(method?: string): BlockchainAnchor | null { + // If no method is specified, return null to indicate no anchoring is required + if (!method) { + return null; + } + + // Determine the appropriate service implementation based on the method + switch (method) { + case 'cord': + // Return the CORD-specific implementation + return this.cordService; + default: + throw new BadRequestException(`Unsupported blockchain method: ${method}`); + } + } +} diff --git a/services/credentials-service/src/credentials/implementations/anchor-cord.service.ts b/services/credentials-service/src/credentials/implementations/anchor-cord.service.ts new file mode 100644 index 000000000..665174eb8 --- /dev/null +++ b/services/credentials-service/src/credentials/implementations/anchor-cord.service.ts @@ -0,0 +1,97 @@ +import { Injectable, Logger, InternalServerErrorException, BadRequestException } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { BlockchainAnchor } from '../interfaces/blockchain_anchor.interface'; +import { AxiosResponse } from '@nestjs/terminus/dist/health-indicator/http/axios.interfaces'; +import { IssueCredentialDTO } from '../dto/issue-credential.dto'; +import { JwtCredentialSubject } from 'src/app.interface'; +import { W3CCredential, Verifiable } from 'vc.types'; + +@Injectable() +export class AnchorCordService implements BlockchainAnchor { + + private readonly logger = new Logger(AnchorCordService.name); + + + constructor(private readonly httpService: HttpService) { + + } + + async anchorCredential(issueRequest: IssueCredentialDTO): Promise { + try { + const credInReq = issueRequest.credential; + if (!issueRequest.credentialSchemaId) { + + this.logger.error('Credential SchemaId Schema ID is required for anchoring but is missing'); + throw new BadRequestException('Cord Schema ID is missing'); + } + + this.logger.debug('url', process.env.ISSUER_AGENT_BASE_URL); + this.logger.debug('Anchoring unsigned credential to Cord blockchain with schema ID:', issueRequest.credentialSchemaId); + const credentialPayload = { + ...credInReq, + schemaId: issueRequest.credentialSchemaId, + } + let anchorHttpResponse: AxiosResponse = + await this.httpService.axiosRef.post( + `${process.env.ISSUER_AGENT_BASE_URL}/cred`, + { + credential: credentialPayload, + } + ); + + this.logger.debug('Credential successfully anchored'); + let anchoredResult = anchorHttpResponse.data.result; + this.logger.debug('Credential successfully anchored to Cord:', anchoredResult); + const { + id, issuer, issuanceDate, validUntil: expirationDate, credentialSubject, proof, + } = anchoredResult.vc; + + const anchoredCredentialData = { + id, + type: issueRequest.credential.type, + issuer, + issuanceDate, + expirationDate, + subject: credentialSubject, + subjectId: (credentialSubject as JwtCredentialSubject).id, + proof, + credential_schema: issueRequest.credentialSchemaId, + signed: anchoredResult.vc as object, + tags: issueRequest.tags, + blockchainStatus: "ANCHORED", + + }; + return anchoredCredentialData; + } catch (err) { + this.logger.error('Error anchoring credential:', err); + + throw new InternalServerErrorException(`Error anchoring credential : ${err.response.data.details}`); + } + } + + + async verifyCredential( + credToVerify: Verifiable + ): Promise { + try { + this.logger.debug(`${process.env.VERIFICATION_MIDDLEWARE_BASE_URL}/credentials/verify}`) + const response = await this.httpService.axiosRef.post( + `${process.env.VERIFICATION_MIDDLEWARE_BASE_URL}/credentials/verify`, + credToVerify + ); + + if (response.status !== 200) { + this.logger.error('Cord verification failed:', response.data); + throw new InternalServerErrorException('Cord verification failed'); + } + + return response.data; + } catch (err) { + this.logger.error('Error calling Cord verification API:', err); + throw new InternalServerErrorException( + 'Error verifying credential on Cord' + ); + } + } + +} diff --git a/services/credentials-service/src/credentials/interfaces/blockchain_anchor.interface.ts b/services/credentials-service/src/credentials/interfaces/blockchain_anchor.interface.ts new file mode 100644 index 000000000..b70d8f24e --- /dev/null +++ b/services/credentials-service/src/credentials/interfaces/blockchain_anchor.interface.ts @@ -0,0 +1,9 @@ +export interface BlockchainAnchor { + /** + * Anchors a Scheam to the blockchain. + * @param body The request payload for anchoring. + * @returns The anchored Schema or related data. + */ + anchorCredential(body: any): Promise; + verifyCredential(body: any): Promise; +}