Skip to content

Commit

Permalink
Credential-service:Interface Implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwin275 committed Dec 13, 2024
1 parent 391e05e commit a66c279
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 { }
97 changes: 42 additions & 55 deletions services/credentials-service/src/credentials/credentials.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -150,9 +151,14 @@ export class CredentialsService {

async verifyCredential(credToVerify: Verifiable<W3CCredential>, 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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<any> {
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<W3CCredential>
): Promise<any> {
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'
);
}
}

}
Original file line number Diff line number Diff line change
@@ -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<any>;
verifyCredential(body: any): Promise<any>;
}

0 comments on commit a66c279

Please sign in to comment.