Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions apps/agent-provisioning/src/agent-provisioning.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { IWalletProvision } from './interface/agent-provisioning.interfaces';
import * as dotenv from 'dotenv';
import { AgentType } from '@credebl/enum/enum';
import * as fs from 'fs';

import { Injectable, Logger, NotFoundException } from '@nestjs/common';

import { AgentType } from '@credebl/enum/enum';
import { IWalletProvision } from './interface/agent-provisioning.interfaces';
import { RpcException } from '@nestjs/microservices';
import { exec } from 'child_process';

dotenv.config();

@Injectable()
Expand Down
37 changes: 19 additions & 18 deletions apps/agent-service/src/agent-service.controller.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AgentServiceService } from './agent-service.service';
import {
IAgentConfigure,
IAgentProofRequest,
IAgentSpinupDto,
IAgentStatus,
IConnectionDetails,
IUserRequestInterface,
ISendProofRequestPayload,
IAgentSpinUpSatus,
ICreateConnectionInvitation,
IDidCreate,
IGetCredDefAgentRedirection,
IGetSchemaAgentRedirection,
IAgentSpinupDto,
IIssuanceCreateOffer,
IOutOfBandCredentialOffer,
ISendProofRequestPayload,
IStoreAgent,
IStoreOrgAgentDetails,
ITenantCredDef,
ITenantDto,
ITenantSchema,
IOutOfBandCredentialOffer,
IAgentProofRequest,
IDidCreate,
IWallet,
ITenantRecord,
ICreateConnectionInvitation,
IStoreAgent,
IAgentConfigure
ITenantSchema,
IUserRequestInterface,
IWallet
} from './interface/agent-service.interface';
import { user } from '@prisma/client';
import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface';

import { AgentServiceService } from './agent-service.service';
import { AgentSpinUpStatus } from '@credebl/enum/enum';
import { Controller } from '@nestjs/common';
import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface';
import { MessagePattern } from '@nestjs/microservices';
import { SignDataDto } from '../../api-gateway/src/agent-service/dto/agent-service.dto';
import { user } from '@prisma/client';

@Controller()
export class AgentServiceController {
Expand All @@ -49,7 +50,7 @@ export class AgentServiceController {
async createTenant(payload: {
createTenantDto: ITenantDto;
user: IUserRequestInterface;
}): Promise<IAgentSpinUpSatus> {
}): Promise<IStoreOrgAgentDetails> {
return this.agentServiceService.createTenant(payload.createTenantDto, payload.user);
}

Expand Down
112 changes: 36 additions & 76 deletions apps/agent-service/src/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
ISendProofRequestPayload,
IIssuanceCreateOffer,
IOutOfBandCredentialOffer,
IAgentSpinUpSatus,
ICreateTenant,
IAgentStatus,
ICreateOrgAgent,
Expand Down Expand Up @@ -126,15 +125,15 @@
}> {
let agentProcess: ICreateOrgAgent;
try {
await this.processWalletProvision(agentSpinupDto, user);
return { agentSpinupStatus: AgentSpinUpStatus.PROCESSED };
await this.provisionWallet(agentSpinupDto, user);
return { agentSpinupStatus: AgentSpinUpStatus.DID_CREATED };
} catch (error) {
this.handleErrorOnWalletProvision(agentSpinupDto, error, agentProcess);
throw new RpcException(error.response ?? error);
}
}

private async processWalletProvision(agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface): Promise<void> {
private async provisionWallet(agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface): Promise<void> {
let platformAdminUser;
let userId: string;
let agentProcess: ICreateOrgAgent;
Expand All @@ -144,6 +143,7 @@
this.agentServiceRepository.getPlatformConfigDetails(),
this.agentServiceRepository.getAgentTypeDetails(),
this.agentServiceRepository.getLedgerDetails(
// TODO: Do we want to get first element from ledgerName

Check warning on line 146 in apps/agent-service/src/agent-service.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZrYqBck27eHY-4VIvZv&open=AZrYqBck27eHY-4VIvZv&pullRequest=1528
agentSpinupDto.ledgerName ? agentSpinupDto.ledgerName : [Ledgers.Indicio_Demonet]
)
Comment thread
pranalidhanavade marked this conversation as resolved.
]);
Expand Down Expand Up @@ -182,20 +182,13 @@
// Get genesis URL and ledger details
const ledgerDetails = await this.agentServiceRepository.getGenesisUrl(agentSpinupDto.ledgerId);

if (AgentSpinUpStatus.PROCESSED === getOrgAgent?.agentSpinUpStatus) {
throw new BadRequestException(ResponseMessages.agent.error.walletAlreadyProcessing, {
if (AgentSpinUpStatus.WALLET_CREATED === getOrgAgent?.agentSpinUpStatus) {
throw new BadRequestException(ResponseMessages.agent.error.walletAlreadyCreated, {
cause: new Error(),
description: ResponseMessages.errorMessages.badRequest
});
}

if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) {
throw new ConflictException(ResponseMessages.agent.error.walletAlreadyCreated, {
cause: new Error(),
description: ResponseMessages.errorMessages.conflict
});
}

if (!agentSpinupDto.orgId) {
if (platformAdminOrgDetails) {
agentSpinupDto.orgId = platformAdminOrgDetails;
Expand Down Expand Up @@ -229,10 +222,10 @@
const socket: Socket = await this.initSocketConnection(`${process.env.SOCKET_HOST}`);
this.emitAgentSpinupInitiatedEvent(agentSpinupDto, socket);

const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED;
agentProcess = await this.createOrgAgent(agentSpinUpStatus, userId);
const agentSpinUpStatus = AgentSpinUpStatus.WALLET_CREATED;
agentProcess = await this.createOrgAgent(agentSpinUpStatus, userId, agentSpinupDto.orgId);

// AFJ agent spin-up
// Credo agent spin-up
this._agentSpinup(
walletProvisionPayload,
agentSpinupDto,
Expand Down Expand Up @@ -298,7 +291,7 @@
const storeAgentConfig = await this.agentServiceRepository.storeOrgAgentDetails({
did,
isDidPublic: true,
agentSpinUpStatus: AgentSpinUpStatus.COMPLETED,
agentSpinUpStatus: AgentSpinUpStatus.DID_CREATED,
walletName,
agentsTypeId,
orgId,
Expand Down Expand Up @@ -441,9 +434,9 @@
return socket;
}

async createOrgAgent(agentSpinUpStatus: AgentSpinUpStatus, userId: string): Promise<ICreateOrgAgent> {
async createOrgAgent(agentSpinUpStatus: AgentSpinUpStatus, userId: string, orgId: string): Promise<ICreateOrgAgent> {
try {
const agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, userId);
const agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, userId, orgId);
this.logger.log(`Organization agent created with status: ${agentSpinUpStatus}`);
return agentProcess;
} catch (error) {
Expand Down Expand Up @@ -488,7 +481,7 @@
ledgerId: string[],
agentProcess: ICreateOrgAgent
): Promise<void> {
let ledgerIdData = [];
let ledgerIdData;

try {
if (agentSpinupDto.method !== DidMethod.KEY && agentSpinupDto.method !== DidMethod.WEB) {
Expand Down Expand Up @@ -626,7 +619,7 @@
did: '',
verkey: '',
isDidPublic: true,
agentSpinUpStatus: AgentSpinUpStatus.COMPLETED,
agentSpinUpStatus: AgentSpinUpStatus.DID_CREATED,
walletName: payload.walletName,
agentsTypeId: payload.agentsTypeId,
orgId: payload.orgId,
Expand Down Expand Up @@ -742,32 +735,20 @@
* @param user
* @returns Get agent status
*/
async createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<IAgentSpinUpSatus> {
async createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<IStoreOrgAgentDetails> {
try {
const agentStatusResponse = {
agentSpinupStatus: AgentSpinUpStatus.PROCESSED
};
const getOrgAgent = await this.agentServiceRepository.getAgentDetails(payload.orgId);

if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) {
this.logger.error(`Your wallet is already been created.`);
if (AgentSpinUpStatus.WALLET_CREATED === getOrgAgent?.agentSpinUpStatus) {
this.logger.error(`Your wallet is already created.`);
throw new ConflictException(ResponseMessages.agent.error.walletAlreadyCreated, {
cause: new Error(),
description: ResponseMessages.errorMessages.conflict
});
}

if (AgentSpinUpStatus.PROCESSED === getOrgAgent?.agentSpinUpStatus) {
this.logger.error(`Your wallet is already processing.`);
throw new ConflictException(ResponseMessages.agent.error.walletAlreadyProcessing, {
cause: new Error(),
description: ResponseMessages.errorMessages.conflict
});
}

// Create tenant
this._createTenant(payload, user);
return agentStatusResponse;
const createdTenant = await this._createTenant(payload, user);
return createdTenant;
} catch (error) {
Comment on lines +742 to 752
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent orphan org_agents on tenant creation errors and tighten “wallet already created” guard.

Two related issues in the shared-agent tenant path:

  1. Orphan org_agents records on failure

In _createTenant:

const agentSpinUpStatus = AgentSpinUpStatus.WALLET_CREATED;
agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, user?.id, payload.orgId);
// ...
const orgAgentDetails = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData);
// ...
return orgAgentDetails;
} catch (error) {
  this.handleError(error, payload.clientSocketId);
  throw error;
}

If any error occurs after agentProcess is created (e.g. tenant creation fails, NATS issues, invitation failure), the catch block calls handleError, which only emits a socket event and logs. The partially created org_agents row is never cleaned up, leaving the DB in an inconsistent state.

You already handle cleanup correctly elsewhere (_agentSpinup and handleErrorOnWalletProvision remove the agent on error). Do the same here:

   } catch (error) {
-    this.handleError(error, payload.clientSocketId);
-    throw error;
+    this.handleError(error, payload.clientSocketId);
+
+    if (agentProcess?.id) {
+      try {
+        await this.agentServiceRepository.removeOrgAgent(agentProcess.id);
+      } catch (cleanupError) {
+        this.logger.error(
+          `[_createTenant] - Failed to cleanup org_agent ${agentProcess.id}: ${JSON.stringify(cleanupError)}`
+        );
+      }
+    }
+    throw error;
   }
  1. “Wallet already created” only checks WALLET_CREATED

In createTenant:

const getOrgAgent = await this.agentServiceRepository.getAgentDetails(payload.orgId);

if (AgentSpinUpStatus.WALLET_CREATED === getOrgAgent?.agentSpinUpStatus) {
  this.logger.error(`Your wallet is already created.`);
  throw new ConflictException(ResponseMessages.agent.error.walletAlreadyCreated, ...);
}

Once a DID is created and status is advanced to DID_CREATED, this guard no longer triggers, even though the org clearly already has a wallet. You probably want to block duplicate wallet creation for any non‑PENDING state, e.g.:

-    if (AgentSpinUpStatus.WALLET_CREATED === getOrgAgent?.agentSpinUpStatus) {
+    if (
+      getOrgAgent &&
+      [AgentSpinUpStatus.WALLET_CREATED, AgentSpinUpStatus.DID_CREATED].includes(
+        getOrgAgent.agentSpinUpStatus
+      )
+    ) {
       this.logger.error(`Your wallet is already created.`);
       throw new ConflictException(ResponseMessages.agent.error.walletAlreadyCreated, {
         cause: new Error(),
         description: ResponseMessages.errorMessages.conflict
       });
     }

This gives a consistent business rule and avoids leaking low-level DB uniqueness errors back to clients.

Also applies to: 764-848

🤖 Prompt for AI Agents
In apps/agent-service/src/agent-service.service.ts around lines 742 to 752, the
current logic throws a ConflictException only when agentSpinUpStatus ===
WALLET_CREATED and creates an org_agent before tenant creation without cleaning
it up on downstream failures; update two things: 1) tighten the pre-check to
treat any non-PENDING agentSpinUpStatus (e.g. DID_CREATED, WALLET_CREATED, or
any terminal state) as a conflict so duplicate wallet/tenant creation is blocked
consistently, and 2) ensure that any partial org_agent created during the
_createTenant path is removed on error — after creating the initial org_agent
record, wrap subsequent steps in try/catch and in the catch call the same
cleanup used elsewhere (remove/delete the org_agent record via the repository)
before rethrowing or handling the error and emitting socket events.

this.logger.error(`error in create tenant : ${JSON.stringify(error)}`);
throw new RpcException(error.response ? error.response : error);
Expand All @@ -780,7 +761,7 @@
* @param user
* @returns Get agent status
*/
async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<void> {
async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<IStoreOrgAgentDetails> {
let agentProcess;
let ledgerIdData = [];
try {
Expand All @@ -800,28 +781,29 @@
description: ResponseMessages.errorMessages.notFound
});
}
ledgerIdData = await this.agentServiceRepository.getLedgerDetails(ledger);

Check warning on line 784 in apps/agent-service/src/agent-service.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this useless assignment to variable "ledgerIdData".

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZrAPdZjkOgkTFjvADiT&open=AZrAPdZjkOgkTFjvADiT&pullRequest=1528

const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED;
const agentSpinUpStatus = AgentSpinUpStatus.WALLET_CREATED;

// Create and stored agent details
agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, user?.id);
agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, user?.id, payload.orgId);

// Get platform admin details
const platformAdminSpinnedUp = await this.getPlatformAdminAndNotify(payload.clientSocketId);

payload.endpoint = platformAdminSpinnedUp.org_agents[0].agentEndPoint;
// Create tenant wallet and DID
// Create tenant wallet
const tenantDetails = await this.createTenantAndNotify(payload, platformAdminSpinnedUp);
if (!tenantDetails?.walletResponseDetails?.id || !tenantDetails?.DIDCreationOption?.did) {

if (!tenantDetails?.walletResponseDetails?.id) {
this.logger.error(`Error in getting wallet id and wallet did`);
throw new NotFoundException(ResponseMessages.agent.error.notAbleToSpinUpAgent, {
cause: new Error(),
description: ResponseMessages.errorMessages.notFound
});
}

if (AgentSpinUpStatus.COMPLETED !== platformAdminSpinnedUp.org_agents[0].agentSpinUpStatus) {
if (AgentSpinUpStatus.DID_CREATED !== platformAdminSpinnedUp.org_agents[0].agentSpinUpStatus) {
this.logger.error(`Platform-admin agent is not spun-up`);
throw new NotFoundException(ResponseMessages.agent.error.platformAdminNotAbleToSpinp, {
cause: new Error(),
Expand All @@ -833,50 +815,34 @@
// Get agent type details
const agentTypeId = await this.agentServiceRepository.getAgentTypeId(AgentType.AFJ);
const storeOrgAgentData: IStoreOrgAgentDetails = {
did: tenantDetails.DIDCreationOption.did,
isDidPublic: true,
didDoc: tenantDetails.DIDCreationOption.didDocument || tenantDetails.DIDCreationOption.didDoc, //changed the didDoc into didDocument
agentSpinUpStatus: AgentSpinUpStatus.COMPLETED,
agentSpinUpStatus: AgentSpinUpStatus.WALLET_CREATED,
agentsTypeId: agentTypeId,
orgId: payload.orgId,
agentEndPoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint,
orgAgentTypeId,
tenantId: tenantDetails.walletResponseDetails['id'],
walletName: payload.label,
ledgerId: ledgerIdData.map((item) => item.id),
id: agentProcess?.id,
apiKey: await this.commonService.dataEncryption(tenantDetails.walletResponseDetails['token'])
};

// Get organization data
const getOrganization = await this.agentServiceRepository.getOrgDetails(payload.orgId);

this.notifyClientSocket('agent-spinup-process-completed', payload.clientSocketId);

const orgAgentDetails = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData);

const createdDidDetails = {
orgId: payload.orgId,
did: tenantDetails.DIDCreationOption.did,
didDocument: tenantDetails.DIDCreationOption.didDocument || tenantDetails.DIDCreationOption.didDoc,
isPrimaryDid: true,
orgAgentId: orgAgentDetails.id,
userId: user.id
};

await this.agentServiceRepository.storeDidDetails(createdDidDetails);

this.notifyClientSocket('invitation-url-creation-started', payload.clientSocketId);

// Create the legacy connection invitation
await this._createConnectionInvitation(payload.orgId, user, getOrganization.name);

this.notifyClientSocket('invitation-url-creation-success', payload.clientSocketId);

return orgAgentDetails;
} catch (error) {
this.handleError(error, payload.clientSocketId);

if (agentProcess && agentProcess?.id) {
this.agentServiceRepository.removeOrgAgent(agentProcess?.id);
}
throw error;
}
}
Expand Down Expand Up @@ -930,7 +896,6 @@
}
}
}

const getApiKey = await this.getOrgAgentApiKey(orgId);
const url = this.constructUrl(agentDetails);

Expand Down Expand Up @@ -961,6 +926,9 @@
if (isPrimaryDid) {
await this.setPrimaryDidAndLedger(orgId, storeDidDetails, createDidPayload.network, createDidPayload.method);
}
if (agentDetails.agentSpinUpStatus === AgentSpinUpStatus.WALLET_CREATED) {
await this.agentServiceRepository.updateAgentSpinupStatus(orgId);
}

return storeDidDetails;
} catch (error) {
Expand Down Expand Up @@ -1119,20 +1087,12 @@
platformAdminSpinnedUp.org_agents[0].agentEndPoint,
getDcryptedToken
);
if (!walletResponseDetails && !walletResponseDetails.id && !walletResponseDetails.token) {
if (!walletResponseDetails || !walletResponseDetails.id || !walletResponseDetails.token) {

Check warning on line 1090 in apps/agent-service/src/agent-service.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZrYqBck27eHY-4VIvZw&open=AZrYqBck27eHY-4VIvZw&pullRequest=1528
throw new InternalServerErrorException('Error while creating the wallet');
}
const didCreateOption = {
didPayload: WalletSetupPayload,
agentEndpoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint,
apiKey: walletResponseDetails.token
return {
walletResponseDetails
};
const DIDCreationOption = await this._createDID(didCreateOption);
if (!DIDCreationOption) {
throw new InternalServerErrorException('Error while creating the wallet');
}

return { walletResponseDetails, DIDCreationOption };
}
//

Expand Down
Loading